TheAlgorithms-C-Plus-Plus/numerical_methods/qr_eigen_values.cpp

285 lines
8.5 KiB
C++
Raw Normal View History

Major rework to improve code quality and add automation checks (#805) * delete secant method - it is identical to regula falsi * document + improvize root finding algorithms * attempt to document gaussian elimination * added file brief * commented doxygen-mainpage, added files-list link * corrected files list link path * files-list link correction - this time works :) * document successive approximations * cleaner equation * updating DIRECTORY.md * documented kmp string search * document brute force string search * document rabin-karp string search * fixed mainpage readme * doxygen v1.8.18 will suppress out the #minipage in the markdown * cpplint correction for header guard style * github action to auto format source code per cpplint standard * updated setting to add 1 space before `private` and `public` keywords * auto rename files and auto format code * added missing "run" for step * corrected asignmemt operation * fixed trim and assign syntax * added git move for renaming bad filenames * added missing pipe for trim * added missing space * use old and new fnames * store old fname using echo * move files only if there is a change in filename * put old filenames in quotes * use double quote for old filename * escape double quotes * remove old_fname * try escape characters and echo" * add file-type to find * cleanup echo * ensure all trim variables are also in quotes * try escape -quote again * remove second escpe quote * use single quote for first check * use carets instead of quotes * put variables in brackets * remove -e from echo * add debug echos * try print0 flag * find command with while instead of for-loop * find command using IFS instead * :tada: IFS fix worked - escaped quotes for git mv * protetc each word in git mv .. * filename exists in lower cases - renamed * :tada: git push enabled * updating DIRECTORY.md * git pull & then push * formatting filenames d7af6fdc8cb08578de6980d412e6e1caca1a1bcf * formatting source-code for d7af6fdc8cb08578de6980d412e6e1caca1a1bcf * remove allman break before braces * updating DIRECTORY.md * added missing comma lost in previous commit * orchestrate all workflows * fix yml indentation * force push format changes, add title to DIRECTORY.md * pull before proceeding * reorganize pull commands * use master branches for actions * rename .cc files to .cpp * added class destructor to clean up dynamic memory allocation * rename to awesome workflow * commented whole repo cpplint - added modified files lint check * removed need for cpplint * attempt to use actions/checkout@master * temporary: no dependency on cpplint * formatting filenames 153fb7b8a572aaf4561ac3d22d47e89480f11318 * formatting source-code for 153fb7b8a572aaf4561ac3d22d47e89480f11318 * updating DIRECTORY.md * fix diff filename * added comments to the code * added test case * formatting source-code for a850308fbada18c0d4b6f9a9cac5c34fc064cbae * updating DIRECTORY.md * added machine learning folder * added adaline algorithm * updating DIRECTORY.md * fixed issue [LWG2192](https://cplusplus.github.io/LWG/issue2192) for std::abs on MacOS * add cmath for same bug: [LWG2192](https://cplusplus.github.io/LWG/issue2192) for std::abs on MacOS * formatting source-code for f8925e482216aecd152bc898653ee9ab82213cf3 * use STL's inner_product * formatting source-code for f94a3305943d4cf00e4531857279b8032d0e9489 * added range comments * define activation function * use equal initial weights * change test2 function to predict * activation function not friend * previous commit correction * added option for predict function to return value before applying activation function as optional argument * added test case to classify points lying within a sphere * improve documentation for adaline * formatting source-code for 15ec4c3aba4fb41b81ed2b44b7154a4f7b45a898 * added cmake to geometry folder * added algorithm include for std::max * add namespace - machine_learning * add namespace - statistics * add namespace - sorting * added sorting algos to namespace sorting * added namespace string_search * formatting source-code for fd695305150777981dc2a1f256aa2be444e4f108 * added documentation to string_search namespace * feat: Add BFS and DFS algorithms to check for cycle in a directed graph * Remove const references for input of simple types Reason: overhead on access * fix bad code sorry for force push * Use pointer instead of the non-const reference because apparently google says so. * Remove a useless and possibly bad Graph constuctor overload * Explicitely specify type of vector during graph instantiation * updating DIRECTORY.md * find openMP before adding subdirectories * added kohonen self organizing map * updating DIRECTORY.md * remove older files and folders from gh-pages before adding new files * remove chronos library due to inacceptability by cpplint * use c++ specific static_cast instead * initialize radom number generator * updated image links with those from CPP repository * rename computer.... folder to numerical methods * added durand kerner method for root computation for arbitrarily large polynomials * fixed additional comma * fix cpplint errors * updating DIRECTORY.md * convert to function module * update documentation * move openmp to main loop * added two test cases * use INT16_MAX * remove return statement from omp-for loop and use "break" * run tests when no input is provided and skip tests when input polynomial is provided * while loop cannot have break - replaced with continue and check is present in the main while condition * (1) break while loop (2) skip runs on break_loop instead of hard-break * add documentation images * use long double for errors and tolerance checks * make iterator variable i local to threads * add critical secions to omp threads * bugfix: move file writing outside of the parallel loop othersie, there is no gurantee of the order of roots written to file * rename folder to data_structures * updating DIRECTORY.md * fix ambiguous symbol `size` * add data_structures to cmake * docs: enable tree view, add timestamp in footer, try clang assistaed parsing * doxygen - open links in external window * remove invalid parameter from function docs * use HTML5 img tag to resize images * move file to proper folder * fix documentations and cpplint * formatting source-code for aacaf9828c61bb0246fe0933ab8ade82128b8346 * updating DIRECTORY.md * cpplint: add braces for multiple statement if * add explicit link to badges * remove duplicate line Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * remove namespace indentation * remove file associations in settings * add author name * enable cmake in subfolders of data_structures * create and link object file * cpp lint fixes and instantiate template classes * cpp lint fixes and instantiate template classes Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * cpplint - ignore `build/include` Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * disable redundant gcc compilation in cpplint workflow Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * template header files contain function codes as well and removed redundant subfolders Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * updating DIRECTORY.md * remove semicolons after functions in a class Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * cpplint header guard style Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * remove semilon Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * added LU decomposition algorithm Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * added QR decomposition algorithm Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * use QR decomposition to find eigen values Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * updating DIRECTORY.md * use std::rand for thread safety Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * move srand to main() Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * cpplint braces correction Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * updated eigen value documentation Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * fix matrix shift doc Signed-off-by: Krishna Vedala <7001608+kvedala@users.noreply.github.com> * rename CONTRIBUTION.md to CONTRIBUTING.md #836 * remove 'sort alphabetical order' check * added documentation check * remove extra paranthesis * added gitpod * added gitpod link from README * attempt to add vscode gitpod extensions * update gitpod extensions * add gitpod extensions cmake-tools and git-graph * remove gitpod init and add commands * use init to one time install doxygen, graphviz, cpplint * use gitpod dockerfile * add ninja build system to docker * remove configure task * add github prebuild specs to gitpod * disable gitpod addcommit * update documentation for kohonen_som * added ode solve using forward euler method * added mid-point euler ode solver * fixed itegration step equation * added semi-implicit euler ODE solver * updating DIRECTORY.md * fix cpplint issues - lines 117 and 124 * added documentation to ode group * corrected semi-implicit euler function * updated docs and test cases better structure * replace `free` with `delete` operator * formatting source-code for f55ab50cf26d176fe56bdaffa6f0ce8023c03c18 * updating DIRECTORY.md * main function must return * added machine learning group * added kohonen som topology algorithm * fix graph image path * updating DIRECTORY.md * fix braces * use snprintf instead of sprintf * use static_cast * hardcode character buffer size * fix machine learning groups in documentation * fix missing namespace function * replace kvedala fork references to TheAlgorithms * fix bug in counting_sort Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com> Co-authored-by: Anmol3299 <mittalanmol22@gmail.com>
2020-06-20 00:04:56 +08:00
/**
* @file
* \brief Compute real eigen values and eigen vectors of a symmetric matrix
* using [QR decomposition](https://en.wikipedia.org/wiki/QR_decomposition)
* method.
* \author [Krishna Vedala](https://github.com/kvedala)
*/
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>
#ifdef _OPENMP
#include <omp.h>
#endif
#include "./qr_decompose.h"
using qr_algorithm::operator<<;
#define LIMS 9 /**< limit of range of matrix values */
/**
* create a symmetric square matrix of given size with random elements. A
* symmetric square matrix will *always* have real eigen values.
*
* \param[out] A matrix to create (must be pre-allocated in memory)
*/
void create_matrix(std::valarray<std::valarray<double>> *A) {
int i, j, tmp, lim2 = LIMS >> 1;
int N = A->size();
#ifdef _OPENMP
#pragma omp for
#endif
for (i = 0; i < N; i++) {
A[0][i][i] = (std::rand() % LIMS) - lim2;
for (j = i + 1; j < N; j++) {
tmp = (std::rand() % LIMS) - lim2;
A[0][i][j] = tmp; // summetrically distribute random values
A[0][j][i] = tmp;
}
}
}
/**
* Perform multiplication of two matrices.
* * R2 must be equal to C1
* * Resultant matrix size should be R1xC2
* \param[in] A first matrix to multiply
* \param[in] B second matrix to multiply
* \param[out] OUT output matrix (must be pre-allocated)
* \returns pointer to resultant matrix
*/
void mat_mul(const std::valarray<std::valarray<double>> &A,
const std::valarray<std::valarray<double>> &B,
std::valarray<std::valarray<double>> *OUT) {
int R1 = A.size();
int C1 = A[0].size();
int R2 = B.size();
int C2 = B[0].size();
if (C1 != R2) {
perror("Matrix dimensions mismatch!");
return;
}
for (int i = 0; i < R1; i++) {
for (int j = 0; j < C2; j++) {
OUT[0][i][j] = 0.f;
for (int k = 0; k < C1; k++) {
OUT[0][i][j] += A[i][k] * B[k][j];
}
}
}
}
namespace qr_algorithm {
/** Compute eigen values using iterative shifted QR decomposition algorithm as
* follows:
* 1. Use last diagonal element of A as eigen value approximation \f$c\f$
* 2. Shift diagonals of matrix \f$A' = A - cI\f$
* 3. Decompose matrix \f$A'=QR\f$
* 4. Compute next approximation \f$A'_1 = RQ \f$
* 5. Shift diagonals back \f$A_1 = A'_1 + cI\f$
* 6. Termination condition check: last element below diagonal is almost 0
* 1. If not 0, go back to step 1 with the new approximation \f$A_1\f$
* 2. If 0, continue to step 7
* 7. Save last known \f$c\f$ as the eigen value.
* 8. Are all eigen values found?
* 1. If not, remove last row and column of \f$A_1\f$ and go back to step 1.
* 2. If yes, stop.
*
* \note The matrix \f$A\f$ gets modified
*
* \param[in,out] A matrix to compute eigen values for
* \param[in] print_intermediates (optional) whether to print intermediate A, Q
* and R matrices (default = `false`)
*/
std::valarray<double> eigen_values(std::valarray<std::valarray<double>> *A,
bool print_intermediates = false) {
int rows = A->size();
int columns = rows;
int counter = 0, num_eigs = rows - 1;
double last_eig = 0;
std::valarray<std::valarray<double>> Q(rows);
std::valarray<std::valarray<double>> R(columns);
/* number of eigen values = matrix size */
std::valarray<double> eigen_vals(rows);
for (int i = 0; i < rows; i++) {
Q[i] = std::valarray<double>(columns);
R[i] = std::valarray<double>(columns);
}
/* continue till all eigen values are found */
while (num_eigs > 0) {
/* iterate with QR decomposition */
while (std::abs(A[0][num_eigs][num_eigs - 1]) >
std::numeric_limits<double>::epsilon()) {
// initial approximation = last diagonal element
last_eig = A[0][num_eigs][num_eigs];
for (int i = 0; i < rows; i++) {
A[0][i][i] -= last_eig; /* A - cI */
}
qr_decompose(*A, &Q, &R);
if (print_intermediates) {
std::cout << *A << "\n";
std::cout << Q << "\n";
std::cout << R << "\n";
printf("-------------------- %d ---------------------\n",
++counter);
}
// new approximation A' = R * Q
mat_mul(R, Q, A);
for (int i = 0; i < rows; i++) {
A[0][i][i] += last_eig; /* A + cI */
}
}
/* store the converged eigen value */
eigen_vals[num_eigs] = last_eig;
// A[0][num_eigs][num_eigs];
if (print_intermediates) {
std::cout << "========================\n";
std::cout << "Eigen value: " << last_eig << ",\n";
std::cout << "========================\n";
}
num_eigs--;
rows--;
columns--;
}
eigen_vals[0] = A[0][0][0];
if (print_intermediates) {
std::cout << Q << "\n";
std::cout << R << "\n";
}
return eigen_vals;
}
} // namespace qr_algorithm
/**
* test function to compute eigen values of a 2x2 matrix
* \f[\begin{bmatrix}
* 5 & 7\\
* 7 & 11
* \end{bmatrix}\f]
* which are approximately, {15.56158, 0.384227}
*/
void test1() {
std::valarray<std::valarray<double>> X = {{5, 7}, {7, 11}};
double y[] = {15.56158, 0.384227}; // corresponding y-values
std::cout << "------- Test 1 -------" << std::endl;
std::valarray<double> eig_vals = qr_algorithm::eigen_values(&X);
for (int i = 0; i < 2; i++) {
std::cout << i + 1 << "/2 Checking for " << y[i] << " --> ";
bool result = false;
for (int j = 0; j < 2 && !result; j++) {
if (std::abs(y[i] - eig_vals[j]) < 0.1) {
result = true;
std::cout << "(" << eig_vals[j] << ") ";
}
}
assert(result); // ensure that i^th expected eigen value was computed
std::cout << "found\n";
}
std::cout << "Test 1 Passed\n\n";
}
/**
* test function to compute eigen values of a 2x2 matrix
* \f[\begin{bmatrix}
* -4& 4& 2& 0& -3\\
* 4& -4& 4& -3& -1\\
* 2& 4& 4& 3& -3\\
* 0& -3& 3& -1&-1\\
* -3& -1& -3& -3& 0
* \end{bmatrix}\f]
* which are approximately, {9.27648, -9.26948, 2.0181, -1.03516, -5.98994}
*/
void test2() {
std::valarray<std::valarray<double>> X = {{-4, 4, 2, 0, -3},
{4, -4, 4, -3, -1},
{2, 4, 4, 3, -3},
{0, -3, 3, -1, -3},
{-3, -1, -3, -3, 0}};
double y[] = {9.27648, -9.26948, 2.0181, -1.03516,
-5.98994}; // corresponding y-values
std::cout << "------- Test 2 -------" << std::endl;
std::valarray<double> eig_vals = qr_algorithm::eigen_values(&X);
std::cout << X << "\n"
<< "Eigen values: " << eig_vals << "\n";
for (int i = 0; i < 5; i++) {
std::cout << i + 1 << "/5 Checking for " << y[i] << " --> ";
bool result = false;
for (int j = 0; j < 5 && !result; j++) {
if (std::abs(y[i] - eig_vals[j]) < 0.1) {
result = true;
std::cout << "(" << eig_vals[j] << ") ";
}
}
assert(result); // ensure that i^th expected eigen value was computed
std::cout << "found\n";
}
std::cout << "Test 2 Passed\n\n";
}
/**
* main function
*/
int main(int argc, char **argv) {
int mat_size = 5;
if (argc == 2) {
mat_size = atoi(argv[1]);
} else { // if invalid input argument is given run tests
test1();
test2();
std::cout << "Usage: ./qr_eigen_values [mat_size]\n";
return 0;
}
if (mat_size < 2) {
fprintf(stderr, "Matrix size should be > 2\n");
return -1;
}
// initialize random number generator
std::srand(std::time(nullptr));
int i, rows = mat_size, columns = mat_size;
std::valarray<std::valarray<double>> A(rows);
for (int i = 0; i < rows; i++) {
A[i] = std::valarray<double>(columns);
}
/* create a random matrix */
create_matrix(&A);
std::cout << A << "\n";
clock_t t1 = clock();
std::valarray<double> eigen_vals = qr_algorithm::eigen_values(&A);
double dtime = static_cast<double>(clock() - t1) / CLOCKS_PER_SEC;
std::cout << "Eigen vals: ";
for (i = 0; i < mat_size; i++) std::cout << eigen_vals[i] << "\t";
std::cout << "\nTime taken to compute: " << dtime << " sec\n";
return 0;
}