├── .clang-format ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── Doxyfile ├── LICENSE ├── README.md ├── codecov.yml ├── doxygen_src └── mainpage.dox ├── include └── lambda_lanczos │ ├── eigenpair_manager.hpp │ ├── exponentiator.hpp │ ├── lambda_lanczos.hpp │ ├── lambda_lanczos_tridiagonal.hpp │ ├── lambda_lanczos_tridiagonal_impl.hpp │ ├── lambda_lanczos_tridiagonal_lapack.hpp │ ├── lambda_lanczos_util.hpp │ ├── macro.hpp │ └── util │ ├── common.hpp │ ├── linear_algebra.hpp │ └── linear_algebra_lapack.hpp ├── src ├── CMakeLists.txt ├── determine_eigenvalue_offset │ └── determine_eigenvalue_offset.cpp └── samples │ ├── CMakeLists.txt │ ├── sample1_simple.cpp │ ├── sample2_sparse.cpp │ ├── sample3_dynamic.cpp │ ├── sample4_use_Eigen_library.cpp │ └── sample5_multiroot.cpp └── test ├── CMakeLists.txt ├── exponentiator_test.cpp └── lambda_lanczos_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | ColumnLimit: 120 5 | AllowShortBlocksOnASingleLine: Empty 6 | AllowShortFunctionsOnASingleLine: Empty 7 | BinPackArguments: false 8 | BinPackParameters: false 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | env: 6 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 7 | BUILD_TYPE: Debug 8 | TARGET: lambda_lanczos_test 9 | 10 | jobs: 11 | build: 12 | # The CMake configure and build commands are platform agnostic and should work equally 13 | # well on Windows or Mac. You can convert this to a matrix build if you need 14 | # cross-platform coverage. 15 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Install external programs and libraries 22 | run: | 23 | sudo apt-get update 24 | sudo apt-get install valgrind 25 | sudo apt-get install libtbb-dev 26 | sudo apt-get install libblas-dev liblapacke-dev 27 | 28 | 29 | - name: Prepare Google Test 30 | run: | 31 | mkdir -p gtest_build 32 | cd gtest_build 33 | git clone https://github.com/google/googletest.git -b release-1.12.1 34 | cd googletest 35 | mkdir -p build 36 | cd build 37 | cmake .. -DBUILD_GMOCK=OFF 38 | make 39 | sudo make install 40 | 41 | - name: Create Build Environment 42 | # Some projects don't allow in-source building, so create a separate build directory 43 | # We'll use this as our working directory for all subsequent commands 44 | run: cmake -E make_directory ${{github.workspace}}/build 45 | 46 | - name: Configure CMake 47 | # Use a bash shell so we can use the same syntax for environment variable 48 | # access regardless of the host operating system 49 | working-directory: ${{github.workspace}}/build 50 | # Note the current convention is to use the -S and -B options here to specify source 51 | # and build directories, but this is only available with CMake 3.13 and higher. 52 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 53 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_FLAGS="--coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" 54 | 55 | - name: Build 56 | working-directory: ${{github.workspace}}/build 57 | # Execute the build. You can specify a specific target with "--target " 58 | # run: cmake --build . --target $TARGET --config $BUILD_TYPE 59 | run: make $TARGET VERBOSE=1 60 | 61 | - name: Test 62 | working-directory: ${{github.workspace}}/build 63 | # Execute tests defined by the CMake configuration. 64 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 65 | run: ctest -C $BUILD_TYPE --verbose 66 | 67 | - name: Memory Test 68 | working-directory: ${{github.workspace}}/build 69 | run: valgrind --leak-check=full --error-exitcode=1 ctest -C $BUILD_TYPE 70 | 71 | - name: Upload Code-Coverage Data 72 | working-directory: ${{github.workspace}}/build 73 | if: success() 74 | run: bash <(curl -s https://codecov.io/bash) 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | build/ 3 | gtest_build/ 4 | /docs/ 5 | .vscode/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # helpful link: 2 | #https://stackoverflow.com/questions/27644586/how-to-set-up-travis-ci-with-multiple-languages 3 | 4 | language: cpp 5 | os: linux 6 | dist: bionic # "dist" specifies the operating system ("bionic"=ubuntu18.04) 7 | jobs: 8 | include: 9 | - compiler: gcc 10 | addons: 11 | apt: 12 | packages: 13 | - g++ 14 | - cmake 15 | - cmake-data 16 | - libgtest-dev 17 | - libeigen3-dev 18 | - lcov 19 | - valgrind 20 | env: COMPILER=g++ 21 | after_success: 22 | # Report code-coverage test results to codecov.io 23 | - bash <(curl -s https://codecov.io/bash) 24 | 25 | - compiler: clang 26 | addons: 27 | apt: 28 | packages: 29 | - cmake 30 | - cmake-data 31 | - libgtest-dev 32 | - libeigen3-dev 33 | - lcov 34 | - valgrind 35 | env: COMPILER=clang++ 36 | 37 | #install: 38 | # Optional: To test the output of your program after running, you can use 39 | # - git clone https://github.com/kward/shunit2 shunit2 40 | 41 | before_script: 42 | - mkdir -p gtest_build 43 | - pushd gtest_build 44 | - cmake -DCMAKE_CXX_COMPILER=$COMPILER /usr/src/gtest/ 45 | - make 46 | - sudo cp *.a /usr/lib 47 | - popd 48 | 49 | script: 50 | - mkdir -p build 51 | - cd build 52 | - cmake -DCMAKE_CXX_COMPILER=$COMPILER -DCMAKE_CXX_FLAGS="--coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" .. 53 | - make lambda_lanczos_test VERBOSE=1 54 | - ctest --verbose 55 | - valgrind --leak-check=full --error-exitcode=1 ctest --verbose 56 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project (lambda_lanczos) 3 | 4 | include_directories(include/lambda_lanczos) 5 | enable_testing() 6 | add_subdirectory(src) 7 | add_subdirectory(test) 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 mrcdr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![CI](https://github.com/mrcdr/lambda-lanczos/workflows/CI/badge.svg) 2 | [![codecov](https://codecov.io/gh/mrcdr/lambda-lanczos/branch/master/graph/badge.svg)](https://codecov.io/gh/mrcdr/lambda-lanczos) 3 | [![License](https://img.shields.io/badge/License-MIT-green.svg)]() 4 | [![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/mrcdr/lambda-lanczos)]() 5 | 6 | Lambda Lanczos 7 | =========== 8 | 9 | C++ adaptive and header-only Lanczos algorithm library 10 | 11 | ## Overview 12 | 13 | **Lambda Lanczos** calculates the smallest or largest eigenvalue and 14 | the corresponding eigenvector of a symmetric (Hermitian) matrix. 15 | 16 | The characteristic feature is the matrix-vector multiplication routine used in 17 | the Lanczos algorithm is adaptable: 18 | 19 | ```c++ 20 | #include 21 | using lambda_lanczos::LambdaLanczos; 22 | /* Some include and using declarations are omitted */ 23 | 24 | void sample() { 25 | const int n = 3; 26 | double matrix[n][n] = { {2.0, 1.0, 1.0}, 27 | {1.0, 2.0, 1.0}, 28 | {1.0, 1.0, 2.0} }; 29 | // Its eigenvalues are {4, 1, 1} 30 | 31 | /* Prepare matrix-vector multiplication routine used in Lanczos algorithm */ 32 | auto mv_mul = [&](const vector& in, vector& out) { 33 | for(int i = 0;i < n;i++) { 34 | for(int j = 0;j < n;j++) { 35 | out[i] += matrix[i][j]*in[j]; 36 | } 37 | } 38 | }; 39 | 40 | 41 | /* Execute Lanczos algorithm */ 42 | LambdaLanczos engine(mv_mul, n, true, 1); // Find 1 maximum eigenvalue 43 | vector eigenvalues; 44 | vector> eigenvectors; 45 | engine.run(eigenvalues, eigenvectors); 46 | //// If C++17 is available, the following notation does the same thing: 47 | // auto [eigenvalues, eigenvectors] = engine.run() 48 | 49 | 50 | /* Print result */ 51 | cout << "Eigenvalue: " << setprecision(16) << eigenvalues[0] << endl; 52 | cout << "Eigenvector:"; 53 | for(int i = 0; i < n; i++) { 54 | cout << eigenvectors[0][i] << " "; 55 | } 56 | cout << endl; 57 | } 58 | ``` 59 | 60 | This feature allows you to 61 | - easily combine **Lambda Lanczos** with existing matrix libraries 62 | (e.g. [Eigen](http://eigen.tuxfamily.org/index.php); 63 | see a [sample code](https://github.com/mrcdr/lambda-lanczos/blob/master/src/samples/sample4_use_Eigen_library.cpp)). 64 | - use a matrix whose elements are partially given, 65 | e.g. a sparse matrix whose non-zero elements are stored 66 | as a list of {row-index, column-index, value} tuples. 67 | 68 | ## Interfaces 69 | Lambda Lanczos provides the following two interfaces: 70 | ### 1. Eigenvalue problem 71 | `LambdaLanczos` class computes maximum (minimum) eigenvalues and 72 | corresponding eigenvectors. Degenerate eigenvalues are taken into account. 73 | This class aims problems that require one or a few eigenpairs 74 | (e.g. low-energy excitation of a quantum system). 75 | 76 | ### 2. Exponentiation 77 | `Exponentiator` class computes the following type of matrix-vector multiplication: 78 | 79 | $$\boldsymbol{v}'=e^{\delta A} \boldsymbol{v},$$ 80 | 81 | where $A$ is a symmetric (Hermitian) matrix and $\delta$ is a scalar parameter. 82 | This class is based on the same theory of the Lanczos algorithm (Krylov subspace method). 83 | 84 | As an application, this class may be used for 85 | time evolution of a quantum many-body system: 86 | 87 | $$ \ket{\psi(t+\Delta t)} = e^{-iH\Delta t} \ket{\psi(t)},$$ 88 | 89 | and more sophisticated algorithms, such as [TDVP](https://arxiv.org/abs/1408.5056) and other tensor networks. 90 | 91 | 92 | 93 | ## Sample programs 94 | See [here](https://github.com/mrcdr/lambda-lanczos/tree/master/src/samples). 95 | 96 | ## API reference 97 | [API reference](https://mrcdr.github.io/lib-docs/lambda-lanczos/) 98 | 99 | ## Requirement 100 | 101 | C++11 compatible environment 102 | 103 | ## Dependencies 104 | **Lambda Lanczos** itself does not depend on any libraries. 105 | 106 | In order to run tests, [Google Test](https://github.com/google/googletest) is required. 107 | 108 | ## Installation 109 | 110 | **Lambda Lanczos** is a header-only library. 111 | So the installation step is as follows: 112 | 113 | 1. Clone or download the latest version from [Github](https://github.com/mrcdr/lambda-lanczos/). 114 | 2. Place the `include/lambda_lanczos` directory anywhere your project can find. 115 | 116 | ## License 117 | 118 | [MIT](https://github.com/mrcdr/lambda-lanczos/blob/master/LICENSE) 119 | 120 | ## Author 121 | 122 | [mrcdr](https://github.com/mrcdr) 123 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: 95% 6 | threshold: 5% 7 | patch: 8 | default: 9 | target: 80% 10 | ignore: 11 | - "src" # Ignore the "src" directory. (Only measure coverage in "include") 12 | -------------------------------------------------------------------------------- /doxygen_src/mainpage.dox: -------------------------------------------------------------------------------- 1 | /** 2 | \mainpage 3 | This document contains full specs of *lambda-lanczos*. 4 | This library has two main interfaces: 5 | 6 | - Eigenvalue problem: #lambda_lanczos::LambdaLanczos 7 | - Exponentiation: #lambda_lanczos::Exponentiator 8 | 9 | [Sample codes](https://github.com/mrcdr/lambda-lanczos/tree/master/src/samples) may also be helpful. 10 | 11 | Github repository: https://github.com/mrcdr/lambda-lanczos/ 12 | */ -------------------------------------------------------------------------------- /include/lambda_lanczos/eigenpair_manager.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_EIGENPAIR_MANAGER_H_ 2 | #define LAMBDA_LANCZOS_EIGENPAIR_MANAGER_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "lambda_lanczos_util.hpp" 8 | 9 | namespace lambda_lanczos { 10 | namespace eigenpair_manager { 11 | 12 | /** 13 | * @brief Class to manage calculated eigenpairs. 14 | * 15 | * This class does: 16 | * (1) keep only N largest (smallest) eigenvalues and correspoinding eigenvectors. 17 | * (2) add new eigenpairs and examine whether some of them are kept or all of them are dropped by (1) 18 | * (which determine whether more Lanczos iterations are required). 19 | * (3) give reference to eigenvectors. 20 | */ 21 | template 22 | class EigenPairManager { 23 | private: 24 | template 25 | using real_t = util::real_t; 26 | 27 | const bool find_maximum; 28 | const size_t num_eigs; 29 | 30 | std::multimap, std::vector, std::function, real_t)>> eigenpairs; 31 | // This super long definition is required because the default comparator type is std::less. 32 | 33 | public: 34 | EigenPairManager(bool find_maximum, size_t num_eigs) : find_maximum(find_maximum), num_eigs(num_eigs) { 35 | std::function, real_t)> comp; 36 | if (find_maximum) { 37 | comp = std::greater>(); 38 | } else { 39 | comp = std::less>(); 40 | } 41 | 42 | std::vector, std::vector>> dummy; 43 | this->eigenpairs = std::multimap, std::vector, std::function, real_t)>>( 44 | dummy.begin(), dummy.end(), comp); 45 | // This super long definition is required because the default comparator type is std::less. 46 | } 47 | 48 | size_t size() const { 49 | return eigenpairs.size(); 50 | } 51 | 52 | bool insertEigenpairs(std::vector>& eigenvalues, std::vector>& eigenvectors) { 53 | assert(eigenvalues.size() == eigenvectors.size()); 54 | 55 | bool nothing_added = true; 56 | for (size_t i = 0; i < eigenvalues.size(); ++i) { 57 | auto inserted = eigenpairs.emplace(std::move(eigenvalues[i]), std::move(eigenvectors[i])); 58 | auto last = eigenpairs.end(); 59 | last--; 60 | if (eigenpairs.size() > num_eigs) { 61 | if (inserted != last) { // If the eigenpair is not inserted to the tail 62 | nothing_added = false; 63 | } 64 | eigenpairs.erase(last); 65 | } else { 66 | nothing_added = false; 67 | } 68 | } 69 | 70 | return nothing_added; 71 | } 72 | 73 | lambda_lanczos::util::MapValueIterable getEigenvectors() const { 74 | return lambda_lanczos::util::MapValueIterable(eigenpairs); 75 | } 76 | 77 | decltype(eigenpairs)& getEigenpairs() { 78 | return eigenpairs; 79 | }; 80 | }; 81 | 82 | } /* namespace eigenpair_manager */ 83 | } /* namespace lambda_lanczos */ 84 | #endif /* LAMBDA_LANCZOS_EIGENPAIR_MANAGER_H_ */ -------------------------------------------------------------------------------- /include/lambda_lanczos/exponentiator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_EXPONENTIATOR_H_ 2 | #define LAMBDA_LANCZOS_EXPONENTIATOR_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "lambda_lanczos.hpp" 17 | #include "lambda_lanczos_util.hpp" 18 | 19 | namespace lambda_lanczos { 20 | 21 | /** 22 | * @brief Calculation engine for Lanczos exponentiation 23 | */ 24 | template 25 | class Exponentiator { 26 | private: 27 | /** 28 | * @brief See #util::real_t for details. 29 | */ 30 | template 31 | using real_t = util::real_t; 32 | 33 | public: 34 | /** 35 | * @brief Matrix-vector multiplication routine. 36 | * 37 | * This must be a function to calculate `A*in` and store the result 38 | * into `out`, where `A` is the matrix to be exponentiated. 39 | * You can assume the output vector `out` has been initialized with zeros before the `mv_mul` is called. 40 | */ 41 | std::function& in, std::vector& out)> mv_mul; 42 | 43 | /** @brief Dimension of the matrix to be exponentiated. */ 44 | size_t matrix_size; 45 | /** @brief Iteration limit of Lanczos algorithm, set to `matrix_size` automatically. */ 46 | size_t max_iteration; 47 | /** @brief Convergence threshold of Lanczos iteration. 48 | * 49 | * `eps` = 1e-14 means that the eigenvalue will be calculated with 14 digits of precision. 50 | * 51 | * Default value is system-dependent. On usual 64-bit systems: 52 | * | type (including complex one) | size (system-dep.) | `eps` | 53 | * | ---------------------------------- | ------------------ | ------- | 54 | * | float | 4 bytes | 1e-6 | 55 | * | double | 8 bytes | 1e-14 | 56 | * | long double | 16 bytes | 1e-21 | 57 | */ 58 | real_t eps = std::numeric_limits>::epsilon() * 1e2; 59 | 60 | /** 61 | * @brief Flag to execute explicit Lanczos-vector orthogonalization. 62 | */ 63 | bool full_orthogonalize = false; 64 | 65 | /** @brief (Not necessary to change) 66 | * 67 | * This variable specifies the initial reserved size of Lanczos vectors. 68 | * Controlling this variable might affect reserving efficiency, 69 | * but that would be very small compared to matrix-vector-multiplication cost. 70 | */ 71 | size_t initial_vector_size = 200; 72 | 73 | /** 74 | * @brief Constructs Lanczos exponetiation engine. 75 | * 76 | * @param mv_mul Matrix-vector multiplication routine. See #mv_mul for details. 77 | * @param matrix_size The size of your matrix, i.e. if your matrix is n by n, 78 | * `matrix_size` should be n. 79 | */ 80 | Exponentiator(std::function&, std::vector&)> mv_mul, size_t matrix_size) 81 | : mv_mul(mv_mul), matrix_size(matrix_size), max_iteration(matrix_size) {} 82 | 83 | /** 84 | * @brief Apply matrix exponentiation exp(a*A) to `input` and store the result into `output`. 85 | * @return Lanczos-iteration count 86 | */ 87 | size_t run(const T& a, const std::vector& input, std::vector& output) const { 88 | assert(input.size() == this->matrix_size); 89 | 90 | std::vector> u; // Lanczos vectors 91 | std::vector> alpha; // Diagonal elements of an approximated tridiagonal matrix 92 | std::vector> beta; // Subdiagonal elements of an approximated tridiagonal matrix 93 | 94 | u.reserve(this->initial_vector_size); 95 | alpha.reserve(this->initial_vector_size); 96 | beta.reserve(this->initial_vector_size); 97 | 98 | const auto n = this->matrix_size; 99 | 100 | u.push_back(input); 101 | util::normalize(u[0]); 102 | 103 | std::vector coeff_prev; 104 | 105 | size_t itern = this->max_iteration; 106 | for (size_t k = 1; k <= this->max_iteration; ++k) { 107 | u.emplace_back(n, 0.0); 108 | this->mv_mul(u[k - 1], u[k]); 109 | 110 | alpha.push_back(std::real(util::inner_prod(u[k - 1], u[k]))); 111 | 112 | for (size_t i = 0; i < n; ++i) { 113 | if (k == 1) { 114 | u[k][i] = u[k][i] - alpha[k - 1] * u[k - 1][i]; 115 | } else { 116 | u[k][i] = u[k][i] - beta[k - 2] * u[k - 2][i] - alpha[k - 1] * u[k - 1][i]; 117 | } 118 | } 119 | 120 | if (this->full_orthogonalize) { 121 | util::schmidt_orth(u[k], u.begin(), u.end() - 1); 122 | } 123 | 124 | std::vector> ev(alpha.size()); 125 | std::vector>> p(alpha.size()); 126 | lambda_lanczos::tridiagonal::tridiagonal_eigenpairs(alpha, beta, ev, p); 127 | 128 | std::vector coeff(alpha.size(), 0.0); 129 | for (size_t i = 0; i < alpha.size(); ++i) { 130 | for (size_t j = 0; j < alpha.size(); ++j) { 131 | coeff[i] += p[j][i] * std::exp(a * ev[j]) * p[j][0]; 132 | } 133 | } 134 | 135 | /*std::cout << "beta:" << std::endl; 136 | for(size_t i = 0; i < beta.size(); ++i) { 137 | std::cout << i << " : " << beta[i] << std::endl; 138 | } 139 | 140 | std::cout << "coeff:" << std::endl; 141 | for(size_t i = 0; i < coeff.size(); ++i) { 142 | std::cout << i << " : " << coeff[i] << std::endl; 143 | }*/ 144 | 145 | beta.push_back(util::norm(u[k])); 146 | 147 | T overlap = 0.0; 148 | for (size_t i = 0; i < coeff_prev.size(); ++i) { 149 | overlap += util::typed_conj(coeff_prev[i]) * coeff[i]; // Last element of coeff is not used here 150 | } 151 | 152 | coeff_prev = std::move(coeff); 153 | 154 | const real_t beta_threshold = std::numeric_limits>::epsilon(); 155 | if (std::abs(1 - std::abs(overlap)) < eps || beta.back() < beta_threshold) { 156 | itern = k; 157 | break; 158 | } 159 | 160 | util::normalize(u[k]); 161 | } 162 | 163 | output.resize(n); 164 | std::fill(output.begin(), output.end(), T()); 165 | const T norm = util::norm(input); 166 | for (size_t l = 0; l < coeff_prev.size(); ++l) { 167 | for (size_t i = 0; i < n; ++i) { 168 | output[i] += norm * coeff_prev[l] * u[l][i]; 169 | } 170 | } 171 | 172 | return itern; 173 | } 174 | 175 | size_t taylor_run(const T& a, const std::vector& input, std::vector& output) { 176 | const size_t n = this->matrix_size; 177 | assert(this->matrix_size == input.size()); 178 | 179 | if (a == T()) { // Zero check 180 | output = input; 181 | return 1; 182 | } 183 | 184 | std::vector> taylors; 185 | taylors.push_back(input); 186 | 187 | T factor = 1.0; 188 | for (size_t k = 1;; ++k) { 189 | factor *= a / (T)k; 190 | taylors.emplace_back(n, 0.0); 191 | mv_mul(taylors[k - 1], taylors[k]); 192 | 193 | if (lambda_lanczos::util::norm(taylors[k]) * std::abs(factor) < eps) { 194 | break; 195 | } 196 | } 197 | 198 | /* Sum Taylor series backward */ 199 | output.resize(n); 200 | std::fill(output.begin(), output.end(), 0.0); 201 | for (size_t k = taylors.size(); k-- > 0;) { 202 | for (size_t i = 0; i < n; ++i) { 203 | output[i] += taylors[k][i] * factor; 204 | } 205 | 206 | factor *= (T)k / a; 207 | } 208 | 209 | return taylors.size(); 210 | } 211 | }; 212 | 213 | } /* namespace lambda_lanczos */ 214 | 215 | #endif /* LAMBDA_LANCZOS_EXPONENTIATOR_H_ */ 216 | -------------------------------------------------------------------------------- /include/lambda_lanczos/lambda_lanczos.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_H_ 2 | #define LAMBDA_LANCZOS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "eigenpair_manager.hpp" 17 | #include "lambda_lanczos_tridiagonal.hpp" 18 | #include "lambda_lanczos_util.hpp" 19 | 20 | namespace lambda_lanczos { 21 | /** 22 | * @brief Computes the eigenvectors from Krylov subspace information. 23 | * 24 | * @param [in] alpha Diagonal elements of the tridiagonal matrix. 25 | * @param [in] beta Sub-diagonal elements of the tridiagonal matrix. 26 | * @param [in] u Lanczos vectors. 27 | * @param [in] find_maximum True to calculate maximum eigenvalues. False to calculate minimum eigenvalues. 28 | * @param [in] num_of_eigenvalues Number of eigenvalues to be calculated. 29 | * 30 | * @return Calculated eigenvectors. 31 | */ 32 | template 33 | inline auto compute_eigenvectors(const std::vector& alpha, 34 | const std::vector& beta, 35 | const std::vector>& u, 36 | const bool find_maximum, 37 | const size_t num_of_eigenvalues) -> std::vector> { 38 | const auto m = alpha.size(); 39 | const auto n = u[0].size(); 40 | 41 | std::vector tridiagonal_eigenvalues; 42 | std::vector> tridiagonal_eigenvectors; 43 | 44 | tridiagonal::tridiagonal_eigenpairs(alpha, beta, tridiagonal_eigenvalues, tridiagonal_eigenvectors); 45 | 46 | std::vector> eigenvectors; 47 | for (size_t i = 0; i < num_of_eigenvalues; ++i) { 48 | eigenvectors.emplace_back(n); 49 | } 50 | 51 | for (size_t index = 0; index < num_of_eigenvalues; index++) { 52 | size_t index_tri = find_maximum ? m - index - 1 : index; 53 | for (size_t k = m; k-- > 0;) { 54 | for (size_t i = 0; i < n; ++i) { 55 | eigenvectors[index][i] += tridiagonal_eigenvectors[index_tri][k] * u[k][i]; 56 | } 57 | } 58 | util::normalize(eigenvectors[index]); 59 | } 60 | 61 | return eigenvectors; 62 | } 63 | 64 | /** 65 | * @brief Template class to implement random vector initializer. 66 | * 67 | * "Partially specialization of function" is not allowed, 68 | * so here it is mimicked by wrapping the "init" function with a class template. 69 | */ 70 | template 71 | struct VectorRandomInitializer { 72 | public: 73 | /** 74 | * @brief Initialize given vector randomly in the range of [-1, 1]. 75 | * 76 | * For complex type, the real and imaginary part of each element will be initialized in 77 | * the range of [-1, 1]. 78 | */ 79 | static void init(std::vector& v) { 80 | std::random_device dev; 81 | std::mt19937 mt(dev()); 82 | std::uniform_real_distribution rand((T)(-1.0), (T)(1.0)); 83 | 84 | size_t n = v.size(); 85 | for (size_t i = 0; i < n; ++i) { 86 | v[i] = rand(mt); 87 | } 88 | } 89 | }; 90 | 91 | template 92 | struct VectorRandomInitializer> { 93 | public: 94 | static void init(std::vector>& v) { 95 | std::random_device dev; 96 | std::mt19937 mt(dev()); 97 | std::uniform_real_distribution rand((T)(-1.0), (T)(1.0)); 98 | 99 | size_t n = v.size(); 100 | for (size_t i = 0; i < n; ++i) { 101 | v[i] = std::complex(rand(mt), rand(mt)); 102 | } 103 | } 104 | }; 105 | 106 | /** 107 | * @brief Calculation engine for Lanczos algorithm 108 | */ 109 | template 110 | class LambdaLanczos { 111 | private: 112 | /** 113 | * @brief See #util::real_t for details. 114 | */ 115 | template 116 | using real_t = util::real_t; 117 | 118 | public: 119 | /** 120 | * @brief Matrix-vector multiplication routine. 121 | * 122 | * This must be a function to calculate `A*in` and store the result 123 | * into `out`, where `A` is the matrix to be diagonalized. 124 | * You can assume the output vector `out` has been initialized with zeros before the `mv_mul` is called. 125 | */ 126 | std::function& in, std::vector& out)> mv_mul; 127 | 128 | /** @brief Function to initialize the initial Lanczos vector. 129 | * 130 | * After this function called, the output vector will be normalized automatically. 131 | * Default value is #lambda_lanczos::VectorRandomInitializer::init. 132 | */ 133 | std::function& vec)> init_vector = VectorRandomInitializer::init; 134 | 135 | /** @brief Dimension of the matrix to be diagonalized. */ 136 | size_t matrix_size; 137 | /** @brief Iteration limit of Lanczos algorithm, set to `matrix_size` automatically. */ 138 | size_t max_iteration; 139 | /** @brief Convergence threshold of Lanczos iteration. 140 | * 141 | * `eps` = 1e-12 means that the eigenvalue will be calculated with 12 digits of precision. 142 | * 143 | * Default value is system-dependent. On usual 64-bit systems: 144 | * | type (including complex one) | size (system-dep.) | `eps` | 145 | * | ---------------------------------- | ------------------ | ------- | 146 | * | float | 4 bytes | 1e-4 | 147 | * | double | 8 bytes | 1e-12 | 148 | * | long double | 16 bytes | 1e-19 | 149 | */ 150 | real_t eps = std::numeric_limits>::epsilon() * 1e3; 151 | 152 | /** @brief true to calculate maximum eigenvalue, false to calculate minimum one.*/ 153 | bool find_maximum; 154 | 155 | /** @brief Number of eigenpairs to be calculated. */ 156 | size_t num_eigs = 1; 157 | 158 | /** 159 | * @brief Shifts the eigenvalues of the given matrix A. 160 | * 161 | * The algorithm will calculate the eigenvalue of matrix (A+`eigenvalue_offset`*E), 162 | * here E is the identity matrix. The result eigenvalue from `run()` will take this shifting into account, 163 | * so you don't have to "reshift" the result with `eigenvalue_offset`. 164 | **/ 165 | real_t eigenvalue_offset = 0.0; 166 | 167 | /** @brief (Not necessary to change) 168 | * 169 | * This variable specifies the number of eigenpairs to be calculated per Lanczos iteration. 170 | * For example, when `num_eigs == 20` and `num_eigs_per_iteration == 5`, 171 | * `run()` will executes 4 Lanczos iterations. 172 | */ 173 | size_t num_eigs_per_iteration = 5; 174 | 175 | /** @brief (Not necessary to change) 176 | * 177 | * This variable specifies the initial reserved size of Lanczos vectors. 178 | * Controlling this variable might affect reserving efficiency, 179 | * but that would be very small compared to matrix-vector-multiplication cost. 180 | */ 181 | size_t initial_vector_size = 200; 182 | 183 | private: 184 | /** 185 | * @brief Iteration counts of the latest run. 186 | */ 187 | std::vector iter_counts; 188 | 189 | public: 190 | /** 191 | * @brief Constructs Lanczos calculation engine. 192 | * 193 | * @param mv_mul Matrix-vector multiplication routine. See #mv_mul for details. 194 | * @param matrix_size The size of your matrix, i.e. if your matrix is n by n, 195 | * `matrix_size` should be n. 196 | * @param find_maximum specifies which of the minimum or maximum eigenvalue to be calculated. 197 | * @param num_eigs specifies how many eigenpairs to be calculate, e.g., 198 | * if `find_maximum = true` and `num_eig = 3`, LambdaLanczos calculates 3 maximum eigenpairs. 199 | */ 200 | LambdaLanczos(std::function&, std::vector&)> mv_mul, 201 | size_t matrix_size, 202 | bool find_maximum, 203 | size_t num_eigs) 204 | : mv_mul(mv_mul), 205 | matrix_size(matrix_size), 206 | max_iteration(matrix_size), 207 | find_maximum(find_maximum), 208 | num_eigs(num_eigs) {} 209 | 210 | /** 211 | * @brief Not documented (In most cases, `run()` is preferred). 212 | * 213 | * @details Lanczos algorithm and stores the result into reference variables passed as arguments. 214 | * @return Lanczos-iteration count 215 | */ 216 | template 217 | size_t run_iteration(std::vector>& eigvalues, 218 | std::vector>& eigvecs, 219 | size_t nroot, 220 | Iterable orthogonalizeTo) const { 221 | std::vector> u; // Lanczos vectors 222 | std::vector> alpha; // Diagonal elements of an approximated tridiagonal matrix 223 | std::vector> beta; // Subdiagonal elements of an approximated tridiagonal matrix 224 | 225 | u.reserve(this->initial_vector_size); 226 | alpha.reserve(this->initial_vector_size); 227 | beta.reserve(this->initial_vector_size); 228 | 229 | const auto n = this->matrix_size; 230 | 231 | u.emplace_back(n); 232 | this->init_vector(u[0]); 233 | util::schmidt_orth(u[0], orthogonalizeTo.cbegin(), orthogonalizeTo.cend()); 234 | util::normalize(u[0]); 235 | 236 | std::vector> evs; // Calculated eigenvalue 237 | std::vector> pevs; // Previous eigenvalue 238 | 239 | size_t itern = this->max_iteration; 240 | for (size_t k = 1; k <= this->max_iteration; ++k) { 241 | /* au = (A + offset*E)uk, here E is the identity matrix */ 242 | std::vector au(n, 0.0); // Temporal storage to store matrix-vector multiplication result 243 | this->mv_mul(u[k - 1], au); 244 | for (size_t i = 0; i < n; ++i) { 245 | au[i] += u[k - 1][i] * this->eigenvalue_offset; 246 | } 247 | 248 | alpha.push_back(std::real(util::inner_prod(u[k - 1], au))); 249 | 250 | u.push_back(std::move(au)); 251 | for (size_t i = 0; i < n; ++i) { 252 | if (k == 1) { 253 | u[k][i] = u[k][i] - alpha[k - 1] * u[k - 1][i]; 254 | } else { 255 | u[k][i] = u[k][i] - beta[k - 2] * u[k - 2][i] - alpha[k - 1] * u[k - 1][i]; 256 | } 257 | } 258 | 259 | util::schmidt_orth(u[k], orthogonalizeTo.cbegin(), orthogonalizeTo.cend()); 260 | util::schmidt_orth(u[k], u.begin(), u.end() - 1); 261 | 262 | beta.push_back(util::norm(u[k])); 263 | 264 | size_t num_eigs_to_calculate = std::min(nroot, alpha.size()); 265 | evs = std::vector>(); 266 | 267 | std::vector> eigvals_all(alpha.size()); 268 | tridiagonal::tridiagonal_eigenvalues(alpha, beta, eigvals_all); 269 | if (this->find_maximum) { 270 | for (size_t i = 0; i < num_eigs_to_calculate; ++i) { 271 | evs.push_back(eigvals_all[eigvals_all.size() - i - 1]); 272 | } 273 | } else { 274 | for (size_t i = 0; i < num_eigs_to_calculate; ++i) { 275 | evs.push_back(eigvals_all[i]); 276 | } 277 | } 278 | 279 | const real_t zero_threshold = std::numeric_limits>::epsilon() * 1e1; 280 | if (beta.back() < zero_threshold) { 281 | itern = k; 282 | break; 283 | } 284 | 285 | util::normalize(u[k]); 286 | 287 | /* 288 | * Only break loop if convergence condition is met for all requied roots 289 | */ 290 | bool break_cond = true; 291 | if (pevs.size() != evs.size()) { 292 | break_cond = false; 293 | } else { 294 | for (size_t iroot = 0; iroot < nroot; ++iroot) { 295 | const auto& ev = evs[iroot]; 296 | const auto& pev = pevs[iroot]; 297 | if (std::abs(ev - pev) >= std::min(std::abs(ev), std::abs(pev)) * this->eps) { 298 | break_cond = false; 299 | break; 300 | } 301 | } 302 | } 303 | 304 | if (break_cond) { 305 | itern = k; 306 | break; 307 | } else { 308 | pevs = evs; 309 | } 310 | } 311 | 312 | eigvalues = evs; 313 | eigvecs.resize(eigvalues.size()); 314 | beta.back() = 0.0; 315 | 316 | eigvecs = compute_eigenvectors(alpha, beta, u, find_maximum, eigvalues.size()); 317 | for (size_t i = 0; i < eigvalues.size(); ++i) { 318 | eigvalues[i] -= this->eigenvalue_offset; 319 | } 320 | 321 | return itern; 322 | } 323 | 324 | /** 325 | * @brief Executes Lanczos algorithm and stores the result into reference variables passed as arguments. 326 | * 327 | * @param [out] eigenvalues Eigenvalues. `eigenvalues[k]` stores the k-th eigenvalue. 328 | * @param [out] eigenvectors Eigenvectors. `eigenvectors[k][:]` stores the k-th eigenvector. 329 | */ 330 | void run(std::vector>& eigenvalues, std::vector>& eigenvectors) { 331 | this->iter_counts = std::vector(); 332 | eigenpair_manager::EigenPairManager ep_manager(find_maximum, num_eigs); 333 | 334 | while (true) { 335 | std::vector> eigenvalues_current; 336 | std::vector> eigenvectors_current; 337 | 338 | size_t nroot = std::min(num_eigs_per_iteration, this->matrix_size - ep_manager.size()); 339 | 340 | size_t iter_count = 341 | this->run_iteration(eigenvalues_current, eigenvectors_current, nroot, ep_manager.getEigenvectors()); 342 | this->iter_counts.push_back(iter_count); 343 | 344 | bool nothing_added = ep_manager.insertEigenpairs(eigenvalues_current, eigenvectors_current); 345 | 346 | if (nothing_added) { 347 | break; 348 | } 349 | } 350 | 351 | const auto& eigenpairs = ep_manager.getEigenpairs(); 352 | eigenvalues = std::vector>(); 353 | eigenvalues.reserve(eigenpairs.size()); 354 | eigenvectors = std::vector>(); 355 | eigenvectors.reserve(eigenvectors.size()); 356 | 357 | for (auto& eigenpair : eigenpairs) { 358 | eigenvalues.push_back(std::move(eigenpair.first)); 359 | eigenvectors.push_back(std::move(eigenpair.second)); 360 | } 361 | } 362 | 363 | /** 364 | * @brief Executes Lanczos algorithm and return result as a tuple. 365 | * 366 | * This function provides C++17 multiple-value-return interface. 367 | * 368 | * @return Eigenvalues. `eigenvalues[k]` stores the k-th eigenvalue. 369 | * @return Eigenvectors. `eigenvectors[k][:]` stores the k-th eigenvector. 370 | */ 371 | std::tuple>, std::vector>> run() { 372 | std::vector> eigenvalues; 373 | std::vector> eigenvectors; 374 | for (size_t i = 0; i < this->num_eigs; ++i) { 375 | eigenvectors.emplace_back(this->matrix_size); 376 | } 377 | 378 | this->run(eigenvalues, eigenvectors); 379 | 380 | return {eigenvalues, eigenvectors}; 381 | } 382 | 383 | /** 384 | * @brief Executes Lanczos algorithm that calculate one eigenpair regardless of `num_eigs`. 385 | * 386 | * @param [out] eigenvalue Eigenvalue. 387 | * @param [out] eigenvector Eigenvector. 388 | */ 389 | void run(real_t& eigenvalue, std::vector& eigenvector) { 390 | const size_t num_eigs_tmp = this->num_eigs; 391 | this->num_eigs = 1; 392 | 393 | std::vector> eigenvalues(1); 394 | std::vector> eigenvectors(1); 395 | 396 | this->run(eigenvalues, eigenvectors); 397 | 398 | this->num_eigs = num_eigs_tmp; 399 | 400 | eigenvalue = eigenvalues[0]; 401 | eigenvector = std::move(eigenvectors[0]); 402 | } 403 | 404 | /** 405 | * @brief Returns the latest iteration counts. 406 | */ 407 | const std::vector& getIterationCounts() const { 408 | return iter_counts; 409 | } 410 | }; 411 | 412 | } /* namespace lambda_lanczos */ 413 | 414 | #endif /* LAMBDA_LANCZOS_H_ */ 415 | -------------------------------------------------------------------------------- /include/lambda_lanczos/lambda_lanczos_tridiagonal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_TRIDIAGONAL_H_ 2 | #define LAMBDA_LANCZOS_TRIDIAGONAL_H_ 3 | 4 | #if defined(LAMBDA_LANCZOS_USE_LAPACK) || defined(LAMBDA_LANCZOS_USE_MKL) 5 | #include "lambda_lanczos_tridiagonal_lapack.hpp" 6 | namespace lambda_lanczos { 7 | namespace tridiagonal { 8 | using namespace lambda_lanczos::tridiagonal_lapack; 9 | } /* namespace tridiagonal */ 10 | } /* namespace lambda_lanczos */ 11 | 12 | #else 13 | #include "lambda_lanczos_tridiagonal_impl.hpp" 14 | namespace lambda_lanczos { 15 | namespace tridiagonal { 16 | using namespace lambda_lanczos::tridiagonal_impl; 17 | } /* namespace tridiagonal */ 18 | } /* namespace lambda_lanczos */ 19 | 20 | #endif 21 | 22 | #endif /* LAMBDA_LANCZOS_TRIDIAGONAL_H_ */ 23 | -------------------------------------------------------------------------------- /include/lambda_lanczos/lambda_lanczos_tridiagonal_impl.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_TRIDIAGONAL_IMPL_H_ 2 | #define LAMBDA_LANCZOS_TRIDIAGONAL_IMPL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "lambda_lanczos_util.hpp" 12 | 13 | namespace lambda_lanczos { 14 | namespace tridiagonal_impl { 15 | /** 16 | * @brief Finds the number of eigenvalues of given tridiagonal matrix smaller than `c`. 17 | * 18 | * Algorithm from 19 | * Peter Arbenz et al. / "High Performance Algorithms for Structured Matrix Problems" / 20 | * Nova Science Publishers, Inc. 21 | */ 22 | template 23 | inline size_t num_of_eigs_smaller_than(T c, const std::vector& alpha, const std::vector& beta) { 24 | T q_i = alpha[0] - c; 25 | size_t count = 0; 26 | size_t m = alpha.size(); 27 | 28 | if (q_i < 0) { 29 | ++count; 30 | } 31 | 32 | for (size_t i = 1; i < m; ++i) { 33 | q_i = alpha[i] - c - beta[i - 1] * beta[i - 1] / q_i; 34 | if (q_i < 0) { 35 | ++count; 36 | } 37 | if (q_i == 0) { 38 | q_i = std::numeric_limits::epsilon(); 39 | } 40 | } 41 | 42 | return count; 43 | } 44 | 45 | /** 46 | * @brief Computes the upper bound of the absolute value of eigenvalues by Gerschgorin theorem. 47 | * 48 | * This routine gives a rough upper bound, 49 | * but it is sufficient because the bisection routine using 50 | * the upper bound converges exponentially. 51 | */ 52 | template 53 | inline T tridiagonal_eigen_limit(const std::vector& alpha, const std::vector& beta) { 54 | T r = util::m_norm(alpha); 55 | r += 2 * util::m_norm(beta); 56 | 57 | return r; 58 | } 59 | 60 | /** 61 | * @brief Finds the `m`th smaller eigenvalue of given tridiagonal matrix. 62 | */ 63 | template 64 | inline T find_mth_eigenvalue(const std::vector& alpha, const std::vector& beta, const size_t m) { 65 | T mid; 66 | T pmid = std::numeric_limits::max(); 67 | T r = tridiagonal_eigen_limit(alpha, beta); 68 | T lower = -r; 69 | T upper = r; 70 | 71 | while (upper - lower > std::min(std::abs(lower), std::abs(upper)) * std::numeric_limits::epsilon()) { 72 | mid = (lower + upper) * T(0.5); 73 | 74 | if (num_of_eigs_smaller_than(mid, alpha, beta) >= m + 1) { 75 | upper = mid; 76 | } else { 77 | lower = mid; 78 | } 79 | 80 | if (mid == pmid) { 81 | /* This avoids an infinite loop due to zero matrix */ 82 | break; 83 | } 84 | pmid = mid; 85 | } 86 | 87 | return lower; // The "lower" almost equals the "upper" here. 88 | } 89 | 90 | /** 91 | * @brief Computes an eigenvector corresponding to given eigenvalue for given tri-diagonal matrix. 92 | */ 93 | template 94 | inline std::vector compute_tridiagonal_eigenvector_from_eigenvalue(const std::vector& alpha, 95 | const std::vector& beta, 96 | const size_t index, 97 | const T ev) { 98 | (void)index; // Unused declaration for compiler 99 | 100 | const auto m = alpha.size(); 101 | std::vector cv(m); 102 | 103 | cv[m - 1] = 1.0; 104 | 105 | if (m >= 2) { 106 | cv[m - 2] = (ev - alpha[m - 1]) * cv[m - 1] / beta[m - 2]; 107 | for (size_t k = m - 2; k-- > 0;) { 108 | cv[k] = ((ev - alpha[k + 1]) * cv[k + 1] - beta[k + 1] * cv[k + 2]) / beta[k]; 109 | } 110 | } 111 | 112 | util::normalize(cv); 113 | 114 | return cv; 115 | } 116 | 117 | /** 118 | * @brief Computes all eigenpairs (eigenvalues and eigenvectors) for given tri-diagonal matrix. 119 | */ 120 | template 121 | inline void tridiagonal_eigenpairs_bisection(const std::vector& alpha, 122 | const std::vector& beta, 123 | std::vector& eigenvalues, 124 | std::vector>& eigenvectors) { 125 | const size_t n = alpha.size(); 126 | eigenvalues.resize(n); 127 | eigenvectors.resize(n); 128 | 129 | for (size_t j = 0; j < n; ++j) { 130 | eigenvalues[j] = lambda_lanczos::tridiagonal_impl::find_mth_eigenvalue(alpha, beta, j); 131 | eigenvectors[j] = lambda_lanczos::tridiagonal_impl::compute_tridiagonal_eigenvector_from_eigenvalue( 132 | alpha, beta, j, eigenvalues[j]); 133 | } 134 | } 135 | 136 | /** 137 | * @brief Calculates the cosine and sine of Givens rotation that eliminates a specific element (see detail). 138 | * 139 | * @details 140 | * This function calculates the cosine and sine that eliminate the element b, i.e. that satisfy 141 | * @verbatim 142 | ( c s)(a) = (x) 143 | (-s c)(b) = (0), 144 | * @endverbatim 145 | * where x >= 0. 146 | * 147 | * @param [in] a Input element to remain non-zero. 148 | * @param [in] b Input element to be eliminated. 149 | * @return Pair (c, s). 150 | */ 151 | template 152 | inline std::pair calc_givens_cs(T a, T b) { 153 | if (b == 0) { 154 | return std::make_pair(1.0, 0.0); 155 | } 156 | 157 | if (a == 0) { 158 | return std::make_pair(0.0, 1.0); 159 | } 160 | 161 | T x = std::sqrt(a * a + b * b); 162 | T c = a / x; 163 | T s = b / x; 164 | 165 | return std::make_pair(c, s); 166 | } 167 | 168 | /** 169 | * @brief Performs an implicit shift QR step A = Z^T A Z on given sub tridiagonal matrix A. 170 | * @param [in, out] alpha Diagonal elements of the full tridiagonal matrix. 171 | * @param [in, out] beta Subdiagonal elements of the full tridiagonal matrix. 172 | * @param [in, out] q A "matrix" Q that will be overwritten as Q = QZ if `rotate_matrix` is true. See note for details. 173 | * @param offset The first index of the submatrix. 174 | * @param nsub The size of the submatrix. 175 | * @param rotate_matrix True to apply Given's rotations to the matrix `q`. If false, the matrix `q` won't be accessed. 176 | * 177 | * @note A series of the QR steps produces an eigenvector "matrix" q 178 | * that stores the k-th eigenvector as `q[k][:]`. 179 | * This definition differs from a usual mathematical sense (i.e., Q_{:,k} specifies the k-th eigenvector). 180 | */ 181 | template 182 | inline void isqr_step(std::vector& alpha, 183 | std::vector& beta, 184 | std::vector>& q, 185 | size_t offset, 186 | size_t nsub, 187 | bool rotate_matrix) { 188 | using lambda_lanczos::util::sgn; 189 | using std::pow; 190 | using std::sqrt; 191 | 192 | if (nsub == 1) { 193 | return; 194 | } 195 | 196 | T d = (alpha[offset + nsub - 2] - alpha[offset + nsub - 1]) / (2 * beta[offset + nsub - 2]); 197 | T mu = alpha[offset + nsub - 1] - beta[offset + nsub - 2] / (d + sgn(d) * sqrt(d * d + T(1))); 198 | T x = alpha[offset + 0] - mu; 199 | 200 | T s = 1.0; 201 | T c = 1.0; 202 | T p = 0.0; 203 | 204 | for (size_t k = 0; k < nsub - 1; ++k) { 205 | T z = s * beta[offset + k]; 206 | T beta_prev = c * beta[offset + k]; 207 | 208 | auto cs = calc_givens_cs(x, z); 209 | c = std::get<0>(cs); 210 | s = std::get<1>(cs); 211 | 212 | if (k > 0) { 213 | beta[offset + k - 1] = sqrt(x * x + z * z); 214 | } 215 | T u = ((alpha[offset + k + 1] - alpha[offset + k] + p) * s + T(2) * c * beta_prev); 216 | alpha[offset + k] = alpha[offset + k] - p + s * u; 217 | p = s * u; 218 | x = c * u - beta_prev; 219 | 220 | // Keep in mind that q[k][j] is the jth element of the kth eigenvector. 221 | // This means an eigenvector is stored as a ROW of the matrix q 222 | // in the sense of mathematical notation. 223 | if (rotate_matrix) { 224 | for (size_t j = 0; j < alpha.size(); ++j) { 225 | auto v0 = q[offset + k][j]; 226 | auto v1 = q[offset + k + 1][j]; 227 | 228 | q[offset + k][j] = c * v0 + s * v1; 229 | q[offset + k + 1][j] = -s * v0 + c * v1; 230 | } 231 | } 232 | } 233 | 234 | alpha[offset + nsub - 1] = alpha[offset + nsub - 1] - p; 235 | beta[offset + nsub - 2] = x; 236 | } 237 | 238 | /** 239 | * @brief Find sub-tridiagonal matrix that remains non-diagonal. 240 | * 241 | * @details 242 | * This function does the following two things: 243 | * 1. Overwrite small elements with zero, 244 | * 2. Find subspace that remains non-diagonal. 245 | * The dimension of the resulting sub-matrix is submatrix_last - submatrix_first + 1. 246 | * 247 | * @param [in] alpha Diagonal elements of the full tridiagonal matrix. 248 | * @param [in,out] beta Sub-diagonal elements of the full tridiagonal matrix. 249 | * @param [out] submatrix_first Index to point the first element of the sub-tridiagonal matrix. 250 | * @param [in,out] submatrix_last Index to point the last element of the sub-tridiagonal matrix. 251 | */ 252 | template 253 | inline void find_subspace(const std::vector& alpha, 254 | std::vector& beta, 255 | size_t& submatrix_first, 256 | size_t& submatrix_last) { 257 | const T eps = std::numeric_limits::epsilon() * 0.5; 258 | const T safe_min = std::numeric_limits::min(); 259 | const size_t n = alpha.size(); 260 | 261 | /* Overwrite small elements with zero */ 262 | for (size_t i = 0; i < n - 1; ++i) { 263 | if (std::abs(beta[i]) < std::sqrt(std::abs(alpha[i]) * std::abs(alpha[i + 1])) * eps + safe_min) { 264 | beta[i] = 0; 265 | } 266 | } 267 | 268 | /* Find subspace */ 269 | while (submatrix_last > 0 && beta[submatrix_last - 1] == 0) { 270 | submatrix_last--; 271 | } 272 | submatrix_first = submatrix_last; 273 | while (submatrix_first > 0 && beta[submatrix_first - 1] != 0) { 274 | submatrix_first--; 275 | } 276 | } 277 | 278 | /** 279 | * @brief Computes all eigenpairs (eigenvalues and eigenvectors) for given tri-diagonal matrix 280 | * using the Implicitly Shifted QR algorithm. 281 | * 282 | * @param [in] alpha Diagonal elements of the full tridiagonal matrix. 283 | * @param [in] beta Sub-diagonal elements of the full tridiagonal matrix. 284 | * @param [out] eigenvalues Eigenvalues. 285 | * @param [out] eigenvectors Eigenvectors. The k-th eigenvector will be stored in `eigenvectors[k]`. 286 | * @param [in] compute_eigenvector True to calculate eigenvectors. If false, `eigenvectors` won't be accessed. 287 | * 288 | * @return Count of forced breaks due to unconvergence. 289 | */ 290 | template 291 | inline size_t tridiagonal_eigenpairs(const std::vector& alpha, 292 | const std::vector& beta, 293 | std::vector& eigenvalues, 294 | std::vector>& eigenvectors, 295 | bool compute_eigenvector = true) { 296 | const size_t n = alpha.size(); 297 | 298 | auto alpha_work = alpha; 299 | auto beta_work = beta; 300 | 301 | /* Prepare an identity matrix to be transformed into an eigenvector matrix */ 302 | if (compute_eigenvector) { 303 | lambda_lanczos::util::initAsIdentity(eigenvectors, n); 304 | } 305 | 306 | size_t unconverged_count = 0; 307 | size_t submatrix_last_prev = n - 1; 308 | 309 | size_t loop_count = 1; 310 | while (true) { 311 | size_t submatrix_last = submatrix_last_prev; 312 | size_t submatrix_first; 313 | find_subspace(alpha_work, beta_work, submatrix_first, submatrix_last); 314 | 315 | const size_t nsub = submatrix_last - submatrix_first + 1; 316 | const size_t max_loop_count = nsub * 50; 317 | 318 | if (submatrix_last > 0) { 319 | isqr_step(alpha_work, beta_work, eigenvectors, submatrix_first, nsub, compute_eigenvector); 320 | } else { 321 | break; 322 | } 323 | 324 | if (submatrix_last == submatrix_last_prev) { 325 | if (loop_count > max_loop_count) { 326 | submatrix_last_prev = submatrix_first; 327 | unconverged_count++; 328 | loop_count = 1; 329 | } else { 330 | loop_count++; 331 | } 332 | } else { 333 | loop_count = 1; 334 | submatrix_last_prev = submatrix_last; 335 | } 336 | } 337 | 338 | eigenvalues = alpha_work; 339 | 340 | lambda_lanczos::util::sort_eigenpairs(eigenvalues, eigenvectors, compute_eigenvector); 341 | 342 | return unconverged_count; 343 | } 344 | 345 | /** 346 | * @brief Computes all eigenvalues for given tri-diagonal matrix 347 | * using the Implicitly Shifted QR algorithm. 348 | * 349 | * @param [in] alpha Diagonal elements of the full tridiagonal matrix. 350 | * @param [in] beta Sub-diagonal elements of the full tridiagonal matrix. 351 | * @param [out] eigenvalues Eigenvalues. 352 | * 353 | * @return Count of forced breaks due to unconvergence. 354 | */ 355 | template 356 | inline size_t tridiagonal_eigenvalues(const std::vector& alpha, 357 | const std::vector& beta, 358 | std::vector& eigenvalues) { 359 | std::vector> dummy_eigenvectors; 360 | return tridiagonal_eigenpairs(alpha, beta, eigenvalues, dummy_eigenvectors, false); 361 | } 362 | 363 | } /* namespace tridiagonal_impl */ 364 | } /* namespace lambda_lanczos */ 365 | 366 | #endif /* LAMBDA_LANCZOS_TRIDIAGONAL_IMPL_H_ */ 367 | -------------------------------------------------------------------------------- /include/lambda_lanczos/lambda_lanczos_tridiagonal_lapack.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_TRIDIAGONAL_LAPACK_H_ 2 | #define LAMBDA_LANCZOS_TRIDIAGONAL_LAPACK_H_ 3 | 4 | /* 5 | * This file is used just for debug and benchmark. 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #if defined(LAMBDA_LANCZOS_USE_LAPACK) 12 | #include 13 | #elif defined(LAMBDA_LANCZOS_USE_MKL) 14 | #include 15 | #endif 16 | 17 | #include "lambda_lanczos_util.hpp" 18 | 19 | namespace lambda_lanczos { 20 | namespace tridiagonal_lapack { 21 | 22 | inline lapack_int stev(int matrix_layout, char jobz, lapack_int n, float* d, float* e, float* z, lapack_int ldz) { 23 | return LAPACKE_sstev(matrix_layout, jobz, n, d, e, z, ldz); 24 | } 25 | 26 | inline lapack_int stev(int matrix_layout, char jobz, lapack_int n, double* d, double* e, double* z, lapack_int ldz) { 27 | return LAPACKE_dstev(matrix_layout, jobz, n, d, e, z, ldz); 28 | } 29 | 30 | /** 31 | * @brief Finds the `m`th smaller eigenvalue of given tridiagonal matrix. 32 | */ 33 | template 34 | inline T find_mth_eigenvalue(const std::vector& alpha, const std::vector& beta, const size_t index) { 35 | const size_t n = alpha.size(); 36 | auto a = alpha; // copy required because the contents will be destroyed 37 | auto b = beta; // copy required because the contents will be destroyed 38 | auto z = std::vector(1); 39 | 40 | stev(LAPACK_COL_MAJOR, 'N', n, a.data(), b.data(), z.data(), 1); 41 | 42 | return a[index]; 43 | } 44 | 45 | /** 46 | * @brief Computes all eigenpairs (eigenvalues and eigenvectors) for given tri-diagonal matrix. 47 | */ 48 | template 49 | inline void tridiagonal_eigenpairs(const std::vector& alpha, 50 | const std::vector& beta, 51 | std::vector& eigenvalues, 52 | std::vector>& eigenvectors, 53 | bool compute_eigenvector = true) { 54 | const size_t n = alpha.size(); 55 | auto a = alpha; 56 | auto b = beta; 57 | auto z = std::vector(n * n); 58 | char jobz = compute_eigenvector ? 'V' : 'N'; 59 | 60 | stev(LAPACK_COL_MAJOR, jobz, n, a.data(), b.data(), z.data(), n); 61 | 62 | eigenvalues = std::move(a); 63 | eigenvectors.resize(n); 64 | for (size_t k = 0; k < n; ++k) { // k-th eigenvector 65 | eigenvectors[k] = std::vector(n); 66 | for (size_t i = 0; i < n; ++i) { 67 | eigenvectors[k][i] = z[k * n + i]; 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * @brief Computes all eigenvalues for given tri-diagonal matrix 74 | * using the Implicitly Shifted QR algorithm. 75 | * 76 | * @param [in] alpha Diagonal elements of the full tridiagonal matrix. 77 | * @param [in] beta Sub-diagonal elements of the full tridiagonal matrix. 78 | * @param [out] eigenvalues Eigenvalues. 79 | * 80 | * @return Count of forced breaks due to unconvergence. 81 | */ 82 | template 83 | inline void tridiagonal_eigenvalues(const std::vector& alpha, 84 | const std::vector& beta, 85 | std::vector& eigenvalues) { 86 | std::vector> dummy_eigenvectors; 87 | return tridiagonal_eigenpairs(alpha, beta, eigenvalues, dummy_eigenvectors, false); 88 | } 89 | 90 | } /* namespace tridiagonal_lapack */ 91 | } /* namespace lambda_lanczos */ 92 | 93 | #endif /* LAMBDA_LANCZOS_TRIDIAGONAL_LAPACK_H_ */ 94 | -------------------------------------------------------------------------------- /include/lambda_lanczos/lambda_lanczos_util.hpp: -------------------------------------------------------------------------------- 1 | #include "util/common.hpp" 2 | 3 | #if defined(LAMBDA_LANCZOS_USE_LAPACK) || defined(LAMBDA_LANCZOS_USE_MKL) 4 | #include "util/linear_algebra_lapack.hpp" 5 | #else 6 | #include "util/linear_algebra.hpp" 7 | #endif -------------------------------------------------------------------------------- /include/lambda_lanczos/macro.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_MACRO_H 2 | #define LAMBDA_LANCZOS_MACRO_H_ 3 | 4 | #if defined(LAMBDA_LANCZOS_STDPAR_SEQ) 5 | #define LAMBDA_LANCZOS_STDPAR_POLICY std::execution::seq 6 | #elif defined(LAMBDA_LANCZOS_STDPAR_PAR) 7 | #define LAMBDA_LANCZOS_STDPAR_POLICY std::execution::par 8 | #elif defined(LAMBDA_LANCZOS_STDPAR_PAR_UNSEQ) 9 | #define LAMBDA_LANCZOS_STDPAR_POLICY std::execution::par_unseq 10 | #elif defined(LAMBDA_LANCZOS_STDPAR_UNSEQ) 11 | #define LAMBDA_LANCZOS_STDPAR_POLICY std::execution::unseq 12 | #endif 13 | 14 | #endif /* LAMBDA_LANCZOS_MACRO_H_ */ -------------------------------------------------------------------------------- /include/lambda_lanczos/util/common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_UTIL_COMMON_H_ 2 | #define LAMBDA_LANCZOS_UTIL_COMMON_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace lambda_lanczos { 15 | namespace util { 16 | 17 | /** 18 | * @brief Iterator for a container of tuples to iterate over the I-th tuple elements. 19 | */ 20 | template 21 | class TupleViewIterator { 22 | private: 23 | typename container_type::const_iterator iter; 24 | 25 | public: 26 | TupleViewIterator(const typename container_type::const_iterator& iter) : iter(iter) {} 27 | 28 | const typename std::tuple_element::type& operator*() const { 29 | return std::get(*iter); 30 | } 31 | 32 | bool operator==(const TupleViewIterator& obj) const { 33 | return this->iter == obj.iter; 34 | } 35 | 36 | bool operator!=(const TupleViewIterator& obj) const { 37 | return this->iter != obj.iter; 38 | } 39 | 40 | TupleViewIterator& operator++() { 41 | this->iter++; 42 | return *this; 43 | } 44 | 45 | TupleViewIterator operator++(int dummy) { 46 | auto before = *this; 47 | this->iter++; 48 | return before; 49 | } 50 | }; 51 | 52 | /** 53 | * @brief Iterator for a map to iterate over its values. 54 | */ 55 | template 56 | using MapValueIterator = TupleViewIterator<1, map_type>; 57 | 58 | template 59 | class MapValueIterable { 60 | private: 61 | const typename map_type::const_iterator itr_cbegin; 62 | const typename map_type::const_iterator itr_cend; 63 | 64 | public: 65 | MapValueIterable(const map_type& map) : itr_cbegin(map.cbegin()), itr_cend(map.cend()) {} 66 | 67 | MapValueIterator cbegin() const { 68 | return itr_cbegin; 69 | } 70 | 71 | MapValueIterator cend() const { 72 | return itr_cend; 73 | } 74 | }; 75 | 76 | /** 77 | * @brief Template class to map specific types. See #real_t for usage. 78 | * 79 | */ 80 | template 81 | struct realTypeMap { 82 | typedef T type; 83 | }; 84 | 85 | template 86 | struct realTypeMap> { 87 | typedef T type; 88 | }; 89 | 90 | /** 91 | * @brief Type mapper from `T` to real type of `T`. 92 | * 93 | * By default, `real_t` returns `T`. 94 | * However `real_t>` returns `T`. 95 | * Usage example: This function returns a real number even if `T` is complex: 96 | * @code 97 | * template 98 | * inline real_t norm(const std::vector& vec); 99 | * @endcode 100 | */ 101 | template 102 | using real_t = typename realTypeMap::type; 103 | 104 | /** 105 | * @brief Complex conjugate template. 106 | * 107 | * This structure is required because partial specialization of 108 | * function template is not allowed in C++. 109 | * 110 | * Use #typed_conj in practice. 111 | */ 112 | template 113 | struct TypedConjugate { 114 | static T invoke(const T& val) { 115 | return val; 116 | } 117 | }; 118 | 119 | template 120 | struct TypedConjugate> { 121 | static std::complex invoke(const std::complex val) { 122 | return std::conj(val); 123 | } 124 | }; 125 | 126 | /** 127 | * @brief Complex conjugate with type. 128 | * This function returns the argument itself for real type, 129 | * and returns its complex conjugate for complex type. 130 | */ 131 | template 132 | inline T typed_conj(const T& val) { 133 | return TypedConjugate::invoke(val); 134 | } 135 | 136 | /** 137 | * @brief Sorts eigenvalues and eigenvectors with respect to given predicate. 138 | * 139 | * @note This function changes the memory location of the eigenpairs. 140 | */ 141 | template 142 | inline void sort_eigenpairs(std::vector>& eigenvalues, 143 | std::vector>& eigenvectors, 144 | bool sort_eigenvector, 145 | const std::function, real_t)> predicate = std::less>()) { 146 | using ev_index_t = std::pair, size_t>; 147 | std::vector ev_index_pairs; 148 | ev_index_pairs.reserve(eigenvalues.size()); 149 | for (size_t i = 0; i < eigenvalues.size(); ++i) { 150 | ev_index_pairs.emplace_back(eigenvalues[i], i); 151 | } 152 | 153 | std::sort(ev_index_pairs.begin(), ev_index_pairs.end(), [&predicate](const ev_index_t& x, const ev_index_t& y) { 154 | return predicate(x.first, y.first); 155 | }); 156 | 157 | std::vector> eigenvalues_new; 158 | eigenvalues_new.reserve(eigenvalues.size()); 159 | for (const auto& ev_index : ev_index_pairs) { 160 | size_t k = ev_index.second; 161 | eigenvalues_new.emplace_back(eigenvalues[k]); 162 | } 163 | eigenvalues = std::move(eigenvalues_new); 164 | 165 | if (sort_eigenvector) { 166 | std::vector> eigenvectors_new; 167 | eigenvectors_new.reserve(eigenvalues.size()); 168 | for (const auto& ev_index : ev_index_pairs) { 169 | size_t k = ev_index.second; 170 | eigenvectors_new.push_back(std::move(eigenvectors[k])); 171 | } 172 | eigenvectors = std::move(eigenvectors_new); 173 | } 174 | } 175 | 176 | /** 177 | * @brief Returns the significant decimal digits of type T. 178 | * 179 | */ 180 | template 181 | inline constexpr int sig_decimal_digit() { 182 | return (int)(std::numeric_limits::digits * log10(std::numeric_limits::radix)); 183 | } 184 | 185 | template 186 | inline constexpr T minimum_effective_decimal() { 187 | return pow(10, -sig_decimal_digit()); 188 | } 189 | 190 | /** 191 | * @brief Return the sign of given value. 192 | * @details If 0 is given, this function returns +1. 193 | */ 194 | template 195 | T sgn(T val) { 196 | if (val >= 0) { 197 | return (T)1; 198 | } else { 199 | return (T)(-1); 200 | } 201 | } 202 | 203 | /** 204 | * @brief Returns string representation of given vector. 205 | */ 206 | template 207 | std::string vectorToString(const std::vector& vec, std::string delimiter = " ") { 208 | std::stringstream ss; 209 | 210 | for (const auto& elem : vec) { 211 | ss << elem << delimiter; 212 | } 213 | 214 | /* Remove the last space */ 215 | std::string result = ss.str(); 216 | if (!result.empty()) { 217 | result.pop_back(); 218 | } 219 | 220 | return result; 221 | } 222 | 223 | } /* namespace util */ 224 | } /* namespace lambda_lanczos */ 225 | 226 | #endif /* LAMBDA_LANCZOS_UTIL_COMMON_H_ */ 227 | -------------------------------------------------------------------------------- /include/lambda_lanczos/util/linear_algebra.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_UTIL_LINEAR_ALGEBRA_H_ 2 | #define LAMBDA_LANCZOS_UTIL_LINEAR_ALGEBRA_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef LAMBDA_LANCZOS_STDPAR_POLICY 14 | #include 15 | #endif 16 | 17 | #include "common.hpp" 18 | 19 | namespace lambda_lanczos { 20 | namespace util { 21 | 22 | /** 23 | * @brief Returns "mathematical" inner product of v1 and v2. 24 | * 25 | * This function is needed because 26 | * std::inner_product calculates transpose(v1)*v2 instead of dagger(v1)*v2 for complex type. 27 | * 28 | */ 29 | template 30 | inline T inner_prod(const std::vector& v1, const std::vector& v2) { 31 | assert(v1.size() == v2.size()); 32 | 33 | #ifdef LAMBDA_LANCZOS_STDPAR_POLICY 34 | return std::transform_reduce( 35 | LAMBDA_LANCZOS_STDPAR_POLICY, 36 | std::begin(v1), 37 | std::end(v1), 38 | std::begin(v2), 39 | T(), 40 | [](const T& u, const T& v) -> T { return u + v; }, 41 | [](const T& a, const T& b) -> T { return typed_conj(a) * b; }); 42 | #else 43 | return std::inner_product( 44 | std::begin(v1), 45 | std::end(v1), 46 | std::begin(v2), 47 | T(), 48 | [](const T& u, const T& v) -> T { return u + v; }, 49 | [](const T& a, const T& b) -> T { return typed_conj(a) * b; }); 50 | #endif 51 | } 52 | 53 | /** 54 | * @brief Returns Euclidean norm of given vector. 55 | */ 56 | template 57 | inline real_t norm(const std::vector& vec) { 58 | return std::sqrt(std::real(inner_prod(vec, vec))); 59 | // The norm of any complex vector is real by definition. 60 | } 61 | 62 | /** 63 | * @brief Multiplies each element of vec by a. 64 | */ 65 | template 66 | inline void scalar_mul(T1 a, std::vector& vec) { 67 | #ifdef LAMBDA_LANCZOS_STDPAR_POLICY 68 | std::for_each(LAMBDA_LANCZOS_STDPAR_POLICY, vec.begin(), vec.end(), [&a](T2& elem) { elem *= a; }); 69 | #else 70 | std::for_each(vec.begin(), vec.end(), [&a](T2& elem) { elem *= a; }); 71 | #endif 72 | } 73 | 74 | /** 75 | * @brief Normalizes given vector. 76 | */ 77 | template 78 | inline void normalize(std::vector& vec) { 79 | scalar_mul(T(1) / norm(vec), vec); 80 | } 81 | 82 | template 83 | struct ManhattanNorm { 84 | static T invoke(const std::vector& vec) { 85 | #ifdef LAMBDA_LANCZOS_STDPAR_POLICY 86 | return std::transform_reduce( 87 | LAMBDA_LANCZOS_STDPAR_POLICY, std::begin(vec), std::end(vec), T(), std::plus(), std::abs); 88 | #else 89 | return std::accumulate( 90 | std::begin(vec), std::end(vec), T(), [](const T& acc, const T& elem) -> T { return acc + std::abs(elem); }); 91 | #endif 92 | } 93 | }; 94 | 95 | template 96 | struct ManhattanNorm> { 97 | static T invoke(const std::vector>& vec) { 98 | #ifdef LAMBDA_LANCZOS_STDPAR_POLICY 99 | return std::transform_reduce( 100 | LAMBDA_LANCZOS_STDPAR_POLICY, 101 | std::begin(vec), 102 | std::end(vec), 103 | T(), 104 | std::plus(), 105 | [](const std::complex& elem) -> T { return std::abs(std::real(elem)) + std::abs(std::imag(elem)); }); 106 | #else 107 | return std::accumulate(std::begin(vec), std::end(vec), T(), [](const T& acc, const std::complex& elem) -> T { 108 | return acc + std::abs(std::real(elem)) + std::abs(std::imag(elem)); 109 | }); 110 | #endif 111 | } 112 | }; 113 | 114 | /** 115 | * @brief Returns Manhattan-like norm of given vector. 116 | * 117 | * @note For a real vector {r_i}, returned value is sum_i |r_i|, i.e. the L1 norm in mathematical definition. 118 | * For a complex vector {c_i}, returned value is sum_i |Re(c_i)| + |Im(c_i)|, 119 | * instead of sum_i sqrt(Re(c_i)^2 + Im(c_i)^2) in mathematical definition. 120 | * This definition can avoid sqrt calculations and is also implemented as _ASUM routines in BLAS. 121 | */ 122 | template 123 | inline real_t m_norm(const std::vector& vec) { 124 | return ManhattanNorm::invoke(vec); 125 | } 126 | 127 | /** 128 | * @brief Orthogonalizes vector `uorth` with respect to orthonormal vectors defined by given iterators. 129 | * 130 | * Vectors in `u` must be normalized, but uorth doesn't have to be. 131 | */ 132 | template 133 | inline void schmidt_orth(std::vector& uorth, ForwardIterator first, ForwardIterator last) { 134 | const auto n = uorth.size(); 135 | 136 | for (auto iter = first; iter != last; ++iter) { 137 | const auto& uk = *iter; 138 | T innprod = util::inner_prod(uk, uorth); 139 | 140 | for (size_t i = 0; i < n; ++i) { 141 | uorth[i] -= innprod * uk[i]; 142 | } 143 | } 144 | } 145 | 146 | /** 147 | * @brief Initializes the given matrix `a` to an n by n identity matrix. 148 | */ 149 | template 150 | void initAsIdentity(std::vector>& a, size_t n) { 151 | a.resize(n); 152 | for (size_t i = 0; i < n; ++i) { 153 | a[i].resize(n); 154 | 155 | #ifdef LAMBDA_LANCZOS_STDPAR_POLICY 156 | std::fill(LAMBDA_LANCZOS_STDPAR_POLICY, a[i].begin(), a[i].end(), T()); 157 | #else 158 | std::fill(a[i].begin(), a[i].end(), T()); 159 | #endif 160 | 161 | a[i][i] = 1.0; 162 | } 163 | } 164 | 165 | } /* namespace util */ 166 | } /* namespace lambda_lanczos */ 167 | 168 | #endif /* LAMBDA_LANCZOS_UTIL_LINEAR_ALGEBRA_H_ */ -------------------------------------------------------------------------------- /include/lambda_lanczos/util/linear_algebra_lapack.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_LANCZOS_UTIL_LINEAR_ALGEBRA_LAPACK_H_ 2 | #define LAMBDA_LANCZOS_UTIL_LINEAR_ALGEBRA_LAPACK_H_ 3 | 4 | // clang-format off 5 | #include "macro.hpp" // This processes and defines certain macros 6 | // clang-format on 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef LAMBDA_LANCZOS_STDPAR_POLICY 18 | #include 19 | #endif 20 | 21 | #if defined(LAMBDA_LANCZOS_USE_LAPACK) 22 | #include 23 | #include 24 | #elif defined(LAMBDA_LANCZOS_USE_MKL) 25 | #include 26 | #endif 27 | 28 | #include "common.hpp" 29 | 30 | namespace lambda_lanczos { 31 | namespace util { 32 | 33 | inline float inner_prod(const std::vector& v1, const std::vector& v2) { 34 | assert(v1.size() == v2.size()); 35 | 36 | lapack_int n = v1.size(); 37 | return cblas_sdot(n, v1.data(), 1, v2.data(), 1); 38 | } 39 | 40 | inline double inner_prod(const std::vector& v1, const std::vector& v2) { 41 | assert(v1.size() == v2.size()); 42 | 43 | lapack_int n = v1.size(); 44 | return cblas_ddot(n, v1.data(), 1, v2.data(), 1); 45 | } 46 | 47 | inline std::complex inner_prod(const std::vector>& v1, 48 | const std::vector>& v2) { 49 | assert(v1.size() == v2.size()); 50 | 51 | lapack_int n = v1.size(); 52 | std::complex result; 53 | cblas_cdotc_sub(n, v1.data(), 1, v2.data(), 1, &result); 54 | return result; 55 | } 56 | 57 | inline std::complex inner_prod(const std::vector>& v1, 58 | const std::vector>& v2) { 59 | assert(v1.size() == v2.size()); 60 | 61 | lapack_int n = v1.size(); 62 | std::complex result; 63 | cblas_zdotc_sub(n, v1.data(), 1, v2.data(), 1, &result); 64 | return result; 65 | } 66 | 67 | inline float norm(const std::vector& vec) { 68 | return cblas_snrm2(vec.size(), vec.data(), 1); 69 | } 70 | 71 | inline double norm(const std::vector& vec) { 72 | return cblas_dnrm2(vec.size(), vec.data(), 1); 73 | } 74 | 75 | inline float norm(const std::vector>& vec) { 76 | return cblas_scnrm2(vec.size(), vec.data(), 1); 77 | } 78 | 79 | inline double norm(const std::vector>& vec) { 80 | return cblas_dznrm2(vec.size(), vec.data(), 1); 81 | } 82 | 83 | inline void scalar_mul(float a, std::vector& vec) { 84 | cblas_sscal(vec.size(), a, vec.data(), 1); 85 | } 86 | 87 | inline void scalar_mul(double a, std::vector& vec) { 88 | cblas_dscal(vec.size(), a, vec.data(), 1); 89 | } 90 | 91 | inline void scalar_mul(std::complex a, std::vector>& vec) { 92 | cblas_cscal(vec.size(), &a, vec.data(), 1); 93 | } 94 | 95 | inline void scalar_mul(std::complex a, std::vector>& vec) { 96 | cblas_zscal(vec.size(), &a, vec.data(), 1); 97 | } 98 | 99 | /* Special routine to multiply a real scalar to a complex vector */ 100 | inline void scalar_mul(float a, std::vector>& vec) { 101 | cblas_csscal(vec.size(), a, vec.data(), 1); 102 | } 103 | 104 | /* Special routine to multiply a real scalar to a complex vector */ 105 | inline void scalar_mul(double a, std::vector>& vec) { 106 | cblas_zdscal(vec.size(), a, vec.data(), 1); 107 | } 108 | 109 | template 110 | inline void normalize(std::vector& vec) { 111 | scalar_mul(T(1) / norm(vec), vec); 112 | } 113 | 114 | inline float m_norm(const std::vector& vec) { 115 | return cblas_sasum(vec.size(), vec.data(), 1); 116 | } 117 | 118 | inline double m_norm(const std::vector& vec) { 119 | return cblas_dasum(vec.size(), vec.data(), 1); 120 | } 121 | 122 | inline float m_norm(const std::vector>& vec) { 123 | return cblas_scasum(vec.size(), vec.data(), 1); 124 | } 125 | 126 | inline double m_norm(const std::vector>& vec) { 127 | return cblas_dzasum(vec.size(), vec.data(), 1); 128 | } 129 | 130 | template 131 | inline void schmidt_orth(std::vector& uorth, ForwardIterator first, ForwardIterator last) { 132 | const auto n = uorth.size(); 133 | 134 | for (auto iter = first; iter != last; ++iter) { 135 | const auto& uk = *iter; 136 | float alpha = -util::inner_prod(uk, uorth); 137 | cblas_saxpy(n, alpha, uk.data(), 1, uorth.data(), 1); 138 | } 139 | } 140 | 141 | template 142 | inline void schmidt_orth(std::vector& uorth, ForwardIterator first, ForwardIterator last) { 143 | const auto n = uorth.size(); 144 | 145 | for (auto iter = first; iter != last; ++iter) { 146 | const auto& uk = *iter; 147 | double alpha = -util::inner_prod(uk, uorth); 148 | cblas_daxpy(n, alpha, uk.data(), 1, uorth.data(), 1); 149 | } 150 | } 151 | 152 | template 153 | inline void schmidt_orth(std::vector>& uorth, ForwardIterator first, ForwardIterator last) { 154 | const auto n = uorth.size(); 155 | 156 | for (auto iter = first; iter != last; ++iter) { 157 | const auto& uk = *iter; 158 | std::complex alpha = -util::inner_prod(uk, uorth); 159 | cblas_caxpy(n, &alpha, uk.data(), 1, uorth.data(), 1); 160 | } 161 | } 162 | 163 | template 164 | inline void schmidt_orth(std::vector>& uorth, ForwardIterator first, ForwardIterator last) { 165 | const auto n = uorth.size(); 166 | 167 | for (auto iter = first; iter != last; ++iter) { 168 | const auto& uk = *iter; 169 | std::complex alpha = -util::inner_prod(uk, uorth); 170 | cblas_zaxpy(n, &alpha, uk.data(), 1, uorth.data(), 1); 171 | } 172 | } 173 | 174 | template 175 | void initAsIdentity(std::vector>& a, size_t n) { 176 | a.resize(n); 177 | for (size_t i = 0; i < n; ++i) { 178 | a[i].resize(n); 179 | 180 | #ifdef LAMBDA_LANCZOS_STDPAR_POLICY 181 | std::fill(LAMBDA_LANCZOS_STDPAR_POLICY, a[i].begin(), a[i].end(), T()); 182 | #else 183 | std::fill(a[i].begin(), a[i].end(), T()); 184 | #endif 185 | 186 | a[i][i] = 1.0; 187 | } 188 | } 189 | 190 | } /* namespace util */ 191 | } /* namespace lambda_lanczos */ 192 | 193 | #endif /* LAMBDA_LANCZOS_UTIL_LINEAR_ALGEBRA_LAPACK_H_ */ -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(samples) 2 | -------------------------------------------------------------------------------- /src/determine_eigenvalue_offset/determine_eigenvalue_offset.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | template 5 | using real_t = lambda_lanczos::real_t; 6 | 7 | 8 | /* 9 | * Determine the upper bound of eigenvalue magnitudes for an n by n matrix. 10 | * More readable version only for type double is available below. 11 | */ 12 | template 13 | real_t determine_eigenvalue_offset(const T* const* matrix, int n) { 14 | real_t r = real_t(); // Zero value of real_t 15 | 16 | for(int i = 0;i < n;i++) { 17 | real_t sum = real_t(); // Zero value of real_t 18 | 19 | for(int j = 0;j < n;j++) { 20 | sum += std::abs(matrix[i][j]); 21 | } 22 | 23 | if(sum > r) { 24 | r = sum; 25 | } 26 | } 27 | 28 | return r; 29 | } 30 | 31 | 32 | 33 | double determine_eigenvalue_offset(double** matrix, int n) { 34 | double r = 0.0; 35 | 36 | for(int i = 0;i < n;i++) { 37 | double sum = 0.0; 38 | 39 | for(int j = 0;j < n;j++) { 40 | sum += std::abs(matrix[i][j]); 41 | } 42 | 43 | if(sum > r) { 44 | r = sum; 45 | } 46 | } 47 | 48 | return r; 49 | } 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | /* 58 | * Just for testing 59 | */ 60 | int main() { 61 | const int n = 3; 62 | double** matrix = new double*[n]; 63 | for(int i = 0;i < n; i++) { 64 | matrix[i] = new double[n]; 65 | } 66 | 67 | 68 | 69 | matrix[0][0] = -2.0; matrix[0][1] = -1.0; matrix[0][2] = -1.0; 70 | matrix[1][0] = -1.0; matrix[1][1] = -2.0; matrix[1][2] = -1.0; 71 | matrix[2][0] = -1.0; matrix[2][1] = -1.0; matrix[2][2] = -2.0; 72 | 73 | double r = determine_eigenvalue_offset(matrix, n); 74 | 75 | std::cout << "Offset should be " << r << std::endl; 76 | 77 | 78 | 79 | for(int i = 0;i < n; i++) { 80 | delete[] matrix[i]; 81 | } 82 | delete[] matrix; 83 | } 84 | -------------------------------------------------------------------------------- /src/samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Eigen3 NO_MODULE) 2 | 3 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 4 | 5 | add_executable(sample1 sample1_simple.cpp) 6 | add_executable(sample2 sample2_sparse.cpp) 7 | add_executable(sample3 sample3_dynamic.cpp) 8 | add_executable(sample4 sample4_use_Eigen_library.cpp) 9 | add_executable(sample5 sample5_multiroot.cpp) 10 | -------------------------------------------------------------------------------- /src/samples/sample1_simple.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using std::cout; 7 | using std::endl; 8 | using std::setprecision; 9 | using lambda_lanczos::LambdaLanczos; 10 | 11 | template 12 | using vector = std::vector; 13 | 14 | int main() { 15 | const int n = 3; 16 | double matrix[n][n] = { {2.0, 1.0, 1.0}, 17 | {1.0, 2.0, 1.0}, 18 | {1.0, 1.0, 2.0} }; 19 | /* Its eigenvalues are {4, 1, 1} */ 20 | 21 | // the matrix-vector multiplication routine 22 | auto mv_mul = [&](const vector& in, vector& out) { 23 | for(int i = 0; i < n; ++i) { 24 | for(int j = 0; j < n; ++j) { 25 | out[i] += matrix[i][j]*in[j]; 26 | } 27 | } 28 | }; 29 | 30 | LambdaLanczos engine(mv_mul, n, true, 1); // true means to calculate the largest eigenvalue. 31 | vector eigenvalues; 32 | vector> eigenvectors; 33 | engine.run(eigenvalues, eigenvectors); 34 | 35 | cout << "Eigenvalue: " << setprecision(16) << eigenvalues[0] << endl; 36 | cout << "Eigenvector: "; 37 | for(int i = 0; i < n; ++i) { 38 | cout << eigenvectors[0][i] << " "; 39 | } 40 | cout << endl; 41 | 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /src/samples/sample2_sparse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using std::cout; 7 | using std::endl; 8 | using std::setprecision; 9 | using lambda_lanczos::LambdaLanczos; 10 | 11 | template 12 | using vector = std::vector; 13 | 14 | class IIV { 15 | public: 16 | int r; // row index 17 | int c; // column index 18 | double value; // matrix element at (r,c) 19 | 20 | IIV(int r, int c, double value): r(r), c(c), value(value) {} 21 | }; 22 | 23 | int main() { 24 | const int n = 3; 25 | vector matrix; 26 | matrix.emplace_back(0, 1, 1.0); 27 | matrix.emplace_back(0, 2, 1.0); 28 | matrix.emplace_back(1, 0, 1.0); 29 | matrix.emplace_back(1, 2, -1.0); 30 | matrix.emplace_back(2, 0, 1.0); 31 | matrix.emplace_back(2, 1, -1.0); 32 | /* 33 | means a 3x3 matrix 34 | 35 | 0 1 1 36 | 1 0 -1 37 | 1 -1 0 . 38 | 39 | Its eigenvalues are {1, 1, -2} 40 | */ 41 | 42 | // the matrix-vector multiplication routine 43 | auto mv_mul = [&](const vector& in, vector& out) { 44 | for(int i = 0; i < matrix.size(); ++i) { 45 | out[matrix[i].r] += matrix[i].value*in[matrix[i].c]; 46 | } 47 | }; 48 | 49 | LambdaLanczos engine(mv_mul, n, false, 1); // Find 1 minimum eigenvalue 50 | vector eigenvalues; 51 | vector> eigenvectors; 52 | engine.run(eigenvalues, eigenvectors); 53 | 54 | cout << "Eigenvalue: " << setprecision(16) << eigenvalues[0] << endl; 55 | cout << "Eigenvector: "; 56 | for(int i = 0; i < n; ++i) { 57 | cout << eigenvectors[0][i] << " "; 58 | } 59 | cout << endl; 60 | 61 | return EXIT_SUCCESS; 62 | } 63 | -------------------------------------------------------------------------------- /src/samples/sample3_dynamic.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using std::cout; 7 | using std::endl; 8 | using std::setprecision; 9 | using lambda_lanczos::LambdaLanczos; 10 | 11 | template 12 | using vector = std::vector; 13 | 14 | int main() { 15 | const int n = 100; 16 | 17 | auto mv_mul = [&](const vector& in, vector& out) { 18 | for(int i = 0; i < n-1; ++i) { 19 | out[i] += -1.0*in[i+1]; 20 | out[i+1] += -1.0*in[i]; 21 | } 22 | }; 23 | /* 24 | This lambda is equivalent to applying following n by n matrix 25 | 26 | 0 -1 0 .. 0 27 | -1 0 -1 .. 0 28 | 0 -1 0 .. 0 29 | 0 .. .. 0 30 | 0 .. 0 -1 0 31 | 0 .. -1 0 -1 32 | 0 .. 0 -1 0 . 33 | 34 | Its smallest eigenvalue is -2*cos(pi/(n+1)). 35 | 36 | (For those who are familiar with quantum physics, 37 | the matrix represents a Hamiltonian of a particle 38 | bounded by infinitely high barriers.) 39 | */ 40 | 41 | LambdaLanczos engine(mv_mul, n, false, 1); // Find 1 minimum eigenvalue 42 | vector eigenvalues; 43 | vector> eigenvectors; 44 | engine.run(eigenvalues, eigenvectors); 45 | 46 | cout << "Eigenvalue: " << setprecision(16) << eigenvalues[0] << endl; 47 | cout << "Eigenvector: "; 48 | for(int i = 0; i < n; ++i) { 49 | cout << eigenvectors[0][i] << " "; 50 | } 51 | cout << endl; 52 | 53 | return EXIT_SUCCESS; 54 | } 55 | -------------------------------------------------------------------------------- /src/samples/sample4_use_Eigen_library.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using std::cout; 8 | using std::endl; 9 | using std::setprecision; 10 | using lambda_lanczos::LambdaLanczos; 11 | 12 | template 13 | using vector = std::vector; 14 | 15 | int main() { 16 | const int n = 3; 17 | Eigen::MatrixXd matrix(n, n); 18 | matrix << 19 | 2.0, 1.0, 1.0, 20 | 1.0, 2.0, 1.0, 21 | 1.0, 1.0, 2.0; 22 | /* Its eigenvalues are {4, 1, 1} */ 23 | 24 | // the matrix-vector multiplication routine 25 | auto mv_mul = [&](const vector& in, vector& out) { 26 | auto eigen_in = Eigen::Map(&in[0], in.size()); 27 | auto eigen_out = Eigen::Map(&out[0], out.size()); 28 | 29 | eigen_out = matrix * eigen_in; // Easy version 30 | // eigen_out.noalias() += matrix * eigen_in; // Efficient version 31 | }; 32 | 33 | LambdaLanczos engine(mv_mul, n, true, 1); // Find 1 maximum eigenvalue 34 | vector eigenvalues; 35 | vector> eigenvectors; 36 | engine.run(eigenvalues, eigenvectors); 37 | 38 | cout << "Eigenvalue: " << setprecision(16) << eigenvalues[0] << endl; 39 | cout << "Eigenvector: "; 40 | for(int i = 0; i < n; ++i) { 41 | cout << eigenvectors[0][i] << " "; 42 | } 43 | cout << endl; 44 | 45 | return EXIT_SUCCESS; 46 | } 47 | -------------------------------------------------------------------------------- /src/samples/sample5_multiroot.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using std::cout; 7 | using std::endl; 8 | using std::setprecision; 9 | using lambda_lanczos::LambdaLanczos; 10 | 11 | template 12 | using vector = std::vector; 13 | 14 | int main() { 15 | const int n = 8; 16 | double matrix[n][n] = { 17 | { 6, -3, -3, 0, -1, 1, -1, 1}, 18 | {-3, -4, 2, 2, -1, -5, 0, -4}, 19 | {-3, 2, 2, -3, 0, 0, -1, -1}, 20 | { 0, 2, -3, 0, -3, 3, 2, 2}, 21 | {-1, -1, 0, -3, -2, 0, -5, -4}, 22 | { 1, -5, 0, 3, 0, -4, 5, 0}, 23 | {-1, 0, -1, 2, -5, 5, -4, 4}, 24 | { 1, -4, -1, 2, -4, 0, 4, 2} 25 | }; 26 | /* Its eigenvalues are 27 | * -13.215086, -8.500332, -4.266749, -0.467272, 0.397895, 2.303837, 7.400955, 12.346751 28 | */ 29 | 30 | // the matrix-vector multiplication routine 31 | auto mv_mul = [&](const vector& in, vector& out) { 32 | for(int i = 0; i < n; ++i) { 33 | for(int j = 0; j < n; ++j) { 34 | out[i] += matrix[i][j]*in[j]; 35 | } 36 | } 37 | }; 38 | 39 | const size_t nroot = 2; 40 | 41 | LambdaLanczos engine(mv_mul, n, false, nroot); // Find 2 minimum eigenvalues 42 | 43 | vector eigenvalues; 44 | vector> eigenvectors; 45 | engine.run(eigenvalues, eigenvectors); 46 | 47 | for (size_t iroot=0; iroot < nroot; ++iroot) { 48 | cout << "Eigenvalue (root "<< iroot <<"): " << setprecision(16) << eigenvalues[iroot] << endl; 49 | cout << "Eigenvector (root "<< iroot <<"): "; 50 | for (int i = 0; i < n; ++i) { 51 | cout << eigenvectors[iroot][i] << " "; 52 | } 53 | cout << endl; 54 | } 55 | 56 | return EXIT_SUCCESS; 57 | } 58 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(GTest) 2 | include(GoogleTest) 3 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -g -O0 -Wall -Wextra -Wfloat-conversion -fsanitize=address") 4 | 5 | add_executable(lambda_lanczos_test_impl lambda_lanczos_test.cpp exponentiator_test.cpp) 6 | add_executable(lambda_lanczos_test_stdpar lambda_lanczos_test.cpp exponentiator_test.cpp) 7 | add_executable(lambda_lanczos_test_lapack lambda_lanczos_test.cpp exponentiator_test.cpp) 8 | # add_executable(lambda_lanczos_test_mkl lambda_lanczos_test.cpp exponentiator_test.cpp) 9 | 10 | target_compile_options(lambda_lanczos_test_stdpar PRIVATE "-DLAMBDA_LANCZOS_STDPAR_PAR_UNSEQ") 11 | target_compile_options(lambda_lanczos_test_lapack PRIVATE "-DLAMBDA_LANCZOS_USE_LAPACK") 12 | # target_compile_options(lambda_lanczos_test_mkl PRIVATE "-DLAMBDA_LANCZOS_USE_MKL") 13 | 14 | # target_include_directories(lambda_lanczos_test_mkl PRIVATE ${MKLROOT}/include) 15 | # target_link_directories(lambda_lanczos_test_mkl PRIVATE ${MKLROOT}/lib) 16 | 17 | target_link_libraries(lambda_lanczos_test_impl GTest::GTest GTest::Main) 18 | target_link_libraries(lambda_lanczos_test_stdpar GTest::GTest GTest::Main tbb) 19 | target_link_libraries(lambda_lanczos_test_lapack GTest::GTest GTest::Main lapacke) 20 | 21 | file(READ "/etc/issue" ETC_ISSUE) 22 | string(REGEX MATCH "Ubuntu|Debian" DIST ${ETC_ISSUE}) 23 | if(DIST STREQUAL "") 24 | target_link_libraries(lambda_lanczos_test_lapack cblas) 25 | else() 26 | message(STATUS "Ubuntu or Debian detected. blas will be linked for cblas functions.") 27 | target_link_libraries(lambda_lanczos_test_lapack blas) 28 | endif() 29 | 30 | # target_link_libraries(lambda_lanczos_test_mkl GTest::GTest GTest::Main -Wl,--start-group libmkl_intel_lp64.a libmkl_sequential.a libmkl_core.a -Wl,--end-group) 31 | 32 | add_custom_target(lambda_lanczos_test 33 | DEPENDS lambda_lanczos_test_impl 34 | DEPENDS lambda_lanczos_test_stdpar 35 | DEPENDS lambda_lanczos_test_lapack 36 | # DEPENDS lambda_lanczos_test_mkl 37 | ) 38 | 39 | add_test(NAME FullTestImpl COMMAND lambda_lanczos_test_impl) 40 | add_test(NAME FullTestStdpar COMMAND lambda_lanczos_test_stdpar) 41 | add_test(NAME FullTestLapack COMMAND lambda_lanczos_test_lapack) 42 | # add_test(NAME FullTestMKL COMMAND lambda_lanczos_test_mkl) 43 | -------------------------------------------------------------------------------- /test/exponentiator_test.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | #define _USE_MATH_DEFINES 3 | #endif 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | template 14 | std::vector apply_matrix(const MT& matrix, const std::vector& in, bool dagger = false) { 15 | const auto n = in.size(); 16 | std::vector out(n, 0.0); 17 | 18 | for (size_t i = 0; i < n; ++i) { 19 | for (size_t j = 0; j < n; ++j) { 20 | if (dagger) { 21 | out[i] += lambda_lanczos::util::typed_conj(matrix[j][i]) * in[j]; 22 | } else { 23 | out[i] += matrix[i][j] * in[j]; 24 | } 25 | } 26 | } 27 | 28 | return out; 29 | } 30 | 31 | TEST(EXPONENTIATOR_TEST, EXPONENTIATE_REAL) { 32 | using namespace std; 33 | using namespace lambda_lanczos; 34 | 35 | const size_t n = 3; 36 | double matrix[n][n] = {{2.0, 1.0, 1.0}, {1.0, 2.0, 1.0}, {1.0, 1.0, 2.0}}; 37 | 38 | auto mv_mul = [&](const vector& in, vector& out) { 39 | for (size_t i = 0; i < n; ++i) { 40 | for (size_t j = 0; j < n; ++j) { 41 | out[i] += matrix[i][j] * in[j]; 42 | } 43 | } 44 | }; 45 | 46 | double a = 3; 47 | lambda_lanczos::Exponentiator exponentiator(mv_mul, n); 48 | vector input = {1, 0, 0}; 49 | vector output(n); 50 | size_t itern = exponentiator.run(a, input, output); 51 | 52 | double u[n][n] = {{1 / sqrt(3), -1 / sqrt(2), -1 / sqrt(6)}, 53 | {1 / sqrt(3), 0 / sqrt(2), 2 / sqrt(6)}, 54 | {1 / sqrt(3), 1 / sqrt(2), -1 / sqrt(6)}}; // u[:][k] is the k-th eigenvector 55 | vector eigvals = {4, 1, 1}; 56 | vector> diag(n, vector(n, 0.0)); 57 | for (size_t i = 0; i < n; ++i) { 58 | diag[i][i] = exp(a * eigvals[i]); 59 | } 60 | 61 | auto tmp = input; 62 | tmp = apply_matrix(u, tmp, true); 63 | tmp = apply_matrix(diag, tmp); 64 | tmp = apply_matrix(u, tmp); 65 | 66 | double overlap = std::abs(util::inner_prod(tmp, output)) / util::norm(tmp) / util::norm(output); 67 | 68 | cout << setprecision(16); 69 | cout << "itern: " << itern << endl; 70 | cout << "overlap: " << overlap << endl; 71 | 72 | EXPECT_NEAR(1.0, overlap, exponentiator.eps); 73 | 74 | /* Check Taylor exponentiation */ 75 | itern = exponentiator.taylor_run(a, input, output); 76 | overlap = std::abs(util::inner_prod(tmp, output)) / util::norm(tmp) / util::norm(output); 77 | 78 | cout << "itern (Taylor): " << itern << endl; 79 | cout << "overlap (Taylor): " << overlap << endl; 80 | EXPECT_NEAR(1.0, overlap, exponentiator.eps); 81 | } 82 | 83 | void make_plane_wave(double t, size_t n, std::vector& ev, std::vector>>& u) { 84 | using namespace std; 85 | 86 | const static auto I_ = complex(0.0, 1.0); 87 | 88 | vector> ke; 89 | for (size_t j = 0; j < n; ++j) { 90 | double k = 2 * M_PI / n * j; 91 | ke.emplace_back(k, 2 * t * cos(k)); 92 | } 93 | 94 | sort(ke.begin(), ke.end(), [](const auto& x, const auto& y) { return x.second < y.second; }); 95 | 96 | ev = vector(n); 97 | u = vector>>(n, vector>(n, 0)); 98 | for (size_t j = 0; j < n; ++j) { 99 | ev[j] = ke[j].second; 100 | for (size_t i = 0; i < n; ++i) { 101 | u[i][j] = exp(I_ * ke[j].first * (double)i) / sqrt(n); 102 | } 103 | } 104 | } 105 | 106 | TEST(EXPONENTIATOR_TEST, EXPONENTIATE_LARGE_MATRIX) { 107 | using namespace std; 108 | using namespace lambda_lanczos; 109 | 110 | const size_t n = 100; 111 | const double t = -1.0; 112 | 113 | auto mv_mul = [&](const vector>& in, vector>& out) { 114 | for (size_t i = 0; i < n - 1; ++i) { 115 | out[i] += t * in[i + 1]; 116 | out[i + 1] += t * in[i]; 117 | } 118 | 119 | out[0] += t * in[n - 1]; 120 | out[n - 1] += t * in[0]; 121 | }; 122 | 123 | complex a(0.0, 3.0); 124 | 125 | lambda_lanczos::Exponentiator> exponentiator(mv_mul, n); 126 | vector> input(n); 127 | input[0] = complex(1, 2); 128 | input[n - 1] = complex(1, 2); 129 | input[n / 2] = complex(8, 2); 130 | util::normalize(input); 131 | vector> output; // leave uninitialized for testing 132 | size_t itern = exponentiator.run(a, input, output); 133 | 134 | vector eigvals; 135 | vector>> u; 136 | make_plane_wave(t, n, eigvals, u); 137 | vector>> diag(n, vector>(n, 0.0)); 138 | for (size_t i = 0; i < n; ++i) { 139 | diag[i][i] = exp(a * eigvals[i]); 140 | } 141 | 142 | auto tmp = input; 143 | tmp = apply_matrix(u, tmp, true); 144 | tmp = apply_matrix(diag, tmp); 145 | tmp = apply_matrix(u, tmp); 146 | 147 | double overlap = std::abs(util::inner_prod(tmp, output)) / util::norm(tmp) / util::norm(output); 148 | 149 | cout << setprecision(16); 150 | cout << "itern: " << itern << endl; 151 | cout << "overlap: " << overlap << endl; 152 | 153 | EXPECT_NEAR(1.0, overlap, exponentiator.eps); 154 | 155 | /* Check Taylor exponentiation */ 156 | itern = exponentiator.taylor_run(a, input, output); 157 | overlap = std::abs(util::inner_prod(tmp, output)) / util::norm(tmp) / util::norm(output); 158 | 159 | cout << "itern (Taylor): " << itern << endl; 160 | cout << "overlap (Taylor): " << overlap << endl; 161 | EXPECT_NEAR(1.0, overlap, exponentiator.eps); 162 | } 163 | 164 | TEST(EXPONENTIATOR_TEST, EXPONENTIATE_ZERO_DELTA) { 165 | using namespace std; 166 | using namespace lambda_lanczos; 167 | 168 | const size_t n = 100; 169 | const double t = -1.0; 170 | 171 | auto mv_mul = [&](const vector>& in, vector>& out) { 172 | for (size_t i = 0; i < n - 1; ++i) { 173 | out[i] += t * in[i + 1]; 174 | out[i + 1] += t * in[i]; 175 | } 176 | 177 | out[0] += t * in[n - 1]; 178 | out[n - 1] += t * in[0]; 179 | }; 180 | 181 | complex a(0.0, 0.0); 182 | 183 | lambda_lanczos::Exponentiator> exponentiator(mv_mul, n); 184 | exponentiator.full_orthogonalize = true; 185 | 186 | vector> input(n); 187 | input[0] = complex(1, 2); 188 | input[n - 1] = complex(1, 2); 189 | input[n / 2] = complex(8, 2); 190 | util::normalize(input); 191 | vector> output; // leave uninitialized for testing 192 | size_t itern = exponentiator.run(a, input, output); 193 | 194 | vector eigvals; 195 | vector>> u; 196 | make_plane_wave(t, n, eigvals, u); 197 | vector>> diag(n, vector>(n, 0.0)); 198 | for (size_t i = 0; i < n; ++i) { 199 | diag[i][i] = exp(a * eigvals[i]); 200 | } 201 | 202 | auto tmp = input; 203 | tmp = apply_matrix(u, tmp, true); 204 | tmp = apply_matrix(diag, tmp); 205 | tmp = apply_matrix(u, tmp); 206 | 207 | double overlap = std::abs(util::inner_prod(tmp, output)) / util::norm(tmp) / util::norm(output); 208 | 209 | cout << setprecision(16); 210 | cout << "itern: " << itern << endl; 211 | cout << "overlap: " << overlap << endl; 212 | 213 | EXPECT_NEAR(1.0, overlap, exponentiator.eps); 214 | 215 | /* Check Taylor exponentiation */ 216 | itern = exponentiator.taylor_run(a, input, output); 217 | overlap = std::abs(util::inner_prod(tmp, output)) / util::norm(tmp) / util::norm(output); 218 | 219 | cout << "itern (Taylor): " << itern << endl; 220 | cout << "overlap (Taylor): " << overlap << endl; 221 | EXPECT_NEAR(1.0, overlap, exponentiator.eps); 222 | } 223 | -------------------------------------------------------------------------------- /test/lambda_lanczos_test.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | #define _USE_MATH_DEFINES 3 | #endif 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using lambda_lanczos::LambdaLanczos; 15 | 16 | template 17 | using vector = std::vector; 18 | 19 | template 20 | using complex = std::complex; 21 | 22 | template 23 | void vector_initializer(vector& v); 24 | 25 | template <> 26 | void vector_initializer(vector& v) { 27 | std::mt19937 mt(1); 28 | std::uniform_real_distribution rand(-1.0, 1.0); 29 | 30 | size_t n = v.size(); 31 | for (size_t i = 0; i < n; ++i) { 32 | v[i] = rand(mt); 33 | } 34 | } 35 | 36 | template <> 37 | void vector_initializer(vector>& v) { 38 | std::mt19937 mt(1); 39 | std::uniform_real_distribution rand(-1.0, 1.0); 40 | 41 | size_t n = v.size(); 42 | for (size_t i = 0; i < n; ++i) { 43 | v[i] = std::complex(rand(mt), rand(mt)); 44 | } 45 | } 46 | 47 | TEST(UNIT_TEST, INNER_PRODUCT) { 48 | complex c1(1.0, 3.0); 49 | complex c2(2.0, 4.0); 50 | 51 | vector> v1{3.0, c1}; 52 | vector> v2{3.0, c2}; 53 | 54 | auto result = lambda_lanczos::util::inner_prod(v1, v2); 55 | complex correct(23.0, -2.0); 56 | 57 | EXPECT_DOUBLE_EQ(correct.real(), result.real()); 58 | EXPECT_DOUBLE_EQ(correct.imag(), result.imag()); 59 | } 60 | 61 | TEST(UNIT_TEST, SCHMIDT_ORTHOGONALIZATION) { 62 | const size_t n = 10; 63 | 64 | std::mt19937 eng(1); 65 | std::uniform_real_distribution dist(-10.0, 10.0); 66 | 67 | const size_t num_vec = n / 2; 68 | vector>> us; 69 | for (size_t k = 0; k < num_vec; ++k) { 70 | vector> u(n); 71 | for (auto& elem : u) { 72 | elem = complex(dist(eng), dist(eng)); 73 | } 74 | 75 | lambda_lanczos::util::schmidt_orth(u, us.begin(), us.end()); 76 | lambda_lanczos::util::normalize(u); 77 | us.push_back(u); 78 | } 79 | 80 | vector> v(n); 81 | for (auto& elem : v) { 82 | elem = complex(dist(eng), dist(eng)); 83 | } 84 | lambda_lanczos::util::schmidt_orth(v, us.begin(), us.end()); 85 | 86 | for (const auto& u : us) { 87 | auto ip = lambda_lanczos::util::inner_prod(v, u); 88 | EXPECT_NEAR(0.0, ip.real(), 1e-15 * n); 89 | EXPECT_NEAR(0.0, ip.imag(), 1e-15 * n); 90 | } 91 | } 92 | 93 | TEST(UNIT_TEST, MANHATTAN_NORM) { 94 | complex c1(1.0, 3.0); 95 | complex c2(-1.0, -1.0); 96 | 97 | vector> v{c1, c2}; 98 | 99 | EXPECT_DOUBLE_EQ(1.0 + 3.0 + 1.0 + 1.0, lambda_lanczos::util::m_norm(v)); 100 | } 101 | 102 | TEST(UNIT_TEST, SORT_EIGENPAIRS) { 103 | vector eigvals{2, -1, 0}; 104 | vector>> eigvecs{{2, 2, 2}, {0, 0, 0}, {1, 1, 1}}; 105 | const size_t n = eigvals.size(); 106 | 107 | lambda_lanczos::util::sort_eigenpairs(eigvals, eigvecs, true); 108 | 109 | const vector expected_eigvals{-1, 0, 2}; 110 | const vector>> expected_eigvecs{{0, 0, 0}, {1, 1, 1}, {2, 2, 2}}; 111 | 112 | for (size_t i = 0; i < n; ++i) { 113 | EXPECT_DOUBLE_EQ(expected_eigvals[i], eigvals[i]); 114 | for (size_t j = 0; j < n; ++j) { 115 | EXPECT_DOUBLE_EQ(std::real(expected_eigvecs[i][j]), std::real(eigvecs[i][j])); 116 | } 117 | } 118 | } 119 | 120 | TEST(UNIT_TEST, STRINGIFY) { 121 | vector v{1, 2, 3}; 122 | std::string str = lambda_lanczos::util::vectorToString(v); 123 | std::cout << str << std::endl; 124 | 125 | EXPECT_STREQ("1 2 3", str.c_str()); 126 | } 127 | 128 | TEST(DIAGONALIZE_TEST, SIMPLE_MATRIX) { 129 | const size_t n = 3; 130 | double matrix[n][n] = {{2.0, 1.0, 1.0}, {1.0, 2.0, 1.0}, {1.0, 1.0, 2.0}}; 131 | /* Its eigenvalues are {4, 1, 1} */ 132 | 133 | auto matmul = [&](const vector& in, vector& out) { 134 | for (size_t i = 0; i < n; ++i) { 135 | for (size_t j = 0; j < n; ++j) { 136 | out[i] += matrix[i][j] * in[j]; 137 | } 138 | } 139 | }; 140 | 141 | LambdaLanczos engine(matmul, n, true, 1); 142 | engine.init_vector = vector_initializer; 143 | engine.eigenvalue_offset = 6.0; 144 | 145 | double eigvalue; 146 | vector eigvec(1); // The size will be enlarged automatically 147 | engine.run(eigvalue, eigvec); 148 | 149 | auto sign = eigvec[0] / std::abs(eigvec[0]); 150 | vector correct_eigvec(n); 151 | for (size_t i = 0; i < n; ++i) { 152 | correct_eigvec[i] = sign * 1.0 / sqrt(3.0); 153 | } 154 | double correct_eigvalue = 4.0; 155 | 156 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 157 | for (size_t i = 0; i < n; ++i) { 158 | EXPECT_NEAR(correct_eigvec[i], eigvec[i], std::abs(correct_eigvalue * engine.eps * 10)); 159 | } 160 | } 161 | 162 | TEST(DIAGONALIZE_TEST, SIMPLE_MATRIX_FLOAT) { 163 | const size_t n = 3; 164 | float matrix[n][n] = {{2.0f, 1.0f, 1.0f}, {1.0f, 2.0f, 1.0f}, {1.0f, 1.0f, 2.0f}}; 165 | /* Its eigenvalues are {4, 1, 1} */ 166 | 167 | auto matmul = [&](const vector& in, vector& out) { 168 | for (size_t i = 0; i < n; ++i) { 169 | for (size_t j = 0; j < n; ++j) { 170 | out[i] += matrix[i][j] * in[j]; 171 | } 172 | } 173 | }; 174 | 175 | LambdaLanczos engine(matmul, n, true, 1); 176 | 177 | float eigvalue; 178 | vector eigvec(1); // The size will be enlarged automatically 179 | engine.run(eigvalue, eigvec); 180 | 181 | auto sign = eigvec[0] / std::abs(eigvec[0]); 182 | vector correct_eigvec(n); 183 | for (size_t i = 0; i < n; ++i) { 184 | correct_eigvec[i] = sign * 1.0f / std::sqrt(3.0f); 185 | } 186 | float correct_eigvalue = 4.0f; 187 | 188 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 189 | for (size_t i = 0; i < n; ++i) { 190 | EXPECT_NEAR(correct_eigvec[i], eigvec[i], std::abs(correct_eigvalue * engine.eps * 10)); 191 | } 192 | } 193 | 194 | TEST(DIAGONALIZE_TEST, SIMPLE_MATRIX_MULTIPLE_VALUE_RETURN_FEATURE) { 195 | const size_t n = 3; 196 | double matrix[n][n] = {{2.0, 1.0, 1.0}, {1.0, 2.0, 1.0}, {1.0, 1.0, 2.0}}; 197 | /* Its eigenvalues are {4, 1, 1} */ 198 | 199 | auto matmul = [&](const vector& in, vector& out) { 200 | for (size_t i = 0; i < n; ++i) { 201 | for (size_t j = 0; j < n; ++j) { 202 | out[i] += matrix[i][j] * in[j]; 203 | } 204 | } 205 | }; 206 | 207 | LambdaLanczos engine(matmul, n, true, 1); 208 | engine.init_vector = vector_initializer; 209 | engine.eigenvalue_offset = 6.0; 210 | 211 | auto [eigenvalues, eigenvectors] = engine.run(); // C++17 multiple value return 212 | auto eigvalue = eigenvalues[0]; 213 | auto eigvec = eigenvectors[0]; 214 | 215 | auto sign = eigvec[0] / std::abs(eigvec[0]); 216 | vector correct_eigvec(n); 217 | for (size_t i = 0; i < n; ++i) { 218 | correct_eigvec[i] = sign * 1.0 / sqrt(3.0); 219 | } 220 | double correct_eigvalue = 4.0; 221 | 222 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 223 | for (size_t i = 0; i < n; ++i) { 224 | EXPECT_NEAR(correct_eigvec[i], eigvec[i], std::abs(correct_eigvalue * engine.eps * 10)); 225 | } 226 | } 227 | 228 | TEST(DIAGONALIZE_TEST, SIMPLE_MATRIX_NOT_FIX_RANDOM_SEED) { 229 | const size_t n = 3; 230 | double matrix[n][n] = {{2.0, 1.0, 1.0}, {1.0, 2.0, 1.0}, {1.0, 1.0, 2.0}}; 231 | /* Its eigenvalues are {4, 1, 1} */ 232 | 233 | auto matmul = [&](const vector& in, vector& out) { 234 | for (size_t i = 0; i < n; ++i) { 235 | for (size_t j = 0; j < n; ++j) { 236 | out[i] += matrix[i][j] * in[j]; 237 | } 238 | } 239 | }; 240 | 241 | LambdaLanczos engine(matmul, n, true, 1); 242 | engine.eigenvalue_offset = 6.0; 243 | 244 | double eigvalue; 245 | vector eigvec(1); // The size will be enlarged automatically 246 | engine.run(eigvalue, eigvec); 247 | 248 | auto sign = eigvec[0] / std::abs(eigvec[0]); 249 | vector correct_eigvec(n); 250 | for (size_t i = 0; i < n; ++i) { 251 | correct_eigvec[i] = sign * 1.0 / sqrt(3.0); 252 | } 253 | double correct_eigvalue = 4.0; 254 | 255 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 256 | for (size_t i = 0; i < n; ++i) { 257 | EXPECT_NEAR(correct_eigvec[i], eigvec[i], std::abs(correct_eigvalue * engine.eps * 10)); 258 | } 259 | } 260 | 261 | TEST(DIAGONALIZE_TEST, DYNAMIC_MATRIX) { 262 | const size_t n = 10; 263 | 264 | auto matmul = [&](const vector& in, vector& out) { 265 | for (size_t i = 0; i < n - 1; ++i) { 266 | out[i] += -1.0 * in[i + 1]; 267 | out[i + 1] += -1.0 * in[i]; 268 | } 269 | 270 | // out[0] += -1.0*in[n-1]; // This corresponds to 271 | // out[n-1] += -1.0*in[0]; // periodic boundary condition 272 | }; 273 | /* 274 | This lambda is equivalent to applying following n by n matrix 275 | 276 | 0 -1 0 .. 0 277 | -1 0 -1 .. 0 278 | 0 -1 0 .. 0 279 | 0 .. .. 0 280 | 0 .. 0 -1 0 281 | 0 .. -1 0 -1 282 | 0 .. 0 -1 0 283 | 284 | Its smallest eigenvalue is -2*cos(pi/(n+1)). 285 | */ 286 | 287 | LambdaLanczos engine(matmul, n, false, 1); 288 | engine.init_vector = vector_initializer; 289 | engine.eps = 1e-14; 290 | engine.eigenvalue_offset = -10.0; 291 | double eigvalue; 292 | vector eigvec(n); 293 | engine.run(eigvalue, eigvec); 294 | 295 | double correct_eigvalue = -2.0 * cos(M_PI / (n + 1)); 296 | auto sign = eigvec[0] / std::abs(eigvec[0]); 297 | vector correct_eigvec(n); 298 | for (size_t i = 0; i < n; ++i) { 299 | correct_eigvec[i] = sign * std::sin((i + 1) * M_PI / (n + 1)); 300 | } 301 | lambda_lanczos::util::normalize(correct_eigvec); 302 | 303 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 304 | for (size_t i = 0; i < n; ++i) { 305 | EXPECT_NEAR(correct_eigvec[i], eigvec[i], std::abs(correct_eigvalue * engine.eps * 10)); 306 | } 307 | } 308 | 309 | TEST(DIAGONALIZE_TEST, SIMPLE_MATRIX_USE_COMPLEX_TYPE) { 310 | const size_t n = 3; 311 | complex matrix[n][n] = {{2.0, 1.0, 1.0}, {1.0, 2.0, 1.0}, {1.0, 1.0, 2.0}}; 312 | /* Its eigenvalues are {4, 1, 1} */ 313 | 314 | auto matmul = [&](const vector>& in, vector>& out) { 315 | for (size_t i = 0; i < n; ++i) { 316 | for (size_t j = 0; j < n; ++j) { 317 | out[i] += matrix[i][j] * in[j]; 318 | } 319 | } 320 | }; 321 | 322 | LambdaLanczos> engine(matmul, n, true, 1); 323 | engine.init_vector = vector_initializer>; 324 | double eigvalue; 325 | vector> eigvec(n); 326 | engine.run(eigvalue, eigvec); 327 | 328 | vector> correct_eigvec(n); 329 | complex phase_factor = std::exp(complex(0.0, 1.0) * std::arg(eigvec[0])); 330 | for (size_t i = 0; i < n; ++i) { 331 | correct_eigvec[i] = 1.0 / std::sqrt(n) * phase_factor; 332 | } 333 | double correct_eigvalue = 4.0; 334 | 335 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 336 | for (size_t i = 0; i < n; ++i) { 337 | EXPECT_NEAR(correct_eigvec[i].real(), eigvec[i].real(), std::abs(correct_eigvalue * engine.eps * 10)); 338 | EXPECT_NEAR(correct_eigvec[i].imag(), eigvec[i].imag(), std::abs(correct_eigvalue * engine.eps * 10)); 339 | } 340 | } 341 | 342 | TEST(DIAGONALIZE_TEST, SIMPLE_MATRIX_USE_COMPLEX_TYPE_NOT_FIX_RANDOM_SEED) { 343 | const size_t n = 3; 344 | complex matrix[n][n] = {{2.0, 1.0, 1.0}, {1.0, 2.0, 1.0}, {1.0, 1.0, 2.0}}; 345 | /* Its eigenvalues are {4, 1, 1} */ 346 | 347 | auto matmul = [&](const vector>& in, vector>& out) { 348 | for (size_t i = 0; i < n; ++i) { 349 | for (size_t j = 0; j < n; ++j) { 350 | out[i] += matrix[i][j] * in[j]; 351 | } 352 | } 353 | }; 354 | 355 | LambdaLanczos> engine(matmul, n, true, 1); 356 | double eigvalue; 357 | vector> eigvec(n); 358 | engine.run(eigvalue, eigvec); 359 | 360 | vector> correct_eigvec(n); 361 | complex phase_factor = std::exp(complex(0.0, 1.0) * std::arg(eigvec[0])); 362 | for (size_t i = 0; i < n; ++i) { 363 | correct_eigvec[i] = 1.0 / std::sqrt(n) * phase_factor; 364 | } 365 | double correct_eigvalue = 4.0; 366 | 367 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 368 | for (size_t i = 0; i < n; ++i) { 369 | EXPECT_NEAR(correct_eigvec[i].real(), eigvec[i].real(), std::abs(correct_eigvalue * engine.eps * 10)); 370 | EXPECT_NEAR(correct_eigvec[i].imag(), eigvec[i].imag(), std::abs(correct_eigvalue * engine.eps * 10)); 371 | } 372 | } 373 | 374 | TEST(DIAGONALIZE_TEST, HERMITIAN_MATRIX) { 375 | const size_t n = 3; 376 | const auto I_ = complex(0.0, 1.0); 377 | complex matrix[n][n] = {{0.0, I_, 1.0}, {-I_, 0.0, I_}, {1.0, -I_, 0.0}}; 378 | /* Its eigenvalues are {-2, 1, 1} */ 379 | 380 | auto matmul = [&](const vector>& in, vector>& out) { 381 | for (size_t i = 0; i < n; ++i) { 382 | for (size_t j = 0; j < n; ++j) { 383 | out[i] += matrix[i][j] * in[j]; 384 | } 385 | } 386 | }; 387 | 388 | LambdaLanczos> engine(matmul, n, false, 1); 389 | engine.init_vector = vector_initializer>; 390 | double eigvalue; 391 | vector> eigvec(n); 392 | engine.run(eigvalue, eigvec); 393 | 394 | vector> correct_eigvec{1.0, I_, -1.0}; 395 | lambda_lanczos::util::normalize(correct_eigvec); 396 | complex phase_factor = std::exp(complex(0.0, 1.0) * std::arg(eigvec[0])); 397 | for (size_t i = 0; i < n; ++i) { 398 | correct_eigvec[i] *= phase_factor; 399 | } 400 | 401 | double correct_eigvalue = -2.0; 402 | 403 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 404 | for (size_t i = 0; i < n; ++i) { 405 | EXPECT_NEAR(correct_eigvec[i].real(), eigvec[i].real(), std::abs(correct_eigvalue * engine.eps * 10)); 406 | EXPECT_NEAR(correct_eigvec[i].imag(), eigvec[i].imag(), std::abs(correct_eigvalue * engine.eps * 10)); 407 | } 408 | } 409 | 410 | TEST(DIAGONALIZE_TEST, SINGLE_ELEMENT_MATRIX) { 411 | const size_t n = 1; 412 | 413 | double correct_eigvalue = 2.0; 414 | double matrix[n][n] = {{correct_eigvalue}}; 415 | 416 | auto matmul = [&](const vector& in, vector& out) { 417 | for (size_t i = 0; i < n; ++i) { 418 | for (size_t j = 0; j < n; ++j) { 419 | out[i] += matrix[i][j] * in[j]; 420 | } 421 | } 422 | }; 423 | 424 | LambdaLanczos engine(matmul, n, true, 1); 425 | engine.init_vector = vector_initializer; 426 | 427 | double eigvalue; 428 | vector eigvec(1); // The size will be enlarged automatically 429 | engine.run(eigvalue, eigvec); 430 | 431 | auto sign = eigvec[0] / std::abs(eigvec[0]); 432 | vector correct_eigvec(n); 433 | correct_eigvec[0] = sign; 434 | 435 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 436 | for (size_t i = 0; i < n; ++i) { 437 | EXPECT_NEAR(correct_eigvec[i], eigvec[i], std::abs(correct_eigvalue * engine.eps * 10)); 438 | } 439 | } 440 | 441 | TEST(DIAGONALIZE_TEST, MULTIPLE_EIGENPAIRS) { 442 | const int n = 8; 443 | const size_t nroot = 3; 444 | 445 | double matrix[n][n] = {{6, -3, -3, 0, -1, 1, -1, 1}, 446 | {-3, -4, 2, 2, -1, -5, 0, -4}, 447 | {-3, 2, 2, -3, 0, 0, -1, -1}, 448 | {0, 2, -3, 0, -3, 3, 2, 2}, 449 | {-1, -1, 0, -3, -2, 0, -5, -4}, 450 | {1, -5, 0, 3, 0, -4, 5, 0}, 451 | {-1, 0, -1, 2, -5, 5, -4, 4}, 452 | {1, -4, -1, 2, -4, 0, 4, 2}}; 453 | 454 | // the matrix-vector multiplication routine 455 | auto mv_mul = [&](const vector& in, vector& out) { 456 | for (int i = 0; i < n; ++i) { 457 | for (int j = 0; j < n; ++j) { 458 | out[i] += matrix[i][j] * in[j]; 459 | } 460 | } 461 | }; 462 | 463 | LambdaLanczos engine(mv_mul, n, false, 1); // false means to calculate the smallest eigenvalue. 464 | engine.num_eigs = nroot; 465 | engine.eps = 1e-7; 466 | 467 | vector eigenvalues; 468 | vector> eigenvectors; 469 | engine.run(eigenvalues, eigenvectors); 470 | 471 | std::array correct_eigvals = {-13.21508597, -8.50033154, -4.26674892}; 472 | std::array, nroot> correct_eigvecs = { 473 | {{0.02081752, -0.49222707, 0.13202088, 0.24048092, 0.15089223, -0.60850056, 0.48079787, -0.24043829}, 474 | {0.16645991, 0.51818471, -0.00646562, -0.09493495, 0.60595718, 0.02042567, 0.52346924, 0.23043415}, 475 | {0.03381669, -0.07999997, 0.32090331, 0.61650970, 0.41812886, -0.01782613, -0.45571810, 0.35575946}}}; 476 | 477 | for (size_t iroot = 0; iroot < nroot; ++iroot) { 478 | EXPECT_NEAR(correct_eigvals[iroot], eigenvalues[iroot], std::abs(correct_eigvals[iroot] * engine.eps)); 479 | 480 | auto sign = eigenvectors[iroot][0] / std::abs(eigenvectors[iroot][0]); 481 | for (size_t i = 0; i < n; ++i) { 482 | correct_eigvecs[iroot][i] *= sign; 483 | EXPECT_NEAR( 484 | correct_eigvecs[iroot][i], eigenvectors[iroot][i], std::abs(correct_eigvals[iroot] * engine.eps * 10)); 485 | } 486 | } 487 | } 488 | 489 | TEST(DIAGONALIZE_TEST, MULTIPLE_DEGENERATE_EIGENPAIRS) { 490 | const size_t n = 50; 491 | 492 | auto matmul = [&](const vector& in, vector& out) { 493 | for (size_t i = 0; i < n - 1; ++i) { 494 | out[i] += -1.0 * in[i + 1]; 495 | out[i + 1] += -1.0 * in[i]; 496 | } 497 | 498 | out[0] += -1.0 * in[n - 1]; 499 | out[n - 1] += -1.0 * in[0]; 500 | }; 501 | /* 502 | This lambda is equivalent to applying following n by n matrix 503 | 504 | 0 -1 0 .. -1 505 | -1 0 -1 .. 0 506 | 0 -1 0 .. 0 507 | 0 .. .. 0 508 | 0 .. 0 -1 0 509 | 0 .. -1 0 -1 510 | -1 .. 0 -1 0 511 | 512 | Its eigenvalues are -2*cos(2*pi*i/n), 0 <= i < n. 513 | */ 514 | 515 | const int num_eigs = 26; 516 | LambdaLanczos engine(matmul, n, false, 1); 517 | engine.num_eigs = num_eigs; 518 | engine.eps = 1e-14; 519 | vector eigvals; 520 | vector> eigvecs; 521 | engine.run(eigvals, eigvecs); 522 | 523 | vector correct_eigvals(engine.num_eigs); 524 | std::iota(correct_eigvals.begin(), correct_eigvals.end(), -num_eigs / 2); 525 | 526 | std::transform(correct_eigvals.begin(), correct_eigvals.end(), correct_eigvals.begin(), [](double x) { 527 | return -2.0 * cos(2.0 * M_PI * x / n); 528 | }); 529 | std::sort(correct_eigvals.begin(), correct_eigvals.end()); 530 | 531 | EXPECT_EQ(correct_eigvals.size(), eigvals.size()); 532 | for (size_t i = 0; i < correct_eigvals.size(); ++i) { 533 | EXPECT_NEAR(correct_eigvals[i], eigvals[i], engine.eps); 534 | } 535 | } 536 | 537 | template 538 | void generate_random_symmetric_matrix(T** a, vector& eigvec, T& eigvalue, size_t n, size_t rand_n, RE eng) { 539 | const T min_eigvalue = 1.0; 540 | std::uniform_int_distribution dist_index(0, n - 1); 541 | std::uniform_real_distribution dist_angle(0.0, 2 * M_PI); 542 | std::uniform_real_distribution dist_element(min_eigvalue, n * 10); 543 | 544 | /* Generate a random diagonal matrix */ 545 | std::fill(a[0], a[0] + n * n, 0.0); 546 | T max_eigvalue = min_eigvalue; 547 | size_t max_eig_index = 0; 548 | for (size_t i = 0; i < n; ++i) { 549 | a[i][i] = dist_element(eng); 550 | if (a[i][i] > max_eigvalue) { 551 | max_eigvalue = a[i][i]; 552 | max_eig_index = i; 553 | } 554 | } 555 | 556 | eigvalue = max_eigvalue; 557 | 558 | /* Eigenvector corresponding to the maximum eigenvalue */ 559 | std::fill(eigvec.begin(), eigvec.end(), T()); 560 | eigvec[max_eig_index] = 1.0; 561 | 562 | for (size_t i = 0; i < rand_n; ++i) { 563 | size_t k = dist_index(eng); 564 | size_t l = dist_index(eng); 565 | while (k == l) { 566 | l = dist_index(eng); 567 | } 568 | 569 | T theta = dist_angle(eng); 570 | 571 | T c = std::cos(theta); 572 | T s = std::sin(theta); 573 | T a_kk = a[k][k]; 574 | T a_kl = a[k][l]; 575 | T a_ll = a[l][l]; 576 | 577 | for (size_t i = 0; i < n; ++i) { 578 | T aki_next = c * a[k][i] - s * a[l][i]; 579 | a[l][i] = s * a[k][i] + c * a[l][i]; 580 | a[k][i] = aki_next; 581 | } 582 | 583 | /* Symmetrize */ 584 | for (size_t i = 0; i < n; ++i) { 585 | a[i][k] = a[k][i]; 586 | a[i][l] = a[l][i]; 587 | } 588 | 589 | a[k][k] = c * (c * a_kk - s * a_kl) - s * (c * a_kl - s * a_ll); 590 | a[k][l] = s * (c * a_kk - s * a_kl) + c * (c * a_kl - s * a_ll); 591 | a[l][k] = a[k][l]; 592 | a[l][l] = s * (s * a_kk + c * a_kl) + c * (s * a_kl + c * a_ll); 593 | 594 | T vk_next = c * eigvec[k] - s * eigvec[l]; 595 | eigvec[l] = s * eigvec[k] + c * eigvec[l]; 596 | eigvec[k] = vk_next; 597 | } 598 | } 599 | 600 | TEST(DIAGONALIZE_TEST, RANDOM_SYMMETRIC_MATRIX) { 601 | const size_t n = 50; 602 | 603 | double** matrix = new double*[n]; 604 | matrix[0] = new double[n * n]; 605 | for (size_t i = 0; i < n; ++i) { 606 | matrix[i] = matrix[0] + n * i; 607 | } 608 | 609 | vector correct_eigvec(n); 610 | double correct_eigvalue = 0.0; 611 | 612 | generate_random_symmetric_matrix(matrix, correct_eigvec, correct_eigvalue, n, n * 10, std::mt19937(1)); 613 | 614 | auto matmul = [&](const vector& in, vector& out) { 615 | for (size_t i = 0; i < n; ++i) { 616 | for (size_t j = 0; j < n; ++j) { 617 | out[i] += matrix[i][j] * in[j]; 618 | } 619 | } 620 | }; 621 | 622 | LambdaLanczos engine(matmul, n, true, 1); 623 | engine.init_vector = vector_initializer; 624 | double eigvalue; 625 | vector eigvec(n); 626 | engine.run(eigvalue, eigvec); 627 | 628 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 629 | auto sign = (eigvec[0] * correct_eigvec[0] > 0) ? 1 : -1; 630 | for (size_t i = 0; i < n; ++i) { 631 | EXPECT_NEAR(correct_eigvec[i] * sign, eigvec[i], std::abs(correct_eigvalue * engine.eps * n * n)); 632 | } 633 | 634 | delete[] matrix[0]; 635 | delete[] matrix; 636 | } 637 | 638 | template 639 | void generate_random_hermitian_matrix( 640 | complex** a, vector>& eigvec, T& eigvalue, size_t n, size_t rand_n, RE eng) { 641 | const complex I_(0, 1); 642 | 643 | const T min_eigvalue = 1.0; 644 | std::uniform_int_distribution dist_index(0, n - 1); 645 | std::uniform_real_distribution dist_angle(0.0, 2 * M_PI); 646 | std::uniform_real_distribution dist_element(min_eigvalue, n * 10); 647 | 648 | /* Generate a random diagonal matrix */ 649 | std::fill(a[0], a[0] + n * n, 0.0); 650 | T max_eigvalue = min_eigvalue; 651 | size_t max_eig_index = 0; 652 | for (size_t i = 0; i < n; ++i) { 653 | T eigvalue_tmp = dist_element(eng); 654 | a[i][i] = eigvalue_tmp; 655 | if (eigvalue_tmp > max_eigvalue) { 656 | max_eigvalue = eigvalue_tmp; 657 | max_eig_index = i; 658 | } 659 | } 660 | 661 | eigvalue = max_eigvalue; 662 | 663 | /* Eigenvector corresponding to the maximum eigenvalue */ 664 | std::fill(eigvec.begin(), eigvec.end(), T()); 665 | eigvec[max_eig_index] = 1.0; 666 | 667 | for (size_t i = 0; i < rand_n; ++i) { 668 | size_t k = dist_index(eng); 669 | size_t l = dist_index(eng); 670 | while (k == l) { 671 | l = dist_index(eng); 672 | } 673 | 674 | auto theta = dist_angle(eng); 675 | auto phi1 = dist_angle(eng); 676 | auto phi2 = dist_angle(eng); 677 | 678 | complex u_kk = std::exp(I_ * phi1) * std::cos(theta); 679 | complex u_kl = -std::exp(I_ * phi2) * std::sin(theta); 680 | complex u_lk = std::exp(-I_ * phi2) * std::sin(theta); 681 | complex u_ll = std::exp(-I_ * phi1) * std::cos(theta); 682 | 683 | auto a_kk = a[k][k]; 684 | auto a_kl = a[k][l]; 685 | auto a_lk = a[l][k]; 686 | auto a_ll = a[l][l]; 687 | 688 | for (size_t i = 0; i < n; ++i) { 689 | auto aki_next = u_kk * a[k][i] + u_kl * a[l][i]; 690 | a[l][i] = u_lk * a[k][i] + u_ll * a[l][i]; 691 | a[k][i] = aki_next; 692 | } 693 | 694 | /* Hermitize */ 695 | for (size_t i = 0; i < n; ++i) { 696 | a[i][k] = std::conj(a[k][i]); 697 | a[i][l] = std::conj(a[l][i]); 698 | } 699 | 700 | a[k][k] = u_kk * (a_kk * std::conj(u_kk) + a_kl * std::conj(u_kl)) + 701 | u_kl * (a_lk * std::conj(u_kk) + a_ll * std::conj(u_kl)); 702 | a[k][l] = u_kk * (a_kk * std::conj(u_lk) + a_kl * std::conj(u_ll)) + 703 | u_kl * (a_lk * std::conj(u_lk) + a_ll * std::conj(u_ll)); 704 | a[l][k] = std::conj(a[k][l]); 705 | a[l][l] = u_lk * (a_kk * std::conj(u_lk) + a_kl * std::conj(u_ll)) + 706 | u_ll * (a_lk * std::conj(u_lk) + a_ll * std::conj(u_ll)); 707 | 708 | auto vk_next = u_kk * eigvec[k] + u_kl * eigvec[l]; 709 | eigvec[l] = u_lk * eigvec[k] + u_ll * eigvec[l]; 710 | eigvec[k] = vk_next; 711 | } 712 | } 713 | 714 | TEST(DIAGONALIZE_TEST, RANDOM_HERMITIAN_MATRIX) { 715 | const size_t n = 10; 716 | 717 | complex** matrix = new complex*[n]; 718 | matrix[0] = new complex[n * n]; 719 | for (size_t i = 0; i < n; ++i) { 720 | matrix[i] = matrix[0] + n * i; 721 | } 722 | 723 | vector> correct_eigvec(n); 724 | double correct_eigvalue = 0.0; 725 | 726 | generate_random_hermitian_matrix(matrix, correct_eigvec, correct_eigvalue, n, n * 10, std::mt19937(1)); 727 | 728 | auto matmul = [&](const vector>& in, vector>& out) { 729 | for (size_t i = 0; i < n; ++i) { 730 | for (size_t j = 0; j < n; ++j) { 731 | out[i] += matrix[i][j] * in[j]; 732 | } 733 | } 734 | }; 735 | 736 | LambdaLanczos> engine(matmul, n, true, 1); 737 | engine.init_vector = vector_initializer>; 738 | engine.eps = 1e-14; 739 | double eigvalue; 740 | vector> eigvec(n); 741 | engine.run(eigvalue, eigvec); 742 | 743 | EXPECT_NEAR(correct_eigvalue, eigvalue, std::abs(correct_eigvalue * engine.eps)); 744 | 745 | const complex I_(0, 1); 746 | auto phase = std::exp(I_ * (std::arg(eigvec[0]) - std::arg(correct_eigvec[0]))); 747 | for (size_t i = 0; i < n; ++i) { 748 | EXPECT_NEAR((correct_eigvec[i] * phase).real(), eigvec[i].real(), std::abs(correct_eigvalue * engine.eps * 10)); 749 | EXPECT_NEAR((correct_eigvec[i] * phase).imag(), eigvec[i].imag(), std::abs(correct_eigvalue * engine.eps * 10)); 750 | } 751 | 752 | delete[] matrix[0]; 753 | delete[] matrix; 754 | } 755 | 756 | TEST(TRIDIAGONAL_TEST, IMPLICIT_SHIFT_QR) { 757 | vector alpha{1, 2, 3}; 758 | vector beta{2, 2}; 759 | 760 | const auto n = alpha.size(); 761 | vector eigvals(n); 762 | vector> eigvecs; 763 | 764 | lambda_lanczos::tridiagonal_impl::tridiagonal_eigenpairs(alpha, beta, eigvals, eigvecs); 765 | 766 | vector correct_eigvals{-1, 2, 5}; 767 | vector> correct_eigvecs{{2, -2, 1}, {2, 1, -2}, {1, 2, 2}}; 768 | for (auto& v : correct_eigvecs) { 769 | lambda_lanczos::util::normalize(v); 770 | } 771 | 772 | double eps = 1e-10; 773 | for (size_t i = 0; i < n; ++i) { 774 | EXPECT_NEAR(correct_eigvals[i], eigvals[i], eps); 775 | 776 | auto sign = eigvecs[i][0] / std::abs(eigvecs[i][0]); 777 | std::cout << lambda_lanczos::util::vectorToString(eigvecs[i]) << std::endl; 778 | for (size_t j = 0; j < n; ++j) { 779 | EXPECT_NEAR(correct_eigvecs[i][j] * sign, eigvecs[i][j], eps); 780 | } 781 | } 782 | std::cout << std::endl; 783 | } 784 | 785 | TEST(TRIDIAGONAL_TEST, NULL_EIGENVALUE_NO_ASSERTS) { 786 | vector alpha{6.82333617e-03, 3.09398208e+00, 1.89919458e+00, 1.28531906e-16}; 787 | vector beta{1.19582528e-01, -1.37689656e+00, 6.16147405e-15}; 788 | 789 | vector v = alpha; 790 | const auto n = alpha.size(); 791 | 792 | vector> q; 793 | 794 | lambda_lanczos::tridiagonal_impl::tridiagonal_eigenpairs(alpha, beta, v, q); 795 | 796 | for (size_t i = 0; i < n; ++i) { 797 | std::cout << v[i] << " "; 798 | } 799 | std::cout << std::endl; 800 | } 801 | --------------------------------------------------------------------------------