/// <summary>
        /// checks to see whether the solution number eigenvalue and eigenvector is unphysical and therefore shouldn't be added
        /// to the boundary condition matrix
        /// </summary>
        bool Add_BC_EigenSolution(int solution_no, DoubleComplexEigDecomp eig_decomp, Boundary boundary)
        {
            double eigenvalue_norm = DoubleComplex.Norm(eig_decomp.EigenValue(solution_no));

            // check for propagating solutions
            if (Math.Abs(eigenvalue_norm - 1.0) < propagating_mode_error)
            {
                if (eig_decomp.EigenValue(solution_no).Imag > 0 && boundary == Boundary.left)
                {
                    return(true);
                }
                else if (eig_decomp.EigenValue(solution_no).Imag < 0 && boundary == Boundary.right)
                {
                    return(true);
                }
                else if (eig_decomp.EigenValue(solution_no).Imag == 0)
                {
                    throw new NotImplementedException();
                }
            }
            // for evanescent solutions, check for unphysical solutions, ie. |alpha| > 1 for left lead
            else if (eigenvalue_norm > 1.0 && boundary == Boundary.left)
            {
                return(true);
            }
            // and |alpha| < 1 for right lead
            else if (eigenvalue_norm < 1.0 && boundary == Boundary.right)
            {
                return(true);
            }

            // if these properties are not satisfied, do not add the eigenvalue and eigenvector to the BC (default behaviour)
            return(false);
        }
        private DoubleComplexMatrix Calculate_Boundary_Conditions(Boundary boundary, double energy)
        {
            DoubleComplexMatrix bc_matrix = new DoubleComplexMatrix(2 * ny, 2 * ny);

            // generate Hamiltonians for the boundaries
            DoubleComplexMatrix tmp_slice;
            if (boundary == Boundary.left)
                tmp_slice = Generate_Slice_Hamiltonian(-0.5 * (nx - 1) * dx);
            else if (boundary == Boundary.right)
                tmp_slice = Generate_Slice_Hamiltonian(0.5 * (nx - 1) * dx);
            else throw new NotImplementedException();
            DoubleComplexMatrix tmp_hopping_trans = Generate_Hopping_Hamiltonian().Transpose();

            // create temporary matrix for top-left of BC matrix
            DoubleComplexMatrix tmp_matrix = Product(tmp_hopping_trans, (new DoubleComplexMatrix(energy * DoubleMatrix.Identity(ny)) - tmp_slice));

            // fill with transfer matrix
            for (int i = 0; i < ny; i++)
                for (int j = 0; j < ny; j++)
                {
                    bc_matrix[i, j] = tmp_matrix[i, j];
                    bc_matrix[i + ny, j] = tmp_hopping_trans[i, j];
                    bc_matrix[i, j + ny] = -1.0 * tmp_hopping_trans[i, j];
                }

            // solve eigen-problem for transfer matrix
            DoubleComplexEigDecomp eig_decomp = new DoubleComplexEigDecomp(bc_matrix);
            DoubleComplexEigDecompServer server = new DoubleComplexEigDecompServer();
            eig_decomp = server.Factor(bc_matrix);

            // fill the eigenvalue matrix, excluding unphysical solutions which blow-up in the wire
            DoubleComplexMatrix eig_vals = new DoubleComplexMatrix(ny, ny);
            DoubleComplexMatrix eig_vecs = new DoubleComplexMatrix(ny, ny);
            int count = 0;
            for (int i = 0; i < 2 * ny; i++)
                if (Add_BC_EigenSolution(i, eig_decomp, boundary))
                {
                    // invert eigenvalues if calculating for the right lead
                    if (boundary == Boundary.left)
                        eig_vals[count, count] = eig_decomp.EigenValue(i);
                    else if (boundary == Boundary.right)
                        eig_vals[count, count] = 1.0 / eig_decomp.EigenValue(i);
                    else throw new NotImplementedException();

                    // insert corresponding (normalised) eigenvector into the matrix
                    double norm = 0.0;
                    for (int j = 0; j < ny; j++)
                        norm += DoubleComplex.Norm(eig_decomp.RightEigenVector(i)[j]) * DoubleComplex.Norm(eig_decomp.RightEigenVector(i)[j]);
                    double inv_norm = 1.0 / Math.Sqrt(norm);
                    for (int j = 0; j < ny; j++)
                        eig_vecs[j, count] = inv_norm * eig_decomp.RightEigenVector(i)[j];

                    count++;
                }

            // calculate coefficient matrix A and output Green's function
            DoubleComplexMatrix coefficient_matrix = new DoubleComplexMatrix(ny, ny);
            coefficient_matrix = Product(Product(tmp_hopping_trans.Transpose(), eig_vecs), eig_vals);
            DoubleComplexLUFact lu_fact = new DoubleComplexLUFact(coefficient_matrix);
            return new DoubleComplexMatrix(Product(eig_vecs, lu_fact.Inverse()));

            /*          // allocate for evanescent modes
                      if (boundary == Boundary.left && DoubleComplex.Norm(eig_decomp.EigenValue(i)) < 1.0 - propagating_mode_error)
                          eig_vals[i, i] = eig_decomp.EigenValue(i);
                      else if (boundary == Boundary.right && DoubleComplex.Norm(eig_decomp.EigenValue(i)) > 1.0 + propagating_mode_error)
                          eig_vals[i, i] = 1.0 / eig_decomp.EigenValue(i);
                      // and for propagating modes
                      else if (Math.Abs(DoubleComplex.Norm(eig_decomp.EigenValue(i)) - 1.0) < propagating_mode_error)
                          eig_vals[i, i]
                      else
                          throw new InvalidArgumentException("Error - Cannot have eigenvalue of transfer matrix with value " + eig_decomp.EigenValue(i).ToString() + "!");

                  // fill the eigenvector matrix with only the top half of the eigenvector
                  DoubleComplexMatrix eig_vecs = new DoubleComplexMatrix(ny, ny);
                  DoubleComplexMatrix inv_eigvec = new DoubleComplexMatrix(ny, ny);
                  for (int i = 0; i < ny; i++)
                  {
                      DoubleComplexVector tmpvec_right = eig_decomp.RightEigenVector(i);
                      DoubleComplexVector tmpvec_left = eig_decomp.LeftEigenVector(i);

                      // normalise the top of half of the eigenvector
                      double norm2_right = 0.0;
                      double norm2_left = 0.0;
                      for (int j = 0; j < ny; j++)
                      {
                          norm2_right += NMathFunctions.Abs(tmpvec_right[i]) * NMathFunctions.Abs(tmpvec_right[i]);
                          norm2_left += NMathFunctions.Abs(tmpvec_left[i]) * NMathFunctions.Abs(tmpvec_left[i]);
                      }
                      double norm_left = Math.Sqrt(norm2_left);
                      double norm_right = Math.Sqrt(norm2_right);

                      // and insert it into the matrix
                      for (int j = 0; j < ny; j++)
                      {
                          eig_vecs[j, i] = tmpvec_right[j] / norm_right;
                          inv_eigvec[i, j] = tmpvec_left[j] / norm_left;
                      }
                  }

                  // get the inverse of the eigenvector matrix
               //           DoubleComplexMatrix tmp_eigvec = new DoubleComplexMatrix(eig_vecs);
               //           DoubleComplexMatrix inv_eigvec = NMathFunctions.PseudoInverse(tmp_eigvec);
                  //DoubleComplexLUFact eigvec_fact = new DoubleComplexLUFact(tmp_eigvec);
                  //DoubleComplexMatrix inv_eigvec = eigvec_fact.Inverse();

                  // Calculate the on-site Greens function of the end of the wire and return it
                  if (boundary == Boundary.left)
                      return Product(Product(Product(eig_vecs, eig_vals), inv_eigvec), tmp_hopping_trans);
                  else if (boundary == Boundary.right)
                      return Product(Product(Product(eig_vecs, eig_vals), inv_eigvec), tmp_hopping);
                  else
                      throw new NotImplementedException();
                  */
        }
        private DoubleComplexMatrix Calculate_Boundary_Conditions(Boundary boundary, double energy)
        {
            DoubleComplexMatrix bc_matrix = new DoubleComplexMatrix(2 * ny, 2 * ny);

            // generate Hamiltonians for the boundaries
            DoubleComplexMatrix tmp_slice;

            if (boundary == Boundary.left)
            {
                tmp_slice = Generate_Slice_Hamiltonian(-0.5 * (nx - 1) * dx);
            }
            else if (boundary == Boundary.right)
            {
                tmp_slice = Generate_Slice_Hamiltonian(0.5 * (nx - 1) * dx);
            }
            else
            {
                throw new NotImplementedException();
            }
            DoubleComplexMatrix tmp_hopping_trans = Generate_Hopping_Hamiltonian().Transpose();

            // create temporary matrix for top-left of BC matrix
            DoubleComplexMatrix tmp_matrix = Product(tmp_hopping_trans, (new DoubleComplexMatrix(energy * DoubleMatrix.Identity(ny)) - tmp_slice));

            // fill with transfer matrix
            for (int i = 0; i < ny; i++)
            {
                for (int j = 0; j < ny; j++)
                {
                    bc_matrix[i, j]      = tmp_matrix[i, j];
                    bc_matrix[i + ny, j] = tmp_hopping_trans[i, j];
                    bc_matrix[i, j + ny] = -1.0 * tmp_hopping_trans[i, j];
                }
            }

            // solve eigen-problem for transfer matrix
            DoubleComplexEigDecomp       eig_decomp = new DoubleComplexEigDecomp(bc_matrix);
            DoubleComplexEigDecompServer server     = new DoubleComplexEigDecompServer();

            eig_decomp = server.Factor(bc_matrix);

            // fill the eigenvalue matrix, excluding unphysical solutions which blow-up in the wire
            DoubleComplexMatrix eig_vals = new DoubleComplexMatrix(ny, ny);
            DoubleComplexMatrix eig_vecs = new DoubleComplexMatrix(ny, ny);
            int count = 0;

            for (int i = 0; i < 2 * ny; i++)
            {
                if (Add_BC_EigenSolution(i, eig_decomp, boundary))
                {
                    // invert eigenvalues if calculating for the right lead
                    if (boundary == Boundary.left)
                    {
                        eig_vals[count, count] = eig_decomp.EigenValue(i);
                    }
                    else if (boundary == Boundary.right)
                    {
                        eig_vals[count, count] = 1.0 / eig_decomp.EigenValue(i);
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }

                    // insert corresponding (normalised) eigenvector into the matrix
                    double norm = 0.0;
                    for (int j = 0; j < ny; j++)
                    {
                        norm += DoubleComplex.Norm(eig_decomp.RightEigenVector(i)[j]) * DoubleComplex.Norm(eig_decomp.RightEigenVector(i)[j]);
                    }
                    double inv_norm = 1.0 / Math.Sqrt(norm);
                    for (int j = 0; j < ny; j++)
                    {
                        eig_vecs[j, count] = inv_norm * eig_decomp.RightEigenVector(i)[j];
                    }

                    count++;
                }
            }

            // calculate coefficient matrix A and output Green's function
            DoubleComplexMatrix coefficient_matrix = new DoubleComplexMatrix(ny, ny);

            coefficient_matrix = Product(Product(tmp_hopping_trans.Transpose(), eig_vecs), eig_vals);
            DoubleComplexLUFact lu_fact = new DoubleComplexLUFact(coefficient_matrix);

            return(new DoubleComplexMatrix(Product(eig_vecs, lu_fact.Inverse())));

            /*          // allocate for evanescent modes
             *        if (boundary == Boundary.left && DoubleComplex.Norm(eig_decomp.EigenValue(i)) < 1.0 - propagating_mode_error)
             *            eig_vals[i, i] = eig_decomp.EigenValue(i);
             *        else if (boundary == Boundary.right && DoubleComplex.Norm(eig_decomp.EigenValue(i)) > 1.0 + propagating_mode_error)
             *            eig_vals[i, i] = 1.0 / eig_decomp.EigenValue(i);
             *        // and for propagating modes
             *        else if (Math.Abs(DoubleComplex.Norm(eig_decomp.EigenValue(i)) - 1.0) < propagating_mode_error)
             *            eig_vals[i, i]
             *        else
             *            throw new InvalidArgumentException("Error - Cannot have eigenvalue of transfer matrix with value " + eig_decomp.EigenValue(i).ToString() + "!");
             *
             *    // fill the eigenvector matrix with only the top half of the eigenvector
             *    DoubleComplexMatrix eig_vecs = new DoubleComplexMatrix(ny, ny);
             *    DoubleComplexMatrix inv_eigvec = new DoubleComplexMatrix(ny, ny);
             *    for (int i = 0; i < ny; i++)
             *    {
             *        DoubleComplexVector tmpvec_right = eig_decomp.RightEigenVector(i);
             *        DoubleComplexVector tmpvec_left = eig_decomp.LeftEigenVector(i);
             *
             *        // normalise the top of half of the eigenvector
             *        double norm2_right = 0.0;
             *        double norm2_left = 0.0;
             *        for (int j = 0; j < ny; j++)
             *        {
             *            norm2_right += NMathFunctions.Abs(tmpvec_right[i]) * NMathFunctions.Abs(tmpvec_right[i]);
             *            norm2_left += NMathFunctions.Abs(tmpvec_left[i]) * NMathFunctions.Abs(tmpvec_left[i]);
             *        }
             *        double norm_left = Math.Sqrt(norm2_left);
             *        double norm_right = Math.Sqrt(norm2_right);
             *
             *        // and insert it into the matrix
             *        for (int j = 0; j < ny; j++)
             *        {
             *            eig_vecs[j, i] = tmpvec_right[j] / norm_right;
             *            inv_eigvec[i, j] = tmpvec_left[j] / norm_left;
             *        }
             *    }
             *
             *    // get the inverse of the eigenvector matrix
             * //           DoubleComplexMatrix tmp_eigvec = new DoubleComplexMatrix(eig_vecs);
             * //           DoubleComplexMatrix inv_eigvec = NMathFunctions.PseudoInverse(tmp_eigvec);
             *    //DoubleComplexLUFact eigvec_fact = new DoubleComplexLUFact(tmp_eigvec);
             *    //DoubleComplexMatrix inv_eigvec = eigvec_fact.Inverse();
             *
             *    // Calculate the on-site Greens function of the end of the wire and return it
             *    if (boundary == Boundary.left)
             *        return Product(Product(Product(eig_vecs, eig_vals), inv_eigvec), tmp_hopping_trans);
             *    else if (boundary == Boundary.right)
             *        return Product(Product(Product(eig_vecs, eig_vals), inv_eigvec), tmp_hopping);
             *    else
             *        throw new NotImplementedException();
             */
        }
        /// <summary>
        /// checks to see whether the solution number eigenvalue and eigenvector is unphysical and therefore shouldn't be added
        /// to the boundary condition matrix
        /// </summary>
        bool Add_BC_EigenSolution(int solution_no, DoubleComplexEigDecomp eig_decomp, Boundary boundary)
        {
            double eigenvalue_norm = DoubleComplex.Norm(eig_decomp.EigenValue(solution_no));

            // check for propagating solutions
            if (Math.Abs(eigenvalue_norm - 1.0) < propagating_mode_error)
            {
                if (eig_decomp.EigenValue(solution_no).Imag > 0 && boundary == Boundary.left)
                    return true;
                else if (eig_decomp.EigenValue(solution_no).Imag < 0 && boundary == Boundary.right)
                    return true;
                else if (eig_decomp.EigenValue(solution_no).Imag == 0)
                    throw new NotImplementedException();
            }
            // for evanescent solutions, check for unphysical solutions, ie. |alpha| > 1 for left lead
            else if (eigenvalue_norm > 1.0 && boundary == Boundary.left)
                return true;
            // and |alpha| < 1 for right lead
            else if (eigenvalue_norm < 1.0 && boundary == Boundary.right)
                return true;

            // if these properties are not satisfied, do not add the eigenvalue and eigenvector to the BC (default behaviour)
            return false;
        }