LCOV - code coverage report
Current view: top level - modules/linalg - eigen.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 67 122 54.9 %
Date: 2024-05-13 05:06:06 Functions: 5 8 62.5 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*************************************************************************
       2             :  *
       3             :  *  Project
       4             :  *                         _____ _____  __  __ _____
       5             :  *                        / ____|  __ \|  \/  |  __ \
       6             :  *  ___  _ __   ___ _ __ | |  __| |__) | \  / | |__) |
       7             :  * / _ \| '_ \ / _ \ '_ \| | |_ |  ___/| |\/| |  ___/
       8             :  *| (_) | |_) |  __/ | | | |__| | |    | |  | | |
       9             :  * \___/| .__/ \___|_| |_|\_____|_|    |_|  |_|_|
      10             :  *      | |
      11             :  *      |_|
      12             :  *
      13             :  * Copyright (C) Akiel Aries, <akiel@akiel.org>, et al.
      14             :  *
      15             :  * This software is licensed as described in the file LICENSE, which
      16             :  * you should have received as part of this distribution. The terms
      17             :  * among other details are referenced in the official documentation
      18             :  * seen here : https://akielaries.github.io/openGPMP/ along with
      19             :  * important files seen in this project.
      20             :  *
      21             :  * You may opt to use, copy, modify, merge, publish, distribute
      22             :  * and/or sell copies of the Software, and permit persons to whom
      23             :  * the Software is furnished to do so, under the terms of the
      24             :  * LICENSE file. As this is an Open Source effort, all implementations
      25             :  * must be of the same methodology.
      26             :  *
      27             :  *
      28             :  *
      29             :  * This software is distributed on an AS IS basis, WITHOUT
      30             :  * WARRANTY OF ANY KIND, either express or implied.
      31             :  *
      32             :  ************************************************************************/
      33             : #include <cmath>
      34             : #include <iostream>
      35             : #include <numeric>
      36             : #include <openGPMP/linalg/eigen.hpp>
      37             : #include <vector>
      38             : 
      39           7 : gpmp::linalg::Eigen::Eigen(const std::vector<std::vector<double>> &mat)
      40           7 :     : matrix(mat), size(mat.size()) {
      41             :     // Check if the matrix is square
      42             :     /*if (size != mat[0].size()) {
      43             :         throw std::invalid_argument(
      44             :             "Error: Eigenvalue computation requires a square matrix.");
      45             :     }*/
      46           7 : }
      47             : 
      48           1 : double gpmp::linalg::Eigen::power_iter(int max_iters, double tolerance) const {
      49             :     // initial guess
      50           1 :     std::vector<double> x(size, 1.0);
      51           1 :     std::vector<double> y(size);
      52             : 
      53           7 :     for (int iter = 0; iter < max_iters; ++iter) {
      54             :         // perform matrix-vector multiplication: y = A * x
      55          28 :         for (int i = 0; i < size; ++i) {
      56          21 :             y[i] = 0.0;
      57          84 :             for (int j = 0; j < size; ++j) {
      58          63 :                 y[i] += matrix[i][j] * x[j];
      59             :             }
      60             :         }
      61             : 
      62             :         // find the maximum element in y
      63           7 :         double max_element = 0.0;
      64          28 :         for (int i = 0; i < size; ++i) {
      65          21 :             max_element = std::max(max_element, std::abs(y[i]));
      66             :         }
      67             : 
      68             :         // normalize y
      69          28 :         for (int i = 0; i < size; ++i) {
      70          21 :             y[i] /= max_element;
      71             :         }
      72             : 
      73             :         // check for convergence
      74           7 :         double error = 0.0;
      75          28 :         for (int i = 0; i < size; ++i) {
      76          21 :             error += std::abs(y[i] - x[i]);
      77             :         }
      78             : 
      79           7 :         if (error < tolerance) {
      80             :             // eigenvalue found
      81           1 :             return max_element;
      82             :         }
      83             : 
      84           6 :         x = y;
      85             :     }
      86             : 
      87             :     std::cerr << "Warning: Power iteration did not converge within the"
      88           0 :                  "specified iterations\n";
      89             :     // not converged
      90           0 :     return 0.0;
      91           1 : }
      92             : 
      93           6 : std::vector<double> gpmp::linalg::Eigen::qr_algorithm(int max_iters,
      94             :                                                       double tolerance) const {
      95             :     // initialize a copy of the matrix to avoid modifying the original
      96           6 :     std::vector<std::vector<double>> hessenberg_mtx = matrix;
      97             : 
      98             :     // QR Iteration
      99           6 :     for (int iter = 0; iter < max_iters; ++iter) {
     100             :         // QR decomposition of the Hessenberg matrix
     101          18 :         for (int i = 0; i < size - 1; ++i) {
     102             :             // perform Givens rotation to introduce zeros below the subdiagonal
     103          12 :             double a = hessenberg_mtx[i][i];
     104          12 :             double b = hessenberg_mtx[i + 1][i];
     105          12 :             double r = std::hypot(a, b);
     106          12 :             double c = a / r;
     107          12 :             double s = b / r;
     108             : 
     109             :             // apply Givens rotation to the submatrix
     110          48 :             for (int j = 0; j < size; ++j) {
     111             :                 double temp1 =
     112          36 :                     c * hessenberg_mtx[i][j] + s * hessenberg_mtx[i + 1][j];
     113             :                 double temp2 =
     114          36 :                     -s * hessenberg_mtx[i][j] + c * hessenberg_mtx[i + 1][j];
     115          36 :                 hessenberg_mtx[i][j] = temp1;
     116          36 :                 hessenberg_mtx[i + 1][j] = temp2;
     117             :             }
     118             :         }
     119             : 
     120             :         // check for convergence (off-diagonal elements close to zero)
     121           6 :         double offDiagonalSum = 0.0;
     122          12 :         for (int i = 2; i < size; ++i) {
     123           6 :             offDiagonalSum += std::abs(hessenberg_mtx[i][i - 1]);
     124             :         }
     125             : 
     126           6 :         if (offDiagonalSum < tolerance) {
     127             :             // extract eigenvalues from the diagonal of the Hessenberg matrix
     128           6 :             std::vector<double> eigenvalues;
     129          24 :             for (int i = 0; i < size; ++i) {
     130          18 :                 eigenvalues.push_back(hessenberg_mtx[i][i]);
     131             :             }
     132             : 
     133           6 :             return eigenvalues;
     134           6 :         }
     135             :     }
     136             : 
     137             :     std::cerr << "Warning: QR iteration did not converge within the "
     138           0 :                  "specified iterations\n";
     139             :     // empty vector if not converged
     140           0 :     return std::vector<double>();
     141           6 : }
     142             : 
     143           1 : double gpmp::linalg::Eigen::determinant() const {
     144             :     // compute the determinant as the product of eigenvalues
     145           1 :     std::vector<double> eigenvalues = qr_algorithm();
     146           1 :     double determinant = std::accumulate(eigenvalues.begin(),
     147             :                                          eigenvalues.end(),
     148             :                                          1.0,
     149             :                                          std::multiplies<double>());
     150           1 :     return determinant;
     151           1 : }
     152             : 
     153             : std::vector<double>
     154           1 : gpmp::linalg::Eigen::sensitivity(double perturbation) const {
     155           1 :     std::vector<double> originalEigenvalues = qr_algorithm();
     156             : 
     157           1 :     std::vector<double> perturbedEigenvalues;
     158           4 :     for (int i = 0; i < size; ++i) {
     159             :         // perturb the matrix
     160           3 :         std::vector<std::vector<double>> perturbedMatrix = matrix;
     161           3 :         perturbedMatrix[i][i] += perturbation;
     162             : 
     163             :         // calculate eigenvalues of perturbed matrix
     164             :         std::vector<double> perturbedVals =
     165           3 :             Eigen(perturbedMatrix).qr_algorithm();
     166           3 :         perturbedEigenvalues.push_back(perturbedVals[i]);
     167           3 :     }
     168             : 
     169             :     // calculate sensitivity: (perturbed eigenvalue - original eigenvalue) /
     170             :     // perturbation
     171           1 :     std::vector<double> sensitivity;
     172           4 :     for (int i = 0; i < size; ++i) {
     173             :         double sens =
     174           3 :             (perturbedEigenvalues[i] - originalEigenvalues[i]) / perturbation;
     175           3 :         sensitivity.push_back(sens);
     176             :     }
     177             : 
     178           2 :     return sensitivity;
     179           1 : }
     180             : 
     181           0 : void gpmp::linalg::Eigen::schur_decomp(
     182             :     std::vector<std::vector<double>> &schurMatrix,
     183             :     double tolerance) const {
     184             :     // Hessenberg matrix is already calculated in QR iteration
     185           0 :     schurMatrix = matrix;
     186             : 
     187           0 :     for (int i = size - 1; i > 0; --i) {
     188             :         // check for small subdiagonal elements to introduce a zero
     189           0 :         if (std::abs(schurMatrix[i][i - 1]) < tolerance) {
     190           0 :             schurMatrix[i][i - 1] = 0.0;
     191             :         } else {
     192             :             // Apply Givens rotation to introduce a zero below the subdiagonal
     193           0 :             double a = schurMatrix[i - 1][i - 1];
     194           0 :             double b = schurMatrix[i][i - 1];
     195           0 :             double r = std::hypot(a, b);
     196           0 :             double c = a / r;
     197           0 :             double s = b / r;
     198             : 
     199           0 :             for (int j = 0; j < size; ++j) {
     200             :                 double temp1 =
     201           0 :                     c * schurMatrix[i - 1][j] + s * schurMatrix[i][j];
     202             :                 double temp2 =
     203           0 :                     -s * schurMatrix[i - 1][j] + c * schurMatrix[i][j];
     204           0 :                 schurMatrix[i - 1][j] = temp1;
     205           0 :                 schurMatrix[i][j] = temp2;
     206             :             }
     207             :         }
     208             :     }
     209           0 : }
     210             : 
     211             : std::vector<std::vector<double>>
     212           0 : gpmp::linalg::Eigen::jordan_normal_form(double tolerance) const {
     213             :     std::vector<std::vector<double>> jordanMatrix(
     214           0 :         size,
     215           0 :         std::vector<double>(size, 0.0));
     216           0 :     std::vector<double> eigenvalues = qr_algorithm();
     217             : 
     218           0 :     for (int i = 0; i < size; ++i) {
     219           0 :         jordanMatrix[i][i] = eigenvalues[i];
     220             : 
     221             :         // find the size of the Jordan block
     222           0 :         int jordanBlockSize = 1;
     223           0 :         while (i + jordanBlockSize < size &&
     224           0 :                std::abs(matrix[i + jordanBlockSize][i]) < tolerance) {
     225           0 :             ++jordanBlockSize;
     226             :         }
     227             : 
     228             :         // fill the Jordan block
     229           0 :         for (int j = 1; j < jordanBlockSize; ++j) {
     230           0 :             jordanMatrix[i + j][i + j - 1] = 1.0;
     231             :         }
     232             :     }
     233             : 
     234           0 :     return jordanMatrix;
     235           0 : }
     236             : 
     237           0 : double gpmp::linalg::Eigen::rayleigh_iter(int maxIterations,
     238             :                                           double tolerance) const {
     239             :     // initial guess
     240           0 :     std::vector<double> x(size, 1.0);
     241           0 :     double lambdaPrev = 0.0;
     242             : 
     243           0 :     for (int iter = 0; iter < maxIterations; ++iter) {
     244             :         // Rayleigh quotient iteration
     245           0 :         std::vector<double> Ax(size, 0.0);
     246             : 
     247             :         // perform matrix-vector multiplication: Ax = A * x
     248           0 :         for (int i = 0; i < size; ++i) {
     249           0 :             for (int j = 0; j < size; ++j) {
     250           0 :                 Ax[i] += matrix[i][j] * x[j];
     251             :             }
     252             :         }
     253             : 
     254             :         // compute the Rayleigh quotient
     255             :         double lambda =
     256           0 :             std::inner_product(x.begin(), x.end(), Ax.begin(), 0.0) /
     257           0 :             std::inner_product(x.begin(), x.end(), x.begin(), 0.0);
     258             : 
     259             :         // check for convergence
     260           0 :         if (std::abs(lambda - lambdaPrev) < tolerance) {
     261           0 :             return lambda;
     262             :         }
     263             : 
     264             :         // update the guess vector
     265           0 :         double normAx = std::sqrt(
     266             :             std::inner_product(Ax.begin(), Ax.end(), Ax.begin(), 0.0));
     267           0 :         for (int i = 0; i < size; ++i) {
     268             :             // normalize each element of x by dividing by its magnitude
     269           0 :             x[i] = Ax[i] / normAx;
     270             :         }
     271             : 
     272           0 :         lambdaPrev = lambda;
     273           0 :     }
     274             : 
     275             :     std::cerr << "Warning: Rayleigh quotient iteration did not converge within "
     276           0 :                  "the specified iterations."
     277           0 :               << std::endl;
     278             :     // if not converged
     279           0 :     return 0.0;
     280           0 : }

Generated by: LCOV version 1.14