├── .gitignore ├── LICENSE ├── TestCode ├── qbPCATestData_Reduced.csv ├── qbPCATestData.csv ├── qbMatrix_DeterminantTest.cpp ├── TestCode_qbPCA.cpp ├── TestCode_qbLSQ.cpp ├── TestCode_qbQR.cpp ├── TestCode_qbLinearSolve.cpp ├── TestCode_qbEIG.cpp └── qbMatrixTest.cpp ├── qbLSQ.h ├── README.md ├── qbQR.h ├── qbPCA.h ├── qbLinSolve.h ├── qbEIG.h ├── qbVector2.hpp ├── qbVector3.hpp ├── qbVector4.hpp ├── qbVector.h ├── qbMatrix44.hpp ├── qbMatrix33.hpp └── qbMatrix.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # Un-ignore directories 5 | !*/ 6 | 7 | # Un-ignore files with extensions 8 | !*.* 9 | 10 | # Specifically un-ignore 'makefile' 11 | !makefile 12 | 13 | # Ignore image files 14 | *.bmp 15 | *.png 16 | 17 | # Ignore wav files 18 | *.wav 19 | 20 | # Ignore object files 21 | *.o 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Michael Bennett [QuantitativeBytes] 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 | -------------------------------------------------------------------------------- /TestCode/qbPCATestData_Reduced.csv: -------------------------------------------------------------------------------- 1 | 2.52451,-0.013381 2 | -1.7212,0.300613 3 | -0.748765,-0.0431768 4 | -1.12295,-0.98722 5 | 1.72854,-0.385499 6 | -0.0646006,0.579347 7 | 0.39754,-0.953347 8 | -0.616495,0.163284 9 | 1.27735,0.246007 10 | 0.117859,-0.471822 11 | -1.18359,-0.498095 12 | -2.88914,-0.0826765 13 | -0.685375,0.511966 14 | -0.633715,-0.205851 15 | -0.981423,-0.489185 16 | 0.538805,-0.199638 17 | -1.53664,-0.280918 18 | -0.184751,-1.2773 19 | -1.27849,0.456859 20 | 0.549949,0.475191 21 | 1.92119,-0.558645 22 | -0.307575,-0.440838 23 | -0.482957,0.897286 24 | 0.607222,-0.433187 25 | -0.414768,0.345066 26 | 1.12566,0.0171641 27 | 1.02697,-0.672044 28 | 0.737692,-0.00275168 29 | 0.839534,-0.739853 30 | 1.63196,-0.483153 31 | 0.471967,0.660933 32 | 1.07323,-0.364387 33 | -2.21933,-0.237557 34 | 0.00763931,-0.985115 35 | 0.841239,0.617801 36 | 0.492834,-0.377638 37 | -0.0652358,0.869205 38 | -0.503265,0.61177 39 | -1.19374,-0.173109 40 | 1.98499,-0.0423251 41 | -0.756815,1.10063 42 | -1.3023,0.449003 43 | 0.443712,0.957948 44 | -0.543795,-0.686398 45 | -1.61491,-0.0114933 46 | 0.283277,0.384347 47 | 0.0369785,1.03823 48 | -1.25563,-0.924013 49 | -0.509423,-1.02562 50 | -2.69586,-0.0565089 51 | -2.58495,-0.163867 52 | 0.814238,-0.334622 53 | 0.0998189,-0.888966 54 | 1.52276,-0.643275 55 | -1.59491,-0.175252 56 | -0.363983,-0.294105 57 | -0.223643,0.808687 58 | -1.04126,-0.656784 59 | 0.260261,-1.11736 60 | 0.725554,1.03356 61 | 1.23949,-0.70435 62 | 0.441482,-0.786469 63 | -1.31883,-0.139816 64 | -0.0935125,-0.397793 65 | 1.08774,-0.215482 66 | -0.205631,-0.886396 67 | -1.28515,0.0222961 68 | 0.574709,0.36266 69 | -0.54488,-0.933853 70 | 0.807837,0.69321 71 | -0.677669,0.149229 72 | -1.03887,-0.377107 73 | 2.01219,-0.273605 74 | -0.636412,0.174786 75 | 1.13097,-0.161591 76 | 1.9169,0.323541 77 | -0.206266,-0.0520294 78 | -1.18408,0.644262 79 | 1.01667,-0.720719 80 | 1.57433,0.560279 81 | 0.190048,-0.206173 82 | 0.393781,1.11595 83 | 0.0873,-0.497221 84 | 1.66522,0.130875 85 | -0.316912,-0.633128 86 | -2.56611,-0.0920851 87 | -0.0269461,0.747025 88 | -2.05937,-0.424039 89 | -1.25551,0.765286 90 | -0.58654,0.530156 91 | -0.358957,1.08607 92 | 0.433276,0.0561443 93 | 0.273948,0.286227 94 | 1.63402,0.335865 95 | 0.38005,0.358028 96 | -2.30913,0.269175 97 | 2.09505,-0.0837587 98 | 1.21209,0.360768 99 | -0.484901,-0.575595 100 | -0.91279,-0.371491 101 | -------------------------------------------------------------------------------- /TestCode/qbPCATestData.csv: -------------------------------------------------------------------------------- 1 | 0.755189,0.653001,2.32286 2 | -0.71282,-0.231231,-1.58135 3 | -0.469787,-0.492287,-0.499375 4 | 0.519489,-0.847553,-1.1651 5 | 0.838213,0.233165,1.54256 6 | -0.502107,0.323181,0.00654453 7 | 0.645407,-0.708985,0.439726 8 | -0.217682,0.0296491,-0.617505 9 | 0.316519,0.616752,1.10486 10 | 0.618105,-0.0656082,-0.0751438 11 | 0.125802,-0.545865,-1.18709 12 | -0.896763,-0.913365,-2.59144 13 | -0.343721,0.392102,-0.767686 14 | -0.0937631,-0.361455,-0.553169 15 | -0.130832,-0.781145,-0.788785 16 | 0.129717,-0.161966,0.60705 17 | -0.376359,-0.717028,-1.339 18 | 0.832856,-0.967053,-0.194147 19 | -0.510334,0.168611,-1.29425 20 | 0.217511,0.852103,0.251676 21 | 0.876962,0.0276296,1.81126 22 | -0.177047,-0.770699,-0.023011 23 | -0.907594,0.384069,-0.330732 24 | 0.25355,-0.367694,0.705699 25 | -0.401014,0.10249,-0.348682 26 | 0.820276,0.782572,0.694559 27 | 0.55197,-0.421265,1.08167 28 | -0.039848,-0.0551557,0.856381 29 | 0.886347,-0.193622,0.673612 30 | 0.858214,0.119236,1.46505 31 | -0.129989,0.779519,0.317042 32 | 0.435182,-0.108717,1.07309 33 | -0.436815,-0.70218,-2.0836 34 | 0.704659,-0.689368,-0.023865 35 | -0.0922029,0.749176,0.725298 36 | 0.788976,0.219823,0.185868 37 | -0.526973,0.699387,-0.109395 38 | -0.336628,0.533179,-0.613692 39 | -0.208236,-0.414829,-1.11832 40 | 0.734013,0.602822,1.74506 41 | -0.829143,0.744115,-0.784477 42 | -0.968536,-0.277208,-1.00407 43 | -0.698512,0.649479,0.538586 44 | 0.360751,-0.594044,-0.543827 45 | -0.59928,-0.555794,-1.39732 46 | -0.363628,0.179432,0.391218 47 | -0.622191,0.840184,-0.00668309 48 | 0.312346,-0.954782,-1.20119 49 | 0.676052,-0.762972,-0.5661 50 | -0.989841,-0.969255,-2.32282 51 | -0.830795,-0.968234,-2.25813 52 | 0.956335,0.432315,0.412505 53 | 0.879993,-0.391812,-0.0841693 54 | 0.88936,-0.0700387,1.39425 55 | -0.477903,-0.666704,-1.38316 56 | 0.251243,-0.160275,-0.446111 57 | -0.721932,0.433544,-0.126251 58 | 0.193662,-0.702876,-1.00136 59 | 0.622626,-0.954355,0.376061 60 | -0.612061,0.830771,0.761278 61 | 0.712661,-0.317015,1.22503 62 | 0.726867,-0.402191,0.357211 63 | -0.230125,-0.387417,-1.25892 64 | 0.511257,-0.0611623,-0.273067 65 | 0.70609,0.353851,0.836207 66 | 0.417937,-0.82176,-0.112187 67 | -0.208997,-0.144342,-1.30953 68 | 0.303821,0.778446,0.27178 69 | 0.624185,-0.685024,-0.612291 70 | -0.345792,0.612826,0.826812 71 | -0.717304,-0.463396,-0.338111 72 | 0.117554,-0.389462,-1.07423 73 | 0.942716,0.481627,1.73839 74 | -0.674684,-0.388397,-0.332612 75 | 0.473128,0.206019,1.01943 76 | 0.418562,0.808971,1.71745 77 | -0.162092,-0.222027,-0.0969115 78 | -0.660844,0.282358,-1.17087 79 | 0.906745,-0.151085,0.849721 80 | -0.0520252,0.694601,1.54678 81 | 0.11003,-0.183308,0.232099 82 | -0.842296,0.731703,0.50879 83 | 0.161441,-0.53404,0.214408 84 | 0.79064,0.901391,1.26847 85 | 0.521157,-0.372285,-0.42319 86 | -0.837515,-0.87595,-2.26514 87 | -0.21524,0.826386,-0.223846 88 | -0.2495,-0.784334,-1.94709 89 | -0.934731,0.190335,-1.11896 90 | -0.860145,-0.0753583,-0.311943 91 | -0.633706,0.90247,-0.464973 92 | 0.170027,0.231091,0.344372 93 | -0.333835,0.0725703,0.405138 94 | 0.31712,0.734747,1.46373 95 | 0.234943,0.710164,0.102534 96 | -0.952963,-0.492043,-2.0626 97 | 0.722552,0.532765,1.89532 98 | 0.546093,0.994402,0.822285 99 | 0.347255,-0.455106,-0.519051 100 | 0.0422592,-0.455572,-0.88381 101 | -------------------------------------------------------------------------------- /qbLSQ.h: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | /* 3 | MIT License 4 | Copyright (c) 2021 Michael Bennett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell 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 copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef QBLSQ_H 23 | #define QBLSQ_H 24 | 25 | /* ************************************************************************************************* 26 | 27 | qbLSQ 28 | 29 | Function to solve a system of linear equations using a least squares approach to handle systems 30 | where there are more equations (observations) than unknowns. Assumes that the system is in the 31 | form of y = X*beta. 32 | 33 | *** INPUTS *** 34 | 35 | Xin qbMatrix2 The matrix of independent variables (X in the above equation). 36 | yin qbVector The vector of dependent variables (y in the above equation). 37 | result qbVector The vector of unknown parameters (beta in the above equation). 38 | The final solution is returned in this vector. 39 | 40 | *** OUTPUTS *** 41 | 42 | INT Flag indicating success or failure of the process. 43 | 1 Indicates success. 44 | -1 indicates failure due to there being no computable inverse. 45 | 46 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 47 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 48 | YouTube channel at: 49 | 50 | www.youtube.com/c/QuantitativeBytes 51 | 52 | ************************************************************************************************* */ 53 | 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include "qbVector.h" 60 | #include "qbMatrix.h" 61 | 62 | // Define error codes. 63 | constexpr int QBLSQ_NOINVERSE = -1; 64 | 65 | // The qbLSQ function. 66 | template 67 | int qbLSQ(const qbMatrix2 &Xin, const qbVector &yin, qbVector &result) 68 | { 69 | // Firstly, make a copy of X and y. 70 | qbMatrix2 X = Xin; 71 | qbVector y = yin; 72 | 73 | // Compute the tranpose of X. 74 | qbMatrix2 XT = X.Transpose(); 75 | 76 | // Compute XTX. 77 | qbMatrix2 XTX = XT * X; 78 | 79 | // Compute the inverse of this. 80 | if (!XTX.Inverse()) 81 | { 82 | // We were unable to compute the inverse. 83 | return QBLSQ_NOINVERSE; 84 | } 85 | 86 | // Multiply the inverse by XT. 87 | qbMatrix2 XTXXT = XTX * XT; 88 | 89 | // And multiply by y to get the final result. 90 | result = XTXXT * y; 91 | 92 | return 1; 93 | } 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qbLinAlg 2 | Linear algebra library for C++, created to accompany the corresponding series of videos on the QuantitativeBytes YouTube channel at: 3 | 4 | www.youtube.com/c/QuantitativeBytes. 5 | 6 | Many excellent linear algebra libraries already exist for C++ and as such this code is intended to be primarily for educational purposes, rather than as an alternative to those. It is intended to be studied alongside the corresponding series of videos on the QuantitativeBytes YouTube channel, the full playlist for which can be found here: 7 | 8 | https://www.youtube.com/playlist?list=PL3WoIG-PLjSv9vFx2dg0BqzDZH_6qzF8- 9 | 10 | As this code is paired with the corresponding videos on the QuantitativeBytes YouTube channel, pull requests will not be accepted. 11 | 12 | ## Changes 13 | New versions of the matrix and vector classes, qbVector2, qbVector3, qbVector4, qbMatrix33 and qbMatrix44 have been implemented for handling 2-element, 3-element and 4-element vectors and 3x3 and 4x4 matrices. These avoid the use of heap memory and are therefore faster than the generic qbVector and qbMatrix2 classes in cases where a fixed size is useful. This was implemented to support the qbRay ray tracer project and more details can be found over on the QuantitativeBytes YouTube channel here: 14 | 15 | https://youtu.be/EQlXfCIBpdg 16 | https://youtu.be/sr1OA7NPwcI 17 | 18 | ## Specific functions 19 | 20 | ### qbPCA.h 21 | 22 | Implementation of Principal Component Analysis (PCA). 23 | 24 | https://youtu.be/ifxUSa5r_Ls 25 | 26 | ### qbQR.h 27 | 28 | Function to perform QR decomposition on the given matrix, returning an orthogonal matrix, Q, and an upper-triangular matrix, R. Uses the method of Householder reflections to perform the decomposition. 29 | 30 | https://youtu.be/MR54VHqhROw 31 | 32 | ### qbEIG.h 33 | 34 | Functions for computing the eigenvectors and eigenvalues for a given matrix. Contains an implementation of the power iteration method for computing the dominant eigenvector, the inverse-power-iteration method and an implementation of the QR algorithm to estimate eigenvalue / eigenvector pairs for a given symmetric matrix. 35 | 36 | https://youtu.be/hnLyWa2_hd8 37 | 38 | https://youtu.be/tYqOrvUOMFc 39 | 40 | ### qbLinSolve.h 41 | 42 | Function for solving systems of linear equations. Uses an implementation of Gaussian elimination and back-substitution. 43 | 44 | https://youtu.be/GKkUU4T6o08 45 | 46 | https://youtu.be/NJIv0xH-S0I 47 | 48 | https://youtu.be/KsrlAnEmRNE 49 | 50 | ### qbLSQ.h 51 | 52 | Function for computing the linear least squares solution to an over-determined system of linear equations. 53 | 54 | https://youtu.be/4UVPXs3vIHk 55 | 56 | https://youtu.be/fG1JXf7WSQw 57 | 58 | ### qbMatrix.h 59 | 60 | Class for handling matrices. Implements a number of useful functions: 61 | 62 | #### qbMatrix - Inverse() 63 | 64 | Compute the inverse of the matrix using the Gauss-Jordan elimination method. 65 | 66 | https://youtu.be/wOlG_fnd3v8 67 | 68 | https://youtu.be/AEuNHdgn-R8 69 | 70 | https://youtu.be/JWM8Y8b1ZVQ 71 | 72 | #### qbMatrix - RowEchelon() 73 | 74 | Convert the matrix to row echelon form. 75 | 76 | #### qbMatrix - Transpose() 77 | 78 | Transpose the matrix. 79 | 80 | #### qbMatrix - Determinant() 81 | 82 | Compute the determinant of the matrix. 83 | 84 | https://youtu.be/YVk0nYrwBb0 85 | 86 | ### qbVector.h 87 | 88 | Class for handling vectors. Implements a number of useful functions: 89 | 90 | https://youtu.be/YfWX-EsvX7c 91 | 92 | https://youtu.be/c5AB5T7LBCI 93 | 94 | #### qbVector - Normalized() 95 | 96 | Returns a normalized copy of the vector. 97 | 98 | #### qbVector - Normalize() 99 | 100 | Normalizes the vector 'in-place'. 101 | 102 | #### qbVector - dot() 103 | 104 | Computes the vector dot product. 105 | 106 | #### qbVector - cross() 107 | 108 | Computes the vector cross product. 109 | -------------------------------------------------------------------------------- /TestCode/qbMatrix_DeterminantTest.cpp: -------------------------------------------------------------------------------- 1 | /* ************************************************************************************************* 2 | 3 | qbMatrix_DeterminantTest 4 | 5 | Code to test computation of the determinant using the qbMatrix2 class. 6 | 7 | *** INPUTS *** 8 | 9 | None 10 | 11 | *** OUTPUTS *** 12 | 13 | INT Flag indicating success or failure of the process. 14 | 15 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 16 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 17 | YouTube channel at: 18 | 19 | www.youtube.com/c/QuantitativeBytes 20 | 21 | ************************************************************************************************* */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../qbMatrix.h" 33 | 34 | using namespace std; 35 | 36 | // A simple function to print a matrix to stdout. 37 | template 38 | void PrintMatrix(qbMatrix2 matrix) 39 | { 40 | int nRows = matrix.GetNumRows(); 41 | int nCols = matrix.GetNumCols(); 42 | for (int row = 0; row testMatrix(3, 3, testData); 61 | PrintMatrix(testMatrix); 62 | cout << endl; 63 | 64 | cout << "Extract sub-matrix for element (0,0)" << endl; 65 | qbMatrix2 minor1 = testMatrix.FindSubMatrix(0,0); 66 | PrintMatrix(minor1); 67 | cout << endl; 68 | 69 | cout << "Extract sub-matrix for element (0,1)" << endl; 70 | qbMatrix2 minor2 = testMatrix.FindSubMatrix(0,1); 71 | PrintMatrix(minor2); 72 | cout << endl; 73 | 74 | cout << "Extract sub-matrix for element (0,2)" << endl; 75 | qbMatrix2 minor3 = testMatrix.FindSubMatrix(0,2); 76 | PrintMatrix(minor3); 77 | cout << endl; 78 | 79 | cout << "Extract sub-matrix for element (1,1)" << endl; 80 | qbMatrix2 minor4 = testMatrix.FindSubMatrix(1,1); 81 | PrintMatrix(minor4); 82 | cout << endl; 83 | 84 | cout << "Test with a larger matrix." << endl; 85 | double testData2[25] = 86 | {2.0, 3.0, 4.0, 5.0, 6.0, 87 | 1.0, 2.0, 3.0, 4.0, 5.0, 88 | 9.0, 5.0, 3.0, 2.0, 6.0, 89 | 2.0, 4.0, 6.0, 5.0, 1.0, 90 | 1.0, 7.0, 5.0, 2.0, 3.0}; 91 | qbMatrix2 testMatrix2(5, 5, testData2); 92 | PrintMatrix(testMatrix2); 93 | cout << endl; 94 | 95 | cout << "Extract sub-matrix for element (0,0)" << endl; 96 | qbMatrix2 minor5 = testMatrix2.FindSubMatrix(0,0); 97 | PrintMatrix(minor5); 98 | cout << endl; 99 | 100 | cout << "Extract sub-matrix for element (0,1)" << endl; 101 | qbMatrix2 minor6 = testMatrix2.FindSubMatrix(0,1); 102 | PrintMatrix(minor6); 103 | cout << endl; 104 | 105 | cout << "Extract sub-matrix for element (0,2)" << endl; 106 | qbMatrix2 minor7 = testMatrix2.FindSubMatrix(0,2); 107 | PrintMatrix(minor7); 108 | cout << endl; 109 | 110 | cout << "Extract sub-matrix for element (1,1)" << endl; 111 | qbMatrix2 minor8 = testMatrix2.FindSubMatrix(1,1); 112 | PrintMatrix(minor8); 113 | cout << endl; 114 | 115 | cout << "Test determinant of 3x3 matrix:" << endl; 116 | cout << testMatrix.Determinant() << endl; 117 | cout << endl; 118 | 119 | cout << "Test determinant of 5x5 matrix:" << endl; 120 | cout << testMatrix2.Determinant() << endl; 121 | cout << endl; 122 | 123 | cout << "Test determinant of a singular matrix:" << endl; 124 | double testData3[9] = 125 | {1.0, 1.0, 1.0, 126 | 0.0, 1.0, 0.0, 127 | 1.0, 0.0, 1.0}; 128 | qbMatrix2 testMatrix3(3, 3, testData3); 129 | PrintMatrix(testMatrix3); 130 | cout << endl; 131 | cout << "Determinant = " << testMatrix3.Determinant() << endl; 132 | cout << endl; 133 | 134 | return 0; 135 | } 136 | -------------------------------------------------------------------------------- /TestCode/TestCode_qbPCA.cpp: -------------------------------------------------------------------------------- 1 | /* ************************************************************************************************* 2 | 3 | TestCode_qbPCA 4 | 5 | Code to test the principal component analysis functions. 6 | 7 | *** INPUTS *** 8 | 9 | None 10 | 11 | *** OUTPUTS *** 12 | 13 | INT Flag indicating success or failure of the process. 14 | 15 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 16 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 17 | YouTube channel at: 18 | 19 | www.youtube.com/c/QuantitativeBytes 20 | 21 | ************************************************************************************************* */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "../qbMatrix.h" 34 | #include "../qbVector.h" 35 | #include "../qbPCA.h" 36 | 37 | using namespace std; 38 | 39 | int main() 40 | { 41 | cout << "**********************************************" << endl; 42 | cout << "Testing principal component analysis code." << endl; 43 | cout << "**********************************************" << endl; 44 | cout << endl; 45 | 46 | { 47 | cout << "Testing with 100 observations of 3 variables:" << endl; 48 | 49 | // Read data from the .CSV file. 50 | string rowData; 51 | string number; 52 | stringstream rowDataStream; 53 | std::vector testData; 54 | int numRows = 0; 55 | int numCols = 0; 56 | ifstream inputFile("qbPCATestData.csv"); 57 | 58 | /* If the file open successfully then do stuff. */ 59 | if (inputFile.is_open()) 60 | { 61 | cout << "Opened file successfully..." << endl; 62 | 63 | while (!inputFile.eof()) 64 | { 65 | 66 | // Read the next line. 67 | getline(inputFile, rowData); 68 | 69 | // Loop through and extract the individual numbers. 70 | rowDataStream.clear(); 71 | rowDataStream.str(rowData); 72 | 73 | if (numRows < 1) 74 | numCols = 0; 75 | 76 | while (rowDataStream.good()) 77 | { 78 | getline(rowDataStream, number, ','); 79 | testData.push_back(atof(number.c_str())); 80 | 81 | if (numRows < 1) 82 | numCols++; 83 | } 84 | numRows++; 85 | } 86 | 87 | // Close the file. 88 | inputFile.close(); 89 | 90 | // For some reason, the above reads one line too many. 91 | numRows--; 92 | testData.pop_back(); 93 | 94 | cout << "Completed reading file..." << endl; 95 | cout << "Read " << numRows << " observations of " << numCols << " variables." << endl; 96 | cout << "Constituting " << testData.size() << " elements in total." << endl; 97 | 98 | // Form into a matrix. 99 | qbMatrix2 X (numRows, numCols, testData); 100 | 101 | // Compute the covariance matrix. 102 | std::vector columnMeans = qbPCA::ComputeColumnMeans(X); 103 | qbMatrix2 X2 = X; 104 | qbPCA::SubtractColumnMeans(X2, columnMeans); 105 | 106 | qbMatrix2 covX = qbPCA::ComputeCovariance(X2); 107 | cout << endl; 108 | cout << "Giving the covariance matrix as: " << endl; 109 | covX.PrintMatrix(); 110 | 111 | // Compute the eigenvectors. 112 | qbMatrix2 eigenvectors; 113 | int testResult = qbPCA::ComputeEigenvectors(covX, eigenvectors); 114 | cout << endl; 115 | cout << "And the eigenvectors as: " << endl; 116 | eigenvectors.PrintMatrix(); 117 | 118 | // Test the overall function. 119 | cout << endl; 120 | cout << "Testing overall function..." << endl; 121 | qbMatrix2 eigenvectors2; 122 | int testResult2 = qbPCA::qbPCA(X, eigenvectors2); 123 | cout << "testResult2 = " << testResult2 << endl; 124 | cout << "And the final eigenvectors are:" << endl; 125 | eigenvectors2.PrintMatrix(); 126 | 127 | // Test dimensionality reduction. 128 | cout << endl; 129 | cout << "Testing dimensionality reduction." << endl; 130 | cout << "Starting with X which has " << X.GetNumRows() << " rows and " << X.GetNumCols() << " columns." << endl; 131 | cout << endl; 132 | cout << "Using only the first two principal components:" << endl; 133 | qbMatrix2 V, part2; 134 | eigenvectors.Separate(V, part2, 2); 135 | V.PrintMatrix(8); 136 | cout << endl; 137 | 138 | qbMatrix2 newX = (V.Transpose() * X.Transpose()).Transpose(); 139 | cout << "Result has " << newX.GetNumRows() << " rows and " << newX.GetNumCols() << " columns." << endl; 140 | 141 | // Open a file for writing 142 | ofstream outputFile("qbPCATestData_Reduced.csv"); 143 | if (outputFile.is_open()) 144 | { 145 | for (int i=0; i 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "../qbMatrix.h" 35 | #include "../qbVector.h" 36 | #include "../qbLSQ.h" 37 | 38 | using namespace std; 39 | 40 | // A simple function to print a matrix to stdout. 41 | template 42 | void PrintMatrix(qbMatrix2 matrix) 43 | { 44 | int nRows = matrix.GetNumRows(); 45 | int nCols = matrix.GetNumCols(); 46 | for (int row = 0; row 58 | void PrintVector(qbVector inputVector) 59 | { 60 | int nRows = inputVector.GetNumDims(); 61 | for (int row = 0; row simpleData = {1.0, 3.0, -1.0, 13.0, 4.0, -1.0, 1.0, 9.0, 2.0, 4.0, 3.0, -6.0}; 76 | qbMatrix2 testMatrix(3, 4, simpleData); 77 | 78 | cout << "Original matrix:" << endl; 79 | PrintMatrix(testMatrix); 80 | cout << endl; 81 | 82 | qbMatrix2 testMatrixT = testMatrix.Transpose(); 83 | cout << "The transposed result:" << endl; 84 | PrintMatrix(testMatrixT); 85 | 86 | cout << endl; 87 | cout << "Verify the dimensions." << endl; 88 | cout << "Original (rows,cols) = (" << testMatrix.GetNumRows() << ", " << testMatrix.GetNumCols() << ")." << endl; 89 | cout << "Transposed (rows,cols) = (" << testMatrixT.GetNumRows() << ", " << testMatrixT.GetNumCols() << ")." << endl; 90 | } 91 | 92 | cout << endl; 93 | cout << "**********************************************************************" << endl; 94 | cout << endl; 95 | cout << "Testing linear least squares." << endl; 96 | 97 | // Linear least squares - Test 1. 98 | { 99 | // Define our X matrix. 100 | std::vector Xdata = {1.0, 1.0, 1.0, 2.0, 1.0, 3.0}; 101 | qbMatrix2 X(3, 2, Xdata); 102 | 103 | // Define our y vector. 104 | std::vector Ydata = {2.0, 4.0, 4.0}; 105 | qbVector y(Ydata); 106 | 107 | cout << "Testing with X = " << endl; 108 | PrintMatrix(X); 109 | cout << endl; 110 | cout << "And y = " << endl; 111 | PrintVector(y); 112 | cout << endl; 113 | 114 | // Compute the parameters of best fit. 115 | qbVector result; 116 | int test = qbLSQ(X, y, result); 117 | 118 | cout << "Giving beta-hat = " << endl; 119 | PrintVector(result); 120 | cout << endl; 121 | } 122 | 123 | // Linear least squares - Test 2. 124 | cout << "***************************************" << endl; 125 | cout << "Test with a larger number of equations." << endl; 126 | { 127 | // Setup a random number generator. 128 | std::random_device myRandomDevice; 129 | std::mt19937 myRandomGenerator(myRandomDevice()); 130 | std::uniform_real_distribution myDistribution(-1.0, 1.0); 131 | 132 | // Setup the test data. 133 | int numPoints = 100; 134 | double m = 1.5; 135 | double c = 0.5; 136 | double xMax = 10.0; 137 | double xStep = xMax / static_cast(numPoints); 138 | 139 | qbMatrix2 X(numPoints, 2); 140 | qbVector y(numPoints); 141 | int count = 0; 142 | for (double x=0.0; x betaHat; 170 | int test = qbLSQ(X, y, betaHat); 171 | 172 | cout << "Giving beta-hat = " << endl; 173 | PrintVector(betaHat); 174 | cout << endl; 175 | 176 | } 177 | 178 | return 0; 179 | } 180 | -------------------------------------------------------------------------------- /qbQR.h: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | /* 3 | MIT License 4 | Copyright (c) 2021 Michael Bennett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell 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 copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef QBQR_H 23 | #define QBQR_H 24 | 25 | /* ************************************************************************************************* 26 | 27 | qbQR 28 | 29 | Function to perform QR decomposition on a given input matrix. 30 | 31 | *** INPUTS *** 32 | 33 | A qbMatrix2 The matrix on which to perform QR decomposition. 34 | Q qbMatrix2 The output Q matrix. 35 | R qbMatrix2 The output R matrix. 36 | 37 | *** OUTPUTS *** 38 | 39 | INT Flag indicating success or failure of the process. 40 | 1 Indicates success. 41 | -1 indicates failure due to a non-square input matrix. 42 | 43 | Uses an implementation of Householder reflections to perform QR decomposition. 44 | 45 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 46 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 47 | YouTube channel at: 48 | 49 | www.youtube.com/c/QuantitativeBytes 50 | 51 | ************************************************************************************************* */ 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | #include "qbMatrix.h" 60 | #include "qbVector.h" 61 | 62 | // Define error codes. 63 | constexpr int QBQR_MATRIXNOTSQUARE = -1; 64 | 65 | // The qbQR function. 66 | template 67 | int qbQR(const qbMatrix2 &A, qbMatrix2 &Q, qbMatrix2 &R) 68 | { 69 | 70 | // Make a copy of the input matrix. 71 | qbMatrix2 inputMatrix = A; 72 | 73 | // Verify that the input matrix is square. 74 | if (!inputMatrix.IsSquare()) 75 | return QBQR_MATRIXNOTSQUARE; 76 | 77 | // Determine the number of columns (and rows, since the matrix is square). 78 | int numCols = inputMatrix.GetNumCols(); 79 | 80 | // Create a vector to store the P matrices for each column. 81 | std::vector> Plist; 82 | 83 | // Loop through each column. 84 | for (int j=0; j<(numCols-1); ++j) 85 | { 86 | // Create the a1 and b1 vectors. 87 | // a1 is the column vector from A. 88 | // b1 is the vector onto which we wish to reflect a1. 89 | qbVector a1 (numCols-j); 90 | qbVector b1 (numCols-j); 91 | for (int i=j; i(0.0)); 95 | } 96 | b1.SetElement(0, static_cast(1.0)); 97 | 98 | // Compute the norm of the a1 vector. 99 | T a1norm = a1.norm(); 100 | 101 | // Compute the sign we will use. 102 | int sgn = -1; 103 | if (a1.GetElement(0) < static_cast(0.0)) 104 | sgn = 1; 105 | 106 | // Compute the u-vector. 107 | qbVector u = a1 - (sgn * a1norm * b1); 108 | 109 | // Compute the n-vector. 110 | qbVector n = u.Normalized(); 111 | 112 | // Convert n to a matrix so that we can transpose it. 113 | qbMatrix2 nMat (numCols-j, 1); 114 | for (int i=0; i<(numCols-j); ++i) 115 | nMat.SetElement(i, 0, n.GetElement(i)); 116 | 117 | // Transpose nMat. 118 | qbMatrix2 nMatT = nMat.Transpose(); 119 | 120 | // Create an identity matrix of the appropriate size. 121 | qbMatrix2 I (numCols-j, numCols-j); 122 | I.SetToIdentity(); 123 | 124 | // Compute Ptemp. 125 | qbMatrix2 Ptemp = I - static_cast(2.0) * nMat * nMatT; 126 | 127 | // Form the P matrix with the original dimensions. 128 | qbMatrix2 P (numCols, numCols); 129 | P.SetToIdentity(); 130 | for (int row=j; row Qmat = Plist.at(0); 148 | for (int i=1; i<(numCols-1); ++i) 149 | { 150 | Qmat = Qmat * Plist.at(i).Transpose(); 151 | } 152 | 153 | // Return the Q matrix. 154 | Q = Qmat; 155 | 156 | // Compute R. 157 | int numElements = Plist.size(); 158 | qbMatrix2 Rmat = Plist.at(numElements-1); 159 | for (int i=(numElements-2); i>=0; --i) 160 | { 161 | Rmat = Rmat * Plist.at(i); 162 | } 163 | Rmat = Rmat * A; 164 | 165 | // And return the R matrix. 166 | R = Rmat; 167 | 168 | } 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /qbPCA.h: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | /* 3 | MIT License 4 | Copyright (c) 2021 Michael Bennett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell 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 copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef QBPCA_H 23 | #define QBPCA_H 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include "qbMatrix.h" 33 | #include "qbVector.h" 34 | #include "qbEIG.h" 35 | 36 | // Define error codes. 37 | constexpr int QBPCA_MATRIXNOTSQUARE = -1; 38 | constexpr int QBPCA_MATRIXNOTSYMMETRIC = -2; 39 | 40 | namespace qbPCA 41 | { 42 | 43 | // Function to compute the column means. 44 | template 45 | std::vector ComputeColumnMeans(const qbMatrix2 &inputData) 46 | { 47 | // Determine the size of the input data. 48 | int numRows = inputData.GetNumRows(); 49 | int numCols = inputData.GetNumCols(); 50 | 51 | // Create a vector for output. 52 | std::vector output; 53 | 54 | // Loop through and compute means. 55 | for (int j=0; j(0.0); 58 | for (int i=0; i(numRows)); 62 | } 63 | 64 | return output; 65 | } 66 | 67 | // Function to subtract the column means. 68 | template 69 | void SubtractColumnMeans(qbMatrix2 &inputData, std::vector &columnMeans) 70 | { 71 | // Determine the size of the input data. 72 | int numRows = inputData.GetNumRows(); 73 | int numCols = inputData.GetNumCols(); 74 | 75 | // Loop through and subtract the means. 76 | for (int j=0; j 85 | qbMatrix2 ComputeCovariance(const qbMatrix2 &X) 86 | { 87 | /* Compute the covariance matrix. 88 | Note that here we use X'X, rather than XX' as is the usual case. 89 | This is because we are requiring our data to be arranged with one 90 | column (p) for each variable, with one row (k) for each observation. If 91 | we computed XX', the result would be a [k x k] matrix. The covariance 92 | matrix should be [p x p], so we need to transpose, hence the use of 93 | X'X. */ 94 | int numRows = X.GetNumRows(); 95 | qbMatrix2 covX = (static_cast(1.0) / static_cast(numRows - 1)) * (X.Transpose() * X); 96 | return covX; 97 | } 98 | 99 | // Function to compute the eigenvectors of the covariance matrix. 100 | template 101 | int ComputeEigenvectors(const qbMatrix2 &covarianceMatrix, qbMatrix2 &eigenvectors) 102 | { 103 | // Copy the input matrix. 104 | qbMatrix2 X = covarianceMatrix; 105 | 106 | // The covariance matrix must be square and symmetric. 107 | if (!X.IsSquare()) 108 | return QBPCA_MATRIXNOTSQUARE; 109 | 110 | // Verify that the matrix is symmetric. 111 | if (!X.IsSymmetric()) 112 | return QBPCA_MATRIXNOTSYMMETRIC; 113 | 114 | // Compute the eignvalues. 115 | std::vector eigenValues; 116 | int returnStatus = qbEigQR(X, eigenValues); 117 | 118 | // Sort the eigenvalues. 119 | std::sort(eigenValues.begin(), eigenValues.end()); 120 | std::reverse(eigenValues.begin(), eigenValues.end()); 121 | 122 | // Compute the eigenvector for each eigenvalue. 123 | qbVector eV(X.GetNumCols()); 124 | qbMatrix2 eVM(X.GetNumRows(), X.GetNumCols()); 125 | for (int j=0; j(X, eig, eV); 129 | for (int i=0; i 142 | int qbPCA(const qbMatrix2 &inputData, qbMatrix2 &outputComponents) 143 | { 144 | // Make a copy of the input matrix. 145 | qbMatrix2 X = inputData; 146 | 147 | // Compute the mean of each column of X. 148 | std::vector columnMeans = ComputeColumnMeans(X); 149 | 150 | // Subtract the column means from the data. 151 | SubtractColumnMeans(X, columnMeans); 152 | 153 | // Compute the covariance matrix. 154 | qbMatrix2 covX = ComputeCovariance(X); 155 | 156 | // Compute the eigenvectors. 157 | qbMatrix2 eigenvectors; 158 | int returnStatus = ComputeEigenvectors(covX, eigenvectors); 159 | 160 | // Return the output. 161 | outputComponents = eigenvectors; 162 | 163 | return returnStatus; 164 | } 165 | 166 | } 167 | 168 | #endif 169 | -------------------------------------------------------------------------------- /qbLinSolve.h: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | /* 3 | MIT License 4 | Copyright (c) 2021 Michael Bennett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell 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 copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef QBLINSOLVE_H 23 | #define QBLINESOLVE_H 24 | 25 | /* ************************************************************************************************* 26 | 27 | qbLinSolve 28 | 29 | Function to solve a system of linear equations in the form of y = X*beta, where we 30 | want to solve for beta. 31 | 32 | *** INPUTS *** 33 | 34 | aMatrix qbMatrix2 The matrix of independent variables (X in the above equation). 35 | bVector qbVector The vector of dependent variables (y in the above equation). 36 | resultVec qbVector The vector of unknown parameters (beta in the above equation). 37 | The final solution is returned in this vector. 38 | 39 | *** OUTPUTS *** 40 | 41 | INT Flag indicating success or failure of the process. 42 | 1 Indicates success. 43 | -1 indicates failure due to there being no unique solution (infinite solutions). 44 | -2 indicates failure due to there being no solution. 45 | 46 | Uses Gaussian elimination on the augmented matrix, followed by back substitution. 47 | 48 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 49 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 50 | YouTube channel at: 51 | 52 | www.youtube.com/c/QuantitativeBytes 53 | 54 | ************************************************************************************************* */ 55 | 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | #include "qbMatrix.h" 63 | #include "qbVector.h" 64 | 65 | // Define error codes. 66 | constexpr int QBLINSOLVE_NOUNIQUESOLUTION = -1; 67 | constexpr int QBLINSOLVE_NOSOLUTIONS = -2; 68 | 69 | // The qbLinSolve function. 70 | template 71 | int qbLinSolve(const qbMatrix2 &aMatrix, const qbVector &bVector, qbVector &resultVec) 72 | { 73 | // Make a copy of the input matrix, aMatrix. 74 | // We will use this to create the augmented matrix, so we have 75 | // to make a copy. 76 | qbMatrix2 inputMatrix = aMatrix; 77 | 78 | // Compute the rank of the original matrix. 79 | int originalRank = inputMatrix.Rank(); 80 | 81 | /* Combine inputMatrix and bVector together into a single matrix, 82 | ready for using Gaussian elimination to reduce to 83 | row-echelon form. */ 84 | 85 | // Extract data from bVector. 86 | int numDims = bVector.GetNumDims(); 87 | std::vector bVecData; 88 | for (int i=0; i bMatrix(numDims, 1, bVecData); 93 | 94 | // Combine the two matrices together. 95 | inputMatrix.Join(bMatrix); 96 | 97 | /* Use Gaussian elmination to convert to row-echelon form. */ 98 | qbMatrix2 rowEchelonMatrix = inputMatrix.RowEchelon(); 99 | 100 | /* Comute the rank of the augmented matrix. 101 | Note that we do this after performing Gaussian elimination to 102 | reduce the matrix to row echelon form so that if this was 103 | successful, there is no need to repeat this operation twice. */ 104 | int augmentedRank = rowEchelonMatrix.Rank(); 105 | 106 | /* ********************************************************************* 107 | Test the two ranks to determine the nature of the system we 108 | are dealing with. The conditions are as follows: 109 | 110 | n = number of rows. 111 | 112 | 1) originalRank = augmentedRank = n => A unique solution exists. 113 | 2) originalRank = augmentedRank < n => An infinite number of solutions exist. 114 | 3) originalRank < augmentedRank => No solutions exist. 115 | ********************************************************************* */ 116 | if ((originalRank == augmentedRank) && (originalRank < inputMatrix.GetNumRows())) 117 | { 118 | return QBLINSOLVE_NOUNIQUESOLUTION; 119 | } 120 | else if (originalRank < augmentedRank) 121 | { 122 | return QBLINSOLVE_NOSOLUTIONS; 123 | } 124 | else 125 | { 126 | /* Create a qbVector object to store the output. Initially we will 127 | populate this with the data from bVecData, but we are going to modify 128 | the elements as we compute them. */ 129 | qbVector output(bVecData); 130 | 131 | // Now use back-substitution to compute the result. 132 | int numRows = rowEchelonMatrix.GetNumRows(); 133 | int numCols = rowEchelonMatrix.GetNumCols(); 134 | int startRow = numRows-1; 135 | 136 | // Loop over the rows, in reverse order. 137 | for (int i=startRow; i>=0; --i) 138 | { 139 | // Extract the currentResult for this row. 140 | T currentResult = rowEchelonMatrix.GetElement(i, numCols-1); 141 | 142 | // Compute the cumulative sum. 143 | T cumulativeSum = static_cast(0.0); 144 | for (int j=i+1; j 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "../qbMatrix.h" 34 | #include "../qbVector.h" 35 | #include "../qbQR.h" 36 | 37 | using namespace std; 38 | 39 | int main() 40 | { 41 | cout << "**********************************************" << endl; 42 | cout << "Testing QR decomposition code." << endl; 43 | cout << "**********************************************" << endl; 44 | cout << endl; 45 | 46 | { 47 | cout << "Testing with simple 3x3 matrix:" << endl; 48 | 49 | std::vector simpleData = {0.5, 0.75, 0.5, 1.0, 0.5, 0.75, 0.25, 0.25, 0.25}; 50 | qbMatrix2 testMatrix(3, 3, simpleData); 51 | 52 | testMatrix.PrintMatrix(); 53 | 54 | cout << endl; 55 | cout << "Computing QR decomposition..." << endl; 56 | qbMatrix2 Q (3,3); 57 | qbMatrix2 R (3,3); 58 | int status = qbQR(testMatrix, Q, R); 59 | cout << "R = " << endl; 60 | R.PrintMatrix(); 61 | cout << endl; 62 | cout << "Q = " << endl; 63 | Q.PrintMatrix(); 64 | cout << endl; 65 | cout << "QR = " << endl; 66 | qbMatrix2 QR = Q*R; 67 | QR.PrintMatrix(); 68 | cout << endl; 69 | } 70 | 71 | { 72 | cout << "Testing with simple 4x4 matrix:" << endl; 73 | std::vector simpleData = {1.0, 5.0, 3.0, 4.0, 7.0, 8.0, 2.0, 9.0, 7.0, 3.0, 2.0, 1.0, 9.0, 3.0, 5.0, 7.0}; 74 | qbMatrix2 testMatrix(4, 4, simpleData); 75 | testMatrix.PrintMatrix(); 76 | cout << endl; 77 | cout << "Computing QR decomposition..." << endl; 78 | qbMatrix2 Q (4,4); 79 | qbMatrix2 R (4,4); 80 | int status = qbQR(testMatrix, Q, R); 81 | cout << "R = " << endl; 82 | R.PrintMatrix(); 83 | cout << endl; 84 | cout << "Q = " << endl; 85 | Q.PrintMatrix(); 86 | cout << endl; 87 | cout << "QR = " << endl; 88 | qbMatrix2 QR = Q*R; 89 | QR.PrintMatrix(); 90 | cout << endl; 91 | } 92 | 93 | { 94 | cout << "Testing with simple 5x5 matrix:" << endl; 95 | std::vector simpleData = {2, 6, 4, 6, 8, 6, 7, 9, 7, 9, 2, 3, 6, 3, 5, 6, 1, 1, 5, 5, 3, 5, 6, 5, 6}; 96 | qbMatrix2 testMatrix(5, 5, simpleData); 97 | testMatrix.PrintMatrix(); 98 | cout << endl; 99 | cout << "Computing QR decomposition..." << endl; 100 | qbMatrix2 Q (5,5); 101 | qbMatrix2 R (5,5); 102 | int status = qbQR(testMatrix, Q, R); 103 | cout << "R = " << endl; 104 | R.PrintMatrix(); 105 | cout << endl; 106 | cout << "Q = " << endl; 107 | Q.PrintMatrix(); 108 | cout << endl; 109 | cout << "QR = " << endl; 110 | qbMatrix2 QR = Q*R; 111 | QR.PrintMatrix(); 112 | cout << endl; 113 | } 114 | 115 | { 116 | cout << "Testing with simple 5x5 float matrix:" << endl; 117 | std::vector simpleData = {8.662634278267483, 2.3440981169711796, 3.414158790068152, 9.819959485632891, 9.812414578216162, 4.8096369839436495, 7.743133259609277, 9.871217856632036, 7.100783013043249, 8.127838524397976, 1.3468248609110365, 1.3120774834063536, 9.607366488550678, 2.852679282078192, 8.087038227451359, 7.556075051454403, 5.80117852857823, 3.550189544341768, 3.7807047754393994, 7.934423413357392, 2.866445996919499, 7.125441061546031, 4.53141730712106, 4.297092147605687, 2.5126585000174146}; 118 | qbMatrix2 testMatrix(5, 5, simpleData); 119 | testMatrix.PrintMatrix(); 120 | cout << endl; 121 | cout << "Computing QR decomposition..." << endl; 122 | qbMatrix2 Q (5,5); 123 | qbMatrix2 R (5,5); 124 | int status = qbQR(testMatrix, Q, R); 125 | cout << "R = " << endl; 126 | R.PrintMatrix(); 127 | cout << endl; 128 | cout << "Q = " << endl; 129 | Q.PrintMatrix(); 130 | cout << endl; 131 | cout << "QR = " << endl; 132 | qbMatrix2 QR = Q*R; 133 | QR.PrintMatrix(); 134 | cout << endl; 135 | } 136 | 137 | { 138 | cout << "Testing with simple 10x10 matrix:" << endl; 139 | std::vector simpleData = {8, 2, 1, 3, 9, 2, 1, 9, 8, 9, 9, 1, 4, 3, 8, 8, 7, 9, 4, 2, 2, 4, 2, 8, 7, 2, 8, 4, 5, 6, 9, 3, 7, 1, 8, 6, 7, 5, 8, 8, 9, 7, 9, 3, 9, 1, 7, 1, 9, 5, 6, 4, 3, 6, 6, 1, 4, 5, 5, 7, 7, 6, 9, 9, 5, 8, 1, 1, 7, 9, 6, 2, 1, 8, 2, 3, 8, 7, 7, 6, 2, 8, 4, 8, 1, 7, 8, 4, 5, 5, 3, 2, 6, 5, 2, 6, 9, 7, 3, 7}; 140 | qbMatrix2 testMatrix(10, 10, simpleData); 141 | testMatrix.PrintMatrix(); 142 | cout << endl; 143 | cout << "Computing QR decomposition..." << endl; 144 | qbMatrix2 Q (10,10); 145 | qbMatrix2 R (10,10); 146 | int status = qbQR(testMatrix, Q, R); 147 | cout << "R = " << endl; 148 | R.PrintMatrix(); 149 | cout << endl; 150 | cout << "Q = " << endl; 151 | Q.PrintMatrix(); 152 | cout << endl; 153 | cout << "QR = " << endl; 154 | qbMatrix2 QR = Q*R; 155 | QR.PrintMatrix(); 156 | cout << endl; 157 | } 158 | 159 | { 160 | cout << "Testing with simple 10x10 matrix:" << endl; 161 | std::vector simpleData = {8, 2, 1, 3, 9, 2, 1, 9, 8, 9, 9, 1, 4, 3, 8, 8, 7, 9, 4, 2, 2, 4, 2, 8, 7, 2, 8, 4, 5, 6, 9, 3, 7, 1, 8, 6, 7, 5, 8, 8, 9, 7, 9, 3, 9, 1, 7, 1, 9, 5, 6, 4, 3, 6, 6, 1, 4, 5, 5, 7, 7, 6, 9, 9, 5, 8, 1, 1, 7, 9, 6, 2, 1, 8, 2, 3, 8, 7, 7, 6, 2, 8, 4, 8, 1, 7, 8, 4, 5, 5, 3, 2, 6, 5, 2, 6, 9, 7, 3, 7}; 162 | qbMatrix2 testMatrix(10, 10, simpleData); 163 | testMatrix.PrintMatrix(); 164 | cout << endl; 165 | cout << "Computing QR decomposition..." << endl; 166 | qbMatrix2 Q (10,10); 167 | qbMatrix2 R (10,10); 168 | int status = qbQR(testMatrix, Q, R); 169 | cout << "R = " << endl; 170 | R.PrintMatrix(); 171 | cout << endl; 172 | cout << "Q = " << endl; 173 | Q.PrintMatrix(); 174 | cout << endl; 175 | cout << "QR = " << endl; 176 | qbMatrix2 QR = Q*R; 177 | QR.PrintMatrix(); 178 | cout << endl; 179 | } 180 | 181 | return 0; 182 | } 183 | -------------------------------------------------------------------------------- /qbEIG.h: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | /* 3 | MIT License 4 | Copyright (c) 2021 Michael Bennett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell 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 copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef QBEIG_H 23 | #define QBEIG_H 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "qbMatrix.h" 32 | #include "qbVector.h" 33 | #include "qbQR.h" 34 | 35 | // Define error codes. 36 | constexpr int QBEIG_MATRIXNOTSQUARE = -1; 37 | constexpr int QBEIG_MAXITERATIONSEXCEEDED = -2; 38 | constexpr int QBEIG_MATRIXNOTSYMMETRIC = -3; 39 | 40 | // Function to estimate (real) eigenvalues using QR decomposition. 41 | /* Note that this is only valid for matrices that have ALL real 42 | eigenvalues. The only matrices that are guaranteed to have only 43 | real eigenvalues are symmetric matrices. Therefore, this function 44 | is only guaranteed to work with symmetric matrices. */ 45 | template 46 | int qbEigQR(const qbMatrix2 &inputMatrix, std::vector &eigenValues) 47 | { 48 | // Make a copy of the input matrix. 49 | qbMatrix2 A = inputMatrix; 50 | 51 | // Verify that the input matrix is square. 52 | if (!A.IsSquare()) 53 | return QBEIG_MATRIXNOTSQUARE; 54 | 55 | // Verify that the matrix is symmetric. 56 | if (!A.IsSymmetric()) 57 | return QBEIG_MATRIXNOTSYMMETRIC; 58 | 59 | // The number of eigenvalues is equal to the number of rows. 60 | int numRows = A.GetNumRows(); 61 | 62 | // Create an identity matrix of the same dimensions. 63 | qbMatrix2 identityMatrix(numRows, numRows); 64 | identityMatrix.SetToIdentity(); 65 | 66 | // Create matrices to store Q and R. 67 | qbMatrix2 Q (numRows, numRows); 68 | qbMatrix2 R (numRows, numRows); 69 | 70 | // Loop through each iteration. 71 | int maxIterations = 10e3; 72 | int iterationCount = 0; 73 | bool continueFlag = true; 74 | while ((iterationCount < maxIterations) && continueFlag) 75 | { 76 | // Compute the QR decomposition of A. 77 | int returnValue = qbQR(A, Q, R); 78 | 79 | // Compute the next value of A as the product of R and Q. 80 | A = R * Q; 81 | 82 | /* Check if A is now close enough to being upper-triangular. 83 | We can do this using the IsRowEchelon() function from the 84 | qbMatrix2 class. */ 85 | if (A.IsRowEchelon()) 86 | continueFlag = false; 87 | 88 | // Increment iterationCount. 89 | iterationCount++; 90 | } 91 | 92 | // At this point, the eigenvalues should be the diagonal elements of A. 93 | for (int i=0; i 106 | int qbInvPIt(const qbMatrix2 &inputMatrix, const T &eigenValue, qbVector &eigenVector) 107 | { 108 | // Make a copy of the input matrix. 109 | qbMatrix2 A = inputMatrix; 110 | 111 | // Verify that the input matrix is square. 112 | if (!A.IsSquare()) 113 | return QBEIG_MATRIXNOTSQUARE; 114 | 115 | // Setup a random number generator. 116 | std::random_device myRandomDevice; 117 | std::mt19937 myRandomGenerator(myRandomDevice()); 118 | std::uniform_int_distribution myDistribution(1.0, 10.0); 119 | 120 | /* The number of eigenvectors and eigenvalues that we will compute will be 121 | equal to the number of rows in the input matrix. */ 122 | int numRows = A.GetNumRows(); 123 | 124 | // Create an identity matrix of the same dimensions. 125 | qbMatrix2 identityMatrix(numRows, numRows); 126 | identityMatrix.SetToIdentity(); 127 | 128 | // Create an initial vector, v. 129 | qbVector v(numRows); 130 | for (int i=0; i(myDistribution(myRandomGenerator))); 132 | 133 | // Iterate. 134 | int maxIterations = 100; 135 | int iterationCount = 0; 136 | T deltaThreshold = static_cast(1e-9); 137 | T delta = static_cast(1e6); 138 | qbVector prevVector(numRows); 139 | qbMatrix2 tempMatrix(numRows, numRows); 140 | 141 | while ((iterationCount < maxIterations) && (delta > deltaThreshold)) 142 | { 143 | // Store a copy of the current working vector to use for computing delta. 144 | prevVector = v; 145 | 146 | // Compute the next value of v. 147 | tempMatrix = A - (eigenValue * identityMatrix); 148 | tempMatrix.Inverse(); 149 | v = tempMatrix * v; 150 | v.Normalize(); 151 | 152 | // Compute delta. 153 | delta = (v - prevVector).norm(); 154 | 155 | // Increment iteration count. 156 | iterationCount++; 157 | } 158 | 159 | // Return the estimated eigenvector. 160 | eigenVector = v; 161 | 162 | // Set the return status accordingly. 163 | if (iterationCount == maxIterations) 164 | return QBEIG_MAXITERATIONSEXCEEDED; 165 | else 166 | return 0; 167 | 168 | } 169 | 170 | // The qbEIG function (power iteration method). 171 | template 172 | int qbEIG_PIt(const qbMatrix2 &X, T &eigenValue, qbVector &eigenVector) 173 | { 174 | // Make a copy of the input matrix. 175 | qbMatrix2 inputMatrix = X; 176 | 177 | // Verify that the input matrix is square. 178 | if (!inputMatrix.IsSquare()) 179 | return QBEIG_MATRIXNOTSQUARE; 180 | 181 | // Setup a random number generator. 182 | std::random_device myRandomDevice; 183 | std::mt19937 myRandomGenerator(myRandomDevice()); 184 | std::uniform_int_distribution myDistribution(1.0, 10.0); 185 | 186 | /* The number of eigenvectors and eigenvalues that we will compute will be 187 | equal to the number of rows in the input matrix. */ 188 | int numRows = inputMatrix.GetNumRows(); 189 | 190 | // Create an identity matrix of the same dimensions. 191 | qbMatrix2 identityMatrix(numRows, numRows); 192 | identityMatrix.SetToIdentity(); 193 | 194 | /* ************************************************************** 195 | Compute the eigenvector. 196 | ************************************************************** */ 197 | 198 | // Create an initial vector, v. 199 | qbVector v(numRows); 200 | for (int i=0; i(myDistribution(myRandomGenerator))); 202 | 203 | // Loop over the required number of iterations. 204 | qbVector v1(numRows); 205 | int numIterations = 1000; 206 | for (int i=0; i(0.0); 222 | for (int i=1; i 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "../qbMatrix.h" 35 | #include "../qbVector.h" 36 | #include "../qbLinSolve.h" 37 | 38 | using namespace std; 39 | 40 | // A simple function to print a matrix to stdout. 41 | template 42 | void PrintMatrix(qbMatrix2 matrix) 43 | { 44 | int nRows = matrix.GetNumRows(); 45 | int nCols = matrix.GetNumCols(); 46 | for (int row = 0; row 58 | void PrintVector(qbVector inputVector) 59 | { 60 | int nRows = inputVector.GetNumDims(); 61 | for (int row = 0; row simpleData = {1.0, 3.0, -1.0, 13.0, 4.0, -1.0, 1.0, 9.0, 2.0, 4.0, 3.0, -6.0}; 75 | qbMatrix2 testMatrix(3, 4, simpleData); 76 | 77 | cout << "Original matrix:" << endl; 78 | PrintMatrix(testMatrix); 79 | cout << endl; 80 | 81 | // Convert to row echelon form. 82 | qbMatrix2 rowEchelonMatrix = testMatrix.RowEchelon(); 83 | 84 | cout << "Converted to row echelon form:" << endl; 85 | PrintMatrix(rowEchelonMatrix); 86 | cout << endl; 87 | 88 | // Define another matrix as the first part of our system of linear equations. 89 | std::vector simpleData2 = {1.0, 3.0, -1.0, 4.0, -1.0, 1.0, 2.0, 4.0, 3.0}; 90 | qbMatrix2 aMat(3, 3, simpleData2); 91 | cout << "We setup the equations in the form of Ax = b, where A = " << endl; 92 | PrintMatrix(aMat); 93 | cout << endl; 94 | 95 | // Define a vector to hold the RHS of our system of linear equations. 96 | std::vector vectorData {13.0, 9.0, -6.0}; 97 | qbVector bVec {vectorData}; 98 | cout << "And b = " << endl; 99 | PrintVector(bVec); 100 | cout << endl; 101 | 102 | // Call the qbLinSolve function. 103 | qbVector testResult(3); 104 | int test = qbLinSolve(aMat, bVec, testResult); 105 | cout << "And the final result is:" << endl; 106 | PrintVector(testResult); 107 | cout << endl; 108 | 109 | // *************************************************************************************************** 110 | // Try some random tests. 111 | std::random_device myRandomDevice; 112 | std::mt19937 myRandomGenerator(myRandomDevice()); 113 | std::uniform_real_distribution myDistribution(-25.0,25.0); 114 | 115 | int numUnknowns = 10; 116 | 117 | std::vector coefficientData; 118 | std::vector unknownData; 119 | // Populate the coefficient data. 120 | for (int i=0; i<(numUnknowns * numUnknowns); ++i) 121 | { 122 | double randomNumber = myDistribution(myRandomGenerator); 123 | coefficientData.push_back(randomNumber); 124 | } 125 | cout << "A random coefficient matrix = " << endl; 126 | qbMatrix2 coefficientMatrix(numUnknowns, numUnknowns, coefficientData); 127 | PrintMatrix(coefficientMatrix); 128 | cout << endl; 129 | 130 | cout << "And the random unknown values = " << endl; 131 | for (int i=0; i unknownVector {unknownData}; 137 | PrintVector(unknownVector); 138 | cout << endl; 139 | 140 | cout << "Compute the equation results = " << endl; 141 | qbVector systemResult = coefficientMatrix * unknownVector; 142 | PrintVector(systemResult); 143 | cout << endl; 144 | 145 | cout << "Attempt to solve the linear system..." << endl; 146 | qbVector compSolution(numUnknowns); 147 | int compTest = qbLinSolve(coefficientMatrix, systemResult, compSolution); 148 | PrintVector(compSolution); 149 | cout << endl; 150 | 151 | cout << "And compare the actual result with the computed solution..." << endl; 152 | qbVector errorVector = unknownVector - compSolution; 153 | PrintVector(errorVector); 154 | cout << endl; 155 | 156 | // *************************************************************************************************** 157 | // Test computation of the matrix rank. 158 | cout << "***************************************************************" << endl; 159 | cout << "Testing computation of matrix rank" << endl; 160 | cout << "***************************************************************" << endl; 161 | cout << endl; 162 | cout << "Testing with a solvable system:" << endl; 163 | PrintMatrix(aMat); 164 | cout << "Rank = " << aMat.Rank() << endl; 165 | cout << endl; 166 | cout << "Row echelon form:" << endl; 167 | qbMatrix2 aMatRowEchelon = aMat.RowEchelon(); 168 | PrintMatrix(aMatRowEchelon); 169 | cout << endl; 170 | 171 | // Test the condition when Gaussian elmination fails. 172 | cout << "***************************************************************" << endl; 173 | cout << "Testing the condition when Gaussian elimination fails" << endl; 174 | cout << "***************************************************************" << endl; 175 | std::vector geFailData = {0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0}; 176 | qbMatrix2 geFailMatrix (3, 3, geFailData); 177 | cout << "Testing with:" << endl; 178 | PrintMatrix(geFailMatrix); 179 | cout << endl; 180 | cout << "Attempt to perform Gaussian elimination on this gives:" << endl; 181 | qbMatrix2 geFailResult = geFailMatrix.RowEchelon(); 182 | PrintMatrix(geFailResult); 183 | cout << endl; 184 | cout << "Attempt to compute the rank gives:" << endl; 185 | cout << "Rank = "<< geFailMatrix.Rank() << endl; 186 | cout << endl; 187 | cout << "Testing with a zero matrix:" << endl; 188 | std::vector geFailData2 = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; 189 | qbMatrix2 geFailMatrix2 (3, 3, geFailData2); 190 | PrintMatrix(geFailMatrix2); 191 | cout << endl; 192 | cout << "Rank = " << geFailMatrix2.Rank() << endl; 193 | cout << endl; 194 | cout << "Testing with a larger example:" << endl; 195 | PrintMatrix(coefficientMatrix); 196 | cout << endl; 197 | cout << "The rank is " << coefficientMatrix.Rank() << endl; 198 | cout << endl; 199 | 200 | std::vector geFailData3 = {1.0, 2.0, 3.0, 4.0, 4.0, 3.0, 2.0, 1.0, 2.0, 4.0, 6.0, 8.0, 8.0, 6.0, 4.0, 2.0}; 201 | cout << "Testing with 4x4:" << endl; 202 | cout << endl; 203 | qbMatrix2 geFailMatrix3 (4, 4, geFailData3); 204 | PrintMatrix(geFailMatrix3); 205 | cout << "Rank = " << geFailMatrix3.Rank() << endl; 206 | cout << endl; 207 | 208 | // Test the two possible conditions with no solution 209 | cout << "***************************************************************" << endl; 210 | cout << "Testing the two possible conditions with no solution" << endl; 211 | cout << "***************************************************************" << endl; 212 | cout << endl; 213 | { 214 | // Setup a system with an infinite number of solutions. 215 | cout << "A system with an infinite number of solutions:" << endl; 216 | std::vector aMatData = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0}; 217 | std::vector bVecData = {0.0, 0.0, 0.0}; 218 | qbMatrix2 aMat (3, 3, aMatData); 219 | qbVector bVec {bVecData}; 220 | qbVector solution(3); 221 | int test = qbLinSolve(aMat, bVec, solution); 222 | if (test > 0) 223 | PrintVector(solution); 224 | else 225 | cout << "Error condition: " << test << endl; 226 | } 227 | cout << endl; 228 | { 229 | // Setup a system with no solutions. 230 | cout << "A system with no solutions:" << endl; 231 | std::vector aMatData = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0}; 232 | std::vector bVecData = {0.0, -1.0, 1.0}; 233 | qbMatrix2 aMat (3, 3, aMatData); 234 | qbVector bVec {bVecData}; 235 | qbVector solution(3); 236 | int test = qbLinSolve(aMat, bVec, solution); 237 | if (test > 0) 238 | PrintVector(solution); 239 | else 240 | cout << "Error condition: " << test << endl; 241 | } 242 | 243 | return 0; 244 | } 245 | -------------------------------------------------------------------------------- /TestCode/TestCode_qbEIG.cpp: -------------------------------------------------------------------------------- 1 | /* ************************************************************************************************* 2 | 3 | TestCode_qbEIG 4 | 5 | Code to test the eigenvector and eigenvalue code. 6 | 7 | *** INPUTS *** 8 | 9 | None 10 | 11 | *** OUTPUTS *** 12 | 13 | INT Flag indicating success or failure of the process. 14 | 15 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 16 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 17 | YouTube channel at: 18 | 19 | www.youtube.com/c/QuantitativeBytes 20 | 21 | ************************************************************************************************* */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "../qbMatrix.h" 34 | #include "../qbVector.h" 35 | #include "../qbEIG.h" 36 | 37 | using namespace std; 38 | 39 | // A simple function to print a matrix to stdout. 40 | template 41 | void PrintMatrix(qbMatrix2 matrix) 42 | { 43 | int nRows = matrix.GetNumRows(); 44 | int nCols = matrix.GetNumCols(); 45 | for (int row = 0; row 57 | void PrintVector(qbVector inputVector) 58 | { 59 | int nRows = inputVector.GetNumDims(); 60 | for (int row = 0; row simpleData = {1.0, 2.0, 3.0, 4.0}; 78 | //qbMatrix2 testMatrix(2, 2, simpleData); 79 | 80 | std::vector simpleData = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}; 81 | qbMatrix2 testMatrix(3, 3, simpleData); 82 | 83 | PrintMatrix(testMatrix); 84 | 85 | cout << endl; 86 | cout << "Computing eigenvector and eigenvalue..." << endl; 87 | double eigenValue; 88 | qbVector eigenVector; 89 | qbEIG_PIt(testMatrix, eigenValue, eigenVector); 90 | 91 | cout << "Eigenvector: " << endl; 92 | PrintVector(eigenVector); 93 | cout << "Eigenvalue = " << eigenValue << "." << endl; 94 | cout << endl; 95 | } 96 | 97 | cout << "**********************************************" << endl; 98 | cout << "Testing eigenvalue and eigenvector code." << endl; 99 | cout << "Inverse-Power Iteration Method." << endl; 100 | cout << "**********************************************" << endl; 101 | cout << endl; 102 | 103 | { 104 | cout << "Testing with a simple 3x3 matrix:" << endl; 105 | std::vector simpleData = {0.5, 0.75, 0.5, 1.0, 0.5, 0.75, 0.25, 0.25, 0.25}; 106 | qbMatrix2 testMatrix(3, 3, simpleData); 107 | PrintMatrix(testMatrix); 108 | cout << endl; 109 | 110 | // Define the eigenvalues. 111 | std::vector eigenValues = {1.5962551, -0.37253087, 0.02627577}; 112 | 113 | // Setup a vector for the eigenvector. 114 | qbVector eigenVector(3); 115 | 116 | // Loop through each eigenvalue. 117 | for (auto currentValue : eigenValues) 118 | { 119 | cout << "Estimated eigenvector for eigenvalue " << currentValue << " = " << endl; 120 | int returnStatus = qbInvPIt(testMatrix, currentValue, eigenVector); 121 | PrintVector(eigenVector); 122 | 123 | if (returnStatus == QBEIG_MAXITERATIONSEXCEEDED) 124 | cout << "*** Maximum iterations exceeded ***" << endl; 125 | 126 | cout << endl; 127 | } 128 | 129 | } 130 | 131 | cout << "**********************************************" << endl; 132 | cout << "Testing eigenvalue and eigenvector code." << endl; 133 | cout << "New IsSymmetric() function in qbMatrix2 class." << endl; 134 | cout << "**********************************************" << endl; 135 | cout << endl; 136 | 137 | { 138 | cout << "Testing with a simple symmetric matrix." << endl; 139 | std::vector simpleData = {4.0, -7.0, 6.0, -7.0, -2.0, 13.0, 6.0, 13.0, 5.0}; 140 | qbMatrix2 testMatrix(3, 3, simpleData); 141 | PrintMatrix(testMatrix); 142 | cout << endl; 143 | 144 | cout << "Matrix is symmetric: "; 145 | if (testMatrix.IsSymmetric()) 146 | cout << "True." << endl; 147 | else 148 | cout << "False." << endl; 149 | } 150 | 151 | { 152 | cout << "Testing with a simple non-symmetric matrix." << endl; 153 | std::vector simpleData = {4.0, -7.0, 6.0, 7.0, -2.0, 13.0, 6.0, 13.0, 5.0}; 154 | qbMatrix2 testMatrix(3, 3, simpleData); 155 | PrintMatrix(testMatrix); 156 | cout << endl; 157 | 158 | cout << "Matrix is symmetric: "; 159 | if (testMatrix.IsSymmetric()) 160 | cout << "True." << endl; 161 | else 162 | cout << "False." << endl; 163 | } 164 | 165 | cout << endl << endl; 166 | cout << "**********************************************" << endl; 167 | cout << "Testing eigenvalue and eigenvector code." << endl; 168 | cout << "Eigenvalues by QR decomposition." << endl; 169 | cout << "**********************************************" << endl; 170 | cout << endl; 171 | 172 | { 173 | cout << "Testing with a simple (non-symmetric) 3x3 matrix:" << endl; 174 | std::vector simpleData = {0.5, 0.75, 0.5, 1.0, 0.5, 0.75, 0.25, 0.25, 0.25}; 175 | qbMatrix2 testMatrix(3, 3, simpleData); 176 | PrintMatrix(testMatrix); 177 | cout << endl; 178 | 179 | // Compute the eigenvalues. 180 | std::vector eigenValues; 181 | int returnStatus = qbEigQR(testMatrix, eigenValues); 182 | 183 | if (returnStatus == QBEIG_MAXITERATIONSEXCEEDED) 184 | cout << ">>> Maximum iterations exceeded <<<" << endl; 185 | 186 | if (returnStatus == QBEIG_MATRIXNOTSYMMETRIC) 187 | cout << ">>> Matrix not symmetric. <<<" << endl; 188 | 189 | // Display the eigenvalues. 190 | cout << "The estimated eigenvalues are:" << endl; 191 | for (auto currentValue : eigenValues) 192 | cout << std::setprecision(6) << currentValue << " "; 193 | 194 | cout << endl << endl; 195 | } 196 | 197 | { 198 | cout << "Testing with a simple (symmetric) 3x3 matrix:" << endl; 199 | std::vector simpleData = {6.0, 5.5, -1.0, 5.5, 1.0, -2.0, -1.0, -2.0, -3.0}; 200 | qbMatrix2 testMatrix(3, 3, simpleData); 201 | PrintMatrix(testMatrix); 202 | cout << endl; 203 | 204 | // Compute the eigenvalues. 205 | std::vector eigenValues; 206 | int returnStatus = qbEigQR(testMatrix, eigenValues); 207 | 208 | if (returnStatus == QBEIG_MAXITERATIONSEXCEEDED) 209 | cout << ">>> Maximum iterations exceeded <<<" << endl; 210 | 211 | if (returnStatus == QBEIG_MATRIXNOTSYMMETRIC) 212 | cout << ">>> Matrix not symmetric. <<<" << endl; 213 | 214 | // Display the eigenvalues. 215 | cout << "The estimated eigenvalues are:" << endl; 216 | for (auto currentValue : eigenValues) 217 | cout << std::setprecision(6) << currentValue << " "; 218 | 219 | cout << endl << endl; 220 | 221 | // Setup a vector for the eigenvector. 222 | qbVector eigenVector(3); 223 | 224 | // Loop through each eigenvalue. 225 | for (auto currentValue : eigenValues) 226 | { 227 | cout << "Estimated eigenvector for eigenvalue " << currentValue << " = " << endl; 228 | int returnStatus = qbInvPIt(testMatrix, currentValue, eigenVector); 229 | PrintVector(eigenVector); 230 | 231 | if (returnStatus == QBEIG_MAXITERATIONSEXCEEDED) 232 | cout << "*** Maximum iterations exceeded ***" << endl; 233 | 234 | cout << endl; 235 | } 236 | 237 | cout << endl << endl; 238 | } 239 | 240 | { 241 | cout << "Testing with an example that should have complex eigenvalues:" << endl; 242 | std::vector simpleData = {4.0, -6.0, 8.0, 7.0, 9.0, -5.0, 9.0, -6.0, -4.0}; 243 | qbMatrix2 testMatrix(3, 3, simpleData); 244 | PrintMatrix(testMatrix); 245 | cout << endl; 246 | 247 | // Compute the eigenvalues. 248 | std::vector eigenValues; 249 | int returnStatus = qbEigQR(testMatrix, eigenValues); 250 | 251 | if (returnStatus == QBEIG_MATRIXNOTSYMMETRIC) 252 | cout << ">>> Matrix not symmetric. <<<" << endl; 253 | 254 | if (returnStatus == QBEIG_MAXITERATIONSEXCEEDED) 255 | cout << ">>> Maximum iterations exceeded <<<" << endl; 256 | 257 | // Display the eigenvalues. 258 | cout << "The estimated eigenvalues are:" << endl; 259 | for (auto currentValue : eigenValues) 260 | cout << std::setprecision(6) << currentValue << " "; 261 | 262 | cout << endl << endl; 263 | 264 | // Setup a vector for the eigenvector. 265 | qbVector eigenVector(3); 266 | 267 | // Loop through each eigenvalue. 268 | for (auto currentValue : eigenValues) 269 | { 270 | cout << "Estimated eigenvector for eigenvalue " << currentValue << " = " << endl; 271 | int returnStatus = qbInvPIt(testMatrix, currentValue, eigenVector); 272 | PrintVector(eigenVector); 273 | 274 | if (returnStatus == QBEIG_MAXITERATIONSEXCEEDED) 275 | cout << "*** Maximum iterations exceeded ***" << endl; 276 | 277 | cout << endl; 278 | } 279 | 280 | cout << endl << endl; 281 | } 282 | 283 | return 0; 284 | } 285 | -------------------------------------------------------------------------------- /TestCode/qbMatrixTest.cpp: -------------------------------------------------------------------------------- 1 | /* ************************************************************************************************* 2 | 3 | qbMatrixTest 4 | 5 | Code to test the basic functionality of the qbMatrix class contained in qbMatrix.h 6 | 7 | *** INPUTS *** 8 | 9 | None 10 | 11 | *** OUTPUTS *** 12 | 13 | INT Flag indicating success or failure of the process. 14 | 15 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 16 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 17 | YouTube channel at: 18 | 19 | www.youtube.com/c/QuantitativeBytes 20 | 21 | ************************************************************************************************* */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "../qbMatrix.h" 32 | 33 | using namespace std; 34 | 35 | // A simple function to print a matrix to stdout. 36 | template 37 | void PrintMatrix(qbMatrix2 matrix) 38 | { 39 | int nRows = matrix.GetNumRows(); 40 | int nCols = matrix.GetNumCols(); 41 | for (int row = 0; row testMatrix(3, 4, simpleData); 60 | 61 | // Extract and print the elements of testMatrix. 62 | cout << endl << "**************************" << endl; 63 | cout << "3x4 matrix test (testMatrix)." << endl; 64 | PrintMatrix(testMatrix); 65 | 66 | // ******************************************************************* 67 | // Test element retrieval. 68 | cout << endl << "**************************" << endl; 69 | cout << "Test element retrieval." << endl; 70 | cout << "Element (0,0) = " << testMatrix.GetElement(0,0) << endl; 71 | cout << "Element (1,0) = " << testMatrix.GetElement(1,0) << endl; 72 | cout << "Element (2,0) = " << testMatrix.GetElement(2,0) << endl; 73 | cout << "Element (0,1) = " << testMatrix.GetElement(0,1) << endl; 74 | cout << "Element (1,1) = " << testMatrix.GetElement(1,1) << endl; 75 | cout << "Element (2,1) = " << testMatrix.GetElement(2,1) << endl; 76 | cout << "Element (5,5) = " << testMatrix.GetElement(5,5) << endl; 77 | 78 | // ******************************************************************* 79 | // Test matrix multiplication. 80 | cout << endl << "**************************" << endl; 81 | cout << "Test matrix multiplication." << endl; 82 | double simpleData2[12] = {1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0, 1.0, 2.0, 3.0}; 83 | qbMatrix2 testMatrix2(4, 3, simpleData2); 84 | cout << "4x3 matrix (testMatrix2)" << endl; 85 | PrintMatrix(testMatrix2); 86 | cout << "Multiplication (testMatrix * testMatrix2) result:" << endl; 87 | qbMatrix2 multTest1 = testMatrix * testMatrix2; 88 | PrintMatrix(multTest1); 89 | 90 | cout << endl << "**************************" << endl; 91 | cout << "testMatrix2 * testMatrix:" << endl; 92 | qbMatrix2 multTest2 = testMatrix2 * testMatrix; 93 | PrintMatrix(multTest2); 94 | 95 | cout << endl << "**************************" << endl; 96 | cout << "Test multiplication of column vector by matrix." << endl; 97 | double columnData[3] = {1.5, 2.5, 3.5}; 98 | double squareData[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; 99 | qbMatrix2 testColumn(3, 1, columnData); 100 | qbMatrix2 squareMatrix(3, 3, squareData); 101 | cout << "Column vector = " << endl; 102 | PrintMatrix(testColumn); 103 | cout << "Square matrix = " << endl; 104 | PrintMatrix(squareMatrix); 105 | cout << "Column vector * Square matrix = " << endl; 106 | PrintMatrix(testColumn * squareMatrix); 107 | cout << "Square matrix * Column vector = " << endl; 108 | PrintMatrix(squareMatrix * testColumn); 109 | cout << "Square matrix + 1.0 = " << endl; 110 | qbMatrix2 squareMatrix2 = squareMatrix + 1.0; 111 | PrintMatrix(squareMatrix2); 112 | cout << "(Square matrix + 1.0) * Column vector = " << endl; 113 | PrintMatrix(squareMatrix2 * testColumn); 114 | 115 | // ******************************************************************* 116 | // Test equality operator 117 | cout << endl << "**************************" << endl; 118 | cout << "Test equility operator." << endl; 119 | cout << "testMatrix == testMatrix2: " << (testMatrix == testMatrix2) << endl; 120 | cout << "testMatrix2 == testMatrix: " << (testMatrix2 == testMatrix) << endl; 121 | cout << "(Let testMatrix3 = testMatrix)" << endl; 122 | qbMatrix2 testMatrix3 = testMatrix; 123 | cout << "testMatrix == testMatrix3: " << (testMatrix == testMatrix3) << endl; 124 | cout << "testMatrix3 == testMatrix: " << (testMatrix3 == testMatrix) << endl; 125 | 126 | // ******************************************************************* 127 | // Test matrix addition by scaler. 128 | cout << endl << "**************************" << endl; 129 | cout << "Test addition by scaler" << endl; 130 | cout << "testMatrix + 2.0 = " << endl; 131 | PrintMatrix(testMatrix+2.0); 132 | cout << endl; 133 | cout << "2.0 + testMatrix = " << endl; 134 | PrintMatrix(2.0+testMatrix); 135 | 136 | // ******************************************************************* 137 | // Test matrix subtraction by scaler. 138 | cout << endl << "**************************" << endl; 139 | cout << "Test addition by scaler" << endl; 140 | cout << "testMatrix - 2.0 = " << endl; 141 | PrintMatrix(testMatrix-2.0); 142 | cout << endl; 143 | cout << "2.0 - testMatrix = " << endl; 144 | PrintMatrix(2.0-testMatrix); 145 | 146 | // ******************************************************************* 147 | // Test matrix multiplication by scaler. 148 | cout << endl << "**************************" << endl; 149 | cout << "Test multiplication by scaler" << endl; 150 | cout << "testMatrix * 2.0 = " << endl; 151 | PrintMatrix(testMatrix*2.0); 152 | cout << endl; 153 | cout << "2.0 * testMatrix = " << endl; 154 | PrintMatrix(2.0*testMatrix); 155 | 156 | // ******************************************************************* 157 | // Test formation of identity matrix. 158 | cout << endl << "**************************" << endl; 159 | cout << "Test formation of identity matrix." << endl; 160 | qbMatrix2 identityTest(5,5); 161 | identityTest.SetToIdentity(); 162 | PrintMatrix(identityTest); 163 | 164 | // ******************************************************************* 165 | // Test joining of two matrices. 166 | cout << endl << "**************************" << endl; 167 | cout << "Test joining of two matrices." << endl; 168 | qbMatrix2 bigSquare(5,5); 169 | bigSquare.Join(identityTest); 170 | PrintMatrix(bigSquare); 171 | 172 | // ******************************************************************* 173 | // Test matrix inversion 174 | cout << endl << "**************************" << endl; 175 | cout << "Test matrix inversion" << endl; 176 | cout << "Attempt to invert a non-square matrix:" << endl; 177 | try 178 | { 179 | testMatrix.Inverse(); 180 | } 181 | catch (invalid_argument& e) 182 | { 183 | cerr << e.what() << endl; 184 | cout << "Execution would normally stop here with a return code of -1." << endl; 185 | } 186 | 187 | cout << endl << "**************************" << endl; 188 | cout << "Test matrix inversion" << endl; 189 | cout << "Attempt to invert a square matrix:" << endl; 190 | //double invertTestData[9] = {2.0, 1.0, 1.0, 1.0, 2.0, 3.0, 0.0, 3.0, 1.0}; 191 | double invertTestData[9] = {2.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 3.0, 1.0}; 192 | qbMatrix2 invertTest(3, 3, invertTestData); 193 | qbMatrix2 invertResult = invertTest; 194 | invertResult.Inverse(); 195 | cout << "From:" << endl; 196 | PrintMatrix(invertTest); 197 | cout << "To:" << endl; 198 | PrintMatrix(invertResult); 199 | 200 | // ******************************************************************* 201 | // Test inversion of a bigger matrix. 202 | cout << endl << "**************************" << endl; 203 | cout << "Test inversion of a bigger matrix." << endl; 204 | double invertTestData2[25] = 205 | {2.0, 3.0, 4.0, 5.0, 6.0, 206 | 1.0, 2.0, 3.0, 4.0, 5.0, 207 | 9.0, 5.0, 3.0, 2.0, 6.0, 208 | 2.0, 4.0, 6.0, 5.0, 1.0, 209 | 1.0, 7.0, 5.0, 2.0, 3.0}; 210 | qbMatrix2 invertTest2(5, 5, invertTestData2); 211 | qbMatrix2 invertResult2 = invertTest2; 212 | invertResult2.Inverse(); 213 | cout << "From:" << endl; 214 | PrintMatrix(invertTest2); 215 | cout << "To:" << endl; 216 | PrintMatrix(invertResult2); 217 | 218 | cout << endl; 219 | 220 | cout << endl << "**************************" << endl; 221 | cout << "Test multiplication of matrix by it's inverse." << endl; 222 | cout << "Using invertTest2 * invertResult2:" << endl; 223 | qbMatrix2 invertAccuracy3 = invertTest2 * invertResult2; 224 | PrintMatrix(invertAccuracy3); 225 | 226 | cout << endl; 227 | cout << "Using invertTest * invertResult:" << endl; 228 | qbMatrix2 invertAccuracy = invertTest * invertResult; 229 | PrintMatrix(invertAccuracy); 230 | 231 | // ******************************************************************* 232 | // Test inversion of a singular matrix. 233 | /*cout << endl << "**************************" << endl; 234 | cout << "Test inversion of a singular matrix." << endl; 235 | double invertTestData3[9] = 236 | {1.0, 1.0, 1.0, 237 | 0.0, 1.0, 0.0, 238 | 1.0, 0.0, 1.0}; 239 | qbMatrix2 invertTest3(3, 3, invertTestData3); 240 | qbMatrix2 invertResult3 = invertTest3; 241 | invertResult3.Inverse(); 242 | cout << "From:" << endl; 243 | PrintMatrix(invertTest3); 244 | cout << "To:" << endl; 245 | PrintMatrix(invertResult3); */ 246 | 247 | return 0; 248 | } 249 | -------------------------------------------------------------------------------- /qbVector2.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | /* 3 | MIT License 4 | Copyright (c) 2023 Michael Bennett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell 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 copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef QBVECTOR2_H 23 | #define QBVECTOR2_H 24 | 25 | /* ************************************************************************************************* 26 | 27 | qbVector2 28 | 29 | Class to provide capability to handle two-dimensional vectors. 30 | 31 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 32 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 33 | YouTube channel at: 34 | 35 | www.youtube.com/c/QuantitativeBytes 36 | 37 | ************************************************************************************************* */ 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include "qbVector.h" 46 | 47 | template 48 | class qbVector2 49 | { 50 | public: 51 | // Define the various constructors. 52 | // Default. 53 | qbVector2(); 54 | 55 | // With input data (std::vector). 56 | qbVector2(const std::vector &inputData); 57 | // With input data (qbVector). 58 | qbVector2(const qbVector &inputData); 59 | // With input data (qbVector2). 60 | qbVector2(const qbVector2 &inputData); 61 | // With input data as two separate values. 62 | qbVector2(const T x, const T y); 63 | 64 | // And the destructor. 65 | ~qbVector2(); 66 | 67 | // Keep the GetNumDims() function for backwards compatibility. 68 | int GetNumDims() const; 69 | T GetElement(int index) const; 70 | void SetElement(int index, T value); 71 | 72 | // Functions to perform computations on the vector. 73 | // Return the length of the vector. 74 | T norm(); 75 | 76 | // Return a normalized copy of the vector. 77 | qbVector2 Normalized(); 78 | 79 | // Normalize the vector in place. 80 | void Normalize(); 81 | 82 | // Overloaded operators. 83 | qbVector2 operator+ (const qbVector2 &rhs) const; 84 | qbVector2 operator- (const qbVector2 &rhs) const; 85 | qbVector2 operator* (const T &rhs) const; 86 | 87 | // Overload the assignment operator. 88 | qbVector2 operator= (const qbVector &rhs); 89 | qbVector2 operator= (const std::vector &rhs); 90 | qbVector2 operator= (const qbVector2 &rhs); 91 | 92 | // Friend functions. 93 | template friend qbVector2 operator* (const U &lhs, const qbVector2 &rhs); 94 | 95 | // Static functions. 96 | static T dot(const qbVector2 &a, const qbVector2 &b); 97 | 98 | public: 99 | T m_x; 100 | T m_y; 101 | 102 | }; 103 | 104 | /* ************************************************************************************************** 105 | CONSTRUCTOR / DESTRUCTOR FUNCTIONS 106 | /* *************************************************************************************************/ 107 | // The default constructor. 108 | template 109 | qbVector2::qbVector2() 110 | { 111 | m_x = static_cast(0.0); 112 | m_y = static_cast(0.0); 113 | } 114 | 115 | template 116 | qbVector2::qbVector2(const std::vector &inputData) 117 | { 118 | if (inputData.size() != 2) 119 | throw std::invalid_argument("Cannot assign std::vector to qbVector2 - assignment dimension mismatch."); 120 | 121 | m_x = inputData.at(0); 122 | m_y = inputData.at(1); 123 | } 124 | 125 | template 126 | qbVector2::qbVector2(const qbVector &inputData) 127 | { 128 | if (inputData.GetNumDims() != 2) 129 | throw std::invalid_argument("Cannot assign qbVector to qbVector2 - assignment dimension mismatch."); 130 | 131 | m_x = inputData.GetElement(0); 132 | m_y = inputData.GetElement(1); 133 | } 134 | 135 | template 136 | qbVector2::qbVector2(const qbVector2 &inputData) 137 | { 138 | m_x = inputData.m_x; 139 | m_y = inputData.m_y; 140 | } 141 | 142 | template 143 | qbVector2::qbVector2(const T x, const T y) 144 | { 145 | m_x = x; 146 | m_y = y; 147 | } 148 | 149 | template 150 | qbVector2::~qbVector2() 151 | { 152 | // For now, we don't need to do anything in the destructor. 153 | } 154 | 155 | /* ************************************************************************************************** 156 | FUNCTIONS TO PERFORM COMPUTATIONS ON THE VECTOR 157 | /* *************************************************************************************************/ 158 | // Compute the length of the vector, known as the 'norm'. 159 | template 160 | T qbVector2::norm() 161 | { 162 | return sqrt((m_x*m_x) + (m_y*m_y)); 163 | } 164 | 165 | // Return a normalized copy of the vector. 166 | template 167 | qbVector2 qbVector2::Normalized() 168 | { 169 | // Compute the vector norm. 170 | T vecNorm = this->norm(); 171 | 172 | // Compute the normalized version of the vector. 173 | //qbVector result(m_vectorData); 174 | qbVector2 result; 175 | result.m_x = m_x / vecNorm; 176 | result.m_y = m_y / vecNorm; 177 | 178 | return result; 179 | } 180 | 181 | // Normalize the vector in place. 182 | template 183 | void qbVector2::Normalize() 184 | { 185 | // Compute the vector norm. 186 | T vecNorm = this->norm(); 187 | T denominator = static_cast(1.0) / vecNorm; 188 | 189 | m_x = m_x / denominator; 190 | m_y = m_y / denominator; 191 | } 192 | 193 | /* ************************************************************************************************** 194 | OVERLOADED OPERATORS 195 | /* *************************************************************************************************/ 196 | template 197 | qbVector2 qbVector2::operator+ (const qbVector2 &rhs) const 198 | { 199 | qbVector2 result; 200 | result.m_x = m_x + rhs.m_x; 201 | result.m_y = m_y + rhs.m_y; 202 | 203 | return result; 204 | } 205 | 206 | template 207 | qbVector2 qbVector2::operator- (const qbVector2 &rhs) const 208 | { 209 | qbVector2 result; 210 | result.m_x = m_x - rhs.m_x; 211 | result.m_y = m_y - rhs.m_y; 212 | 213 | return result; 214 | } 215 | 216 | template 217 | qbVector2 qbVector2::operator* (const T &rhs) const 218 | { 219 | qbVector2 result; 220 | result.m_x = m_x * rhs; 221 | result.m_y = m_y * rhs; 222 | 223 | return result; 224 | } 225 | 226 | /* ************************************************************************************************** 227 | THE ASSIGNMENT (=) OPERATOR 228 | /* *************************************************************************************************/ 229 | template 230 | qbVector2 qbVector2::operator= (const qbVector &rhs) 231 | { 232 | if (rhs.GetNumDims() != 2) 233 | throw std::invalid_argument("Cannot assign qbVector to qbVector2 - assignment dimension mismatch."); 234 | 235 | m_x = rhs.GetElement(0); 236 | m_y = rhs.GetElement(1); 237 | 238 | return *this; 239 | } 240 | 241 | template 242 | qbVector2 qbVector2::operator= (const std::vector &rhs) 243 | { 244 | if (rhs.size() != 2) 245 | throw std::invalid_argument("Cannot assign std::vector to qbVector2 - assignment dimension mismatch."); 246 | 247 | m_x = rhs.at(0); 248 | m_y = rhs.at(1); 249 | 250 | return *this; 251 | } 252 | 253 | template 254 | qbVector2 qbVector2::operator= (const qbVector2 &rhs) 255 | { 256 | m_x = rhs.m_x; 257 | m_y = rhs.m_y; 258 | 259 | return *this; 260 | } 261 | 262 | /* ************************************************************************************************** 263 | FRIEND FUNCTIONS 264 | /* *************************************************************************************************/ 265 | template 266 | qbVector2 operator* (const T &lhs, const qbVector2 &rhs) 267 | { 268 | // Perform scalar multiplication. 269 | qbVector2 result; 270 | result.m_x = lhs * rhs.m_x; 271 | result.m_y = lhs * rhs.m_y; 272 | 273 | return result; 274 | } 275 | 276 | /* ************************************************************************************************** 277 | STATIC FUNCTIONS 278 | /* *************************************************************************************************/ 279 | template 280 | T qbVector2::dot(const qbVector2 &a, const qbVector2 &b) 281 | { 282 | T cumulativeSum = (a.m_x * b.m_x) + (a.m_y * b.m_y); 283 | 284 | return cumulativeSum; 285 | } 286 | 287 | /* ************************************************************************************************** 288 | FUNCTIONS FOR COMPATIBILITY 289 | /* *************************************************************************************************/ 290 | template 291 | int qbVector2::GetNumDims() const 292 | { 293 | return 2; 294 | } 295 | 296 | template 297 | T qbVector2::GetElement(int index) const 298 | { 299 | if (index == 0) 300 | { 301 | return m_x; 302 | } 303 | else if (index == 1) 304 | { 305 | return m_y; 306 | } 307 | else 308 | { 309 | throw std::invalid_argument("Attempt to get invalid element index."); 310 | } 311 | return 0; 312 | } 313 | 314 | template 315 | void qbVector2::SetElement(int index, T value) 316 | { 317 | if (index == 0) 318 | { 319 | m_x = value; 320 | } 321 | else if (index == 1) 322 | { 323 | m_y = value; 324 | } 325 | else 326 | { 327 | throw std::invalid_argument("Attempt to set invalid element index."); 328 | } 329 | } 330 | 331 | #endif 332 | -------------------------------------------------------------------------------- /qbVector3.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | /* 3 | MIT License 4 | Copyright (c) 2023 Michael Bennett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell 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 copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef QBVECTOR3_H 23 | #define QBVECTOR3_H 24 | 25 | /* ************************************************************************************************* 26 | 27 | qbVector3 28 | 29 | Class to provide capability to handle three-dimensional vectors. 30 | 31 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 32 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 33 | YouTube channel at: 34 | 35 | www.youtube.com/c/QuantitativeBytes 36 | 37 | ************************************************************************************************* */ 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include "qbVector.h" 46 | 47 | template 48 | class qbVector3 49 | { 50 | public: 51 | // Define the various constructors. 52 | // Default. 53 | qbVector3(); 54 | 55 | // With input data (std::vector). 56 | qbVector3(const std::vector &inputData); 57 | // With input data (qbVector). 58 | qbVector3(const qbVector &inputData); 59 | // With input data (qbVector3). 60 | qbVector3(const qbVector3 &inputData); 61 | // With input data as three separate values. 62 | qbVector3(const T x, const T y, const T z); 63 | 64 | // And the destructor. 65 | ~qbVector3(); 66 | 67 | // Keep the GetNumDims() function for backwards compatibility. 68 | int GetNumDims() const; 69 | T GetElement(int index) const; 70 | void SetElement(int index, T value); 71 | 72 | // Functions to perform computations on the vector. 73 | // Return the length of the vector. 74 | T norm(); 75 | 76 | // Return a normalized copy of the vector. 77 | qbVector3 Normalized(); 78 | 79 | // Normalize the vector in place. 80 | void Normalize(); 81 | 82 | // Overloaded operators. 83 | qbVector3 operator+ (const qbVector3 &rhs) const; 84 | qbVector3 operator- (const qbVector3 &rhs) const; 85 | qbVector3 operator* (const T &rhs) const; 86 | 87 | // Overload the assignment operator. 88 | qbVector3 operator= (const qbVector &rhs); 89 | qbVector3 operator= (const std::vector &rhs); 90 | qbVector3 operator= (const qbVector3 &rhs); 91 | 92 | // Friend functions. 93 | template friend qbVector3 operator* (const U &lhs, const qbVector3 &rhs); 94 | 95 | // Static functions. 96 | static T dot(const qbVector3 &a, const qbVector3 &b); 97 | static qbVector3 cross(const qbVector3 &a, const qbVector3 &b); 98 | 99 | public: 100 | T m_x; 101 | T m_y; 102 | T m_z; 103 | 104 | }; 105 | 106 | /* ************************************************************************************************** 107 | CONSTRUCTOR / DESTRUCTOR FUNCTIONS 108 | /* *************************************************************************************************/ 109 | // The default constructor. 110 | template 111 | qbVector3::qbVector3() 112 | { 113 | m_x = static_cast(0.0); 114 | m_y = static_cast(0.0); 115 | m_z = static_cast(0.0); 116 | } 117 | 118 | template 119 | qbVector3::qbVector3(const std::vector &inputData) 120 | { 121 | if (inputData.size() != 3) 122 | throw std::invalid_argument("Cannot assign std::vector to qbVector3 - assignment dimension mismatch."); 123 | 124 | m_x = inputData.at(0); 125 | m_y = inputData.at(1); 126 | m_z = inputData.at(2); 127 | } 128 | 129 | template 130 | qbVector3::qbVector3(const qbVector &inputData) 131 | { 132 | if (inputData.GetNumDims() != 3) 133 | throw std::invalid_argument("Cannot assign qbVector to qbVector3 - assignment dimension mismatch."); 134 | 135 | m_x = inputData.GetElement(0); 136 | m_y = inputData.GetElement(1); 137 | m_z = inputData.GetElement(2); 138 | } 139 | 140 | template 141 | qbVector3::qbVector3(const qbVector3 &inputData) 142 | { 143 | m_x = inputData.m_x; 144 | m_y = inputData.m_y; 145 | m_z = inputData.m_z; 146 | } 147 | 148 | template 149 | qbVector3::qbVector3(const T x, const T y, const T z) 150 | { 151 | m_x = x; 152 | m_y = y; 153 | m_z = z; 154 | } 155 | 156 | template 157 | qbVector3::~qbVector3() 158 | { 159 | // For now, we don't need to do anything in the destructor. 160 | } 161 | 162 | /* ************************************************************************************************** 163 | FUNCTIONS TO PERFORM COMPUTATIONS ON THE VECTOR 164 | /* *************************************************************************************************/ 165 | // Compute the length of the vector, known as the 'norm'. 166 | template 167 | T qbVector3::norm() 168 | { 169 | return sqrt((m_x*m_x) + (m_y*m_y) + (m_z*m_z)); 170 | } 171 | 172 | // Return a normalized copy of the vector. 173 | template 174 | qbVector3 qbVector3::Normalized() 175 | { 176 | // Compute the vector norm. 177 | T vecNorm = this->norm(); 178 | 179 | // Compute the normalized version of the vector. 180 | //qbVector result(m_vectorData); 181 | qbVector3 result; 182 | result.m_x = m_x / vecNorm; 183 | result.m_y = m_y / vecNorm; 184 | result.m_z = m_z / vecNorm; 185 | 186 | return result; 187 | } 188 | 189 | // Normalize the vector in place. 190 | template 191 | void qbVector3::Normalize() 192 | { 193 | // Compute the vector norm. 194 | T vecNorm = this->norm(); 195 | 196 | m_x = m_x / vecNorm; 197 | m_y = m_y / vecNorm; 198 | m_z = m_z / vecNorm; 199 | } 200 | 201 | /* ************************************************************************************************** 202 | OVERLOADED OPERATORS 203 | /* *************************************************************************************************/ 204 | template 205 | qbVector3 qbVector3::operator+ (const qbVector3 &rhs) const 206 | { 207 | qbVector3 result; 208 | result.m_x = m_x + rhs.m_x; 209 | result.m_y = m_y + rhs.m_y; 210 | result.m_z = m_z + rhs.m_z; 211 | 212 | return result; 213 | } 214 | 215 | template 216 | qbVector3 qbVector3::operator- (const qbVector3 &rhs) const 217 | { 218 | qbVector3 result; 219 | result.m_x = m_x - rhs.m_x; 220 | result.m_y = m_y - rhs.m_y; 221 | result.m_z = m_z - rhs.m_z; 222 | 223 | return result; 224 | } 225 | 226 | template 227 | qbVector3 qbVector3::operator* (const T &rhs) const 228 | { 229 | qbVector3 result; 230 | result.m_x = m_x * rhs; 231 | result.m_y = m_y * rhs; 232 | result.m_z = m_z * rhs; 233 | 234 | return result; 235 | } 236 | 237 | /* ************************************************************************************************** 238 | THE ASSIGNMENT (=) OPERATOR 239 | /* *************************************************************************************************/ 240 | template 241 | qbVector3 qbVector3::operator= (const qbVector &rhs) 242 | { 243 | if (rhs.GetNumDims() != 3) 244 | throw std::invalid_argument("Cannot assign qbVector to qbVector3 - assignment dimension mismatch."); 245 | 246 | m_x = rhs.GetElement(0); 247 | m_y = rhs.GetElement(1); 248 | m_z = rhs.GetElement(2); 249 | 250 | return *this; 251 | } 252 | 253 | template 254 | qbVector3 qbVector3::operator= (const std::vector &rhs) 255 | { 256 | if (rhs.size() != 3) 257 | throw std::invalid_argument("Cannot assign std::vector to qbVector3 - assignment dimension mismatch."); 258 | 259 | m_x = rhs.at(0); 260 | m_y = rhs.at(1); 261 | m_z = rhs.at(2); 262 | 263 | return *this; 264 | } 265 | 266 | template 267 | qbVector3 qbVector3::operator= (const qbVector3 &rhs) 268 | { 269 | m_x = rhs.m_x; 270 | m_y = rhs.m_y; 271 | m_z = rhs.m_z; 272 | 273 | return *this; 274 | } 275 | 276 | /* ************************************************************************************************** 277 | FRIEND FUNCTIONS 278 | /* *************************************************************************************************/ 279 | template 280 | qbVector3 operator* (const T &lhs, const qbVector3 &rhs) 281 | { 282 | // Perform scalar multiplication. 283 | qbVector3 result; 284 | result.m_x = lhs * rhs.m_x; 285 | result.m_y = lhs * rhs.m_y; 286 | result.m_z = lhs * rhs.m_z; 287 | 288 | return result; 289 | } 290 | 291 | /* ************************************************************************************************** 292 | STATIC FUNCTIONS 293 | /* *************************************************************************************************/ 294 | template 295 | T qbVector3::dot(const qbVector3 &a, const qbVector3 &b) 296 | { 297 | T cumulativeSum = (a.m_x * b.m_x) + (a.m_y * b.m_y) + (a.m_z * b.m_z); 298 | 299 | return cumulativeSum; 300 | } 301 | 302 | template 303 | qbVector3 qbVector3::cross(const qbVector3 &a, const qbVector3 &b) 304 | { 305 | // Compute the cross product. 306 | qbVector3 result; 307 | result.SetElement(0, ((a.GetElement(1) * b.GetElement(2)) - (a.GetElement(2) * b.GetElement(1)))); 308 | result.SetElement(1, (-((a.GetElement(0) * b.GetElement(2)) - (a.GetElement(2) * b.GetElement(0))))); 309 | result.SetElement(2, ((a.GetElement(0) * b.GetElement(1)) - (a.GetElement(1) * b.GetElement(0)))); 310 | //result.m_x = (a.m_y * b.m_z) - (a.m_z * b.m_y); 311 | //result.m_y = -((a.m_x * b.m_z) - (a.m_z * b.m_x)); 312 | //result.m_z = (a.m_x * b.m_y) - (a.m_y * b.m_x); 313 | 314 | return result; 315 | } 316 | 317 | /* ************************************************************************************************** 318 | FUNCTIONS FOR COMPATIBILITY 319 | /* *************************************************************************************************/ 320 | template 321 | int qbVector3::GetNumDims() const 322 | { 323 | return 3; 324 | } 325 | 326 | template 327 | T qbVector3::GetElement(int index) const 328 | { 329 | if (index == 0) 330 | { 331 | return m_x; 332 | } 333 | else if (index == 1) 334 | { 335 | return m_y; 336 | } 337 | else if (index == 2) 338 | { 339 | return m_z; 340 | } 341 | else 342 | { 343 | throw std::invalid_argument("Attempt to get invalid element index."); 344 | } 345 | return 0; 346 | } 347 | 348 | template 349 | void qbVector3::SetElement(int index, T value) 350 | { 351 | if (index == 0) 352 | { 353 | m_x = value; 354 | } 355 | else if (index == 1) 356 | { 357 | m_y = value; 358 | } 359 | else if (index == 2) 360 | { 361 | m_z = value; 362 | } 363 | else 364 | { 365 | throw std::invalid_argument("Attempt to set invalid element index."); 366 | } 367 | } 368 | 369 | #endif 370 | -------------------------------------------------------------------------------- /qbVector4.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | /* 3 | MIT License 4 | Copyright (c) 2023 Michael Bennett 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 7 | and associated documentation files (the "Software"), to deal in the Software without restriction, 8 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 9 | sublicense, and/or sell 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 copies or 13 | substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 16 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | */ 21 | 22 | #ifndef qbVector4_H 23 | #define qbVector4_H 24 | 25 | /* ************************************************************************************************* 26 | 27 | qbVector4 28 | 29 | Class to provide capability to handle four-dimensional vectors. 30 | 31 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 32 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 33 | YouTube channel at: 34 | 35 | www.youtube.com/c/QuantitativeBytes 36 | 37 | ************************************************************************************************* */ 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include "qbVector.h" 46 | 47 | template 48 | class qbVector4 49 | { 50 | public: 51 | // Define the various constructors. 52 | // Default. 53 | qbVector4(); 54 | 55 | // With input data (std::vector). 56 | qbVector4(const std::vector &inputData); 57 | // With input data (qbVector). 58 | qbVector4(const qbVector &inputData); 59 | // With input data (qbVector4). 60 | qbVector4(const qbVector4 &inputData); 61 | // With input data as four separate values. 62 | qbVector4(const T v1, const T v2, const T v3, const T v4); 63 | 64 | // And the destructor. 65 | ~qbVector4(); 66 | 67 | // Keep the GetNumDims() function for backwards compatibility. 68 | int GetNumDims() const; 69 | T GetElement(int index) const; 70 | void SetElement(int index, T value); 71 | 72 | // Functions to perform computations on the vector. 73 | // Return the length of the vector. 74 | T norm(); 75 | 76 | // Return a normalized copy of the vector. 77 | qbVector4 Normalized(); 78 | 79 | // Normalize the vector in place. 80 | void Normalize(); 81 | 82 | // Overloaded operators. 83 | qbVector4 operator+ (const qbVector4 &rhs) const; 84 | qbVector4 operator- (const qbVector4 &rhs) const; 85 | qbVector4 operator* (const T &rhs) const; 86 | 87 | // Overload the assignment operator. 88 | qbVector4 operator= (const qbVector &rhs); 89 | qbVector4 operator= (const std::vector &rhs); 90 | qbVector4 operator= (const qbVector4 &rhs); 91 | 92 | // Friend functions. 93 | template friend qbVector4 operator* (const U &lhs, const qbVector4 &rhs); 94 | 95 | // Static functions. 96 | static T dot(const qbVector4 &a, const qbVector4 &b); 97 | 98 | public: 99 | T m_v1; 100 | T m_v2; 101 | T m_v3; 102 | T m_v4; 103 | 104 | }; 105 | 106 | /* ************************************************************************************************** 107 | CONSTRUCTOR / DESTRUCTOR FUNCTIONS 108 | /* *************************************************************************************************/ 109 | // The default constructor. 110 | template 111 | qbVector4::qbVector4() 112 | { 113 | m_v1 = static_cast(0.0); 114 | m_v2 = static_cast(0.0); 115 | m_v2 = static_cast(0.0); 116 | m_v4 = static_cast(0.0); 117 | } 118 | 119 | template 120 | qbVector4::qbVector4(const std::vector &inputData) 121 | { 122 | if (inputData.size() != 4) 123 | throw std::invalid_argument("Cannot assign std::vector to qbVector4 - assignment dimension mismatch."); 124 | 125 | m_v1 = inputData.at(0); 126 | m_v2 = inputData.at(1); 127 | m_v3 = inputData.at(2); 128 | m_v4 = inputData.at(3); 129 | } 130 | 131 | template 132 | qbVector4::qbVector4(const qbVector &inputData) 133 | { 134 | if (inputData.GetNumDims() != 4) 135 | throw std::invalid_argument("Cannot assign qbVector to qbVector4 - assignment dimension mismatch."); 136 | 137 | m_v1 = inputData.GetElement(0); 138 | m_v2 = inputData.GetElement(1); 139 | m_v2 = inputData.GetElement(2); 140 | m_v3 = inputData.GetElement(3); 141 | 142 | } 143 | 144 | template 145 | qbVector4::qbVector4(const qbVector4 &inputData) 146 | { 147 | m_v1 = inputData.m_v1; 148 | m_v2 = inputData.m_v2; 149 | m_v3 = inputData.m_v3; 150 | m_v4 = inputData.m_v4; 151 | } 152 | 153 | template 154 | qbVector4::qbVector4(const T v1, const T v2, const T v3, const T v4) 155 | { 156 | m_v1 = v1; 157 | m_v2 = v2; 158 | m_v3 = v3; 159 | m_v4 = v4; 160 | } 161 | 162 | template 163 | qbVector4::~qbVector4() 164 | { 165 | // For now, we don't need to do anything in the destructor. 166 | } 167 | 168 | /* ************************************************************************************************** 169 | FUNCTIONS TO PERFORM COMPUTATIONS ON THE VECTOR 170 | /* *************************************************************************************************/ 171 | // Compute the length of the vector, known as the 'norm'. 172 | template 173 | T qbVector4::norm() 174 | { 175 | return sqrt((m_v1*m_v1) + (m_v2*m_v2) + (m_v3*m_v3) + (m_v4*m_v4)); 176 | } 177 | 178 | // Return a normalized copy of the vector. 179 | template 180 | qbVector4 qbVector4::Normalized() 181 | { 182 | // Compute the vector norm. 183 | T vecNorm = this->norm(); 184 | 185 | // Compute the normalized version of the vector. 186 | //qbVector result(m_vectorData); 187 | qbVector4 result; 188 | result.m_v1 = m_v1 / vecNorm; 189 | result.m_v2 = m_v2 / vecNorm; 190 | result.m_v3 = m_v3 / vecNorm; 191 | result.m_v4 = m_v4 / vecNorm; 192 | 193 | return result; 194 | } 195 | 196 | // Normalize the vector in place. 197 | template 198 | void qbVector4::Normalize() 199 | { 200 | // Compute the vector norm. 201 | T vecNorm = this->norm(); 202 | T denominator = static_cast(1.0) / vecNorm; 203 | 204 | m_v1 = m_v1 / denominator; 205 | m_v2 = m_v2 / denominator; 206 | m_v3 = m_v3 / denominator; 207 | m_v4 = m_v4 / denominator; 208 | } 209 | 210 | /* ************************************************************************************************** 211 | OVERLOADED OPERATORS 212 | /* *************************************************************************************************/ 213 | template 214 | qbVector4 qbVector4::operator+ (const qbVector4 &rhs) const 215 | { 216 | qbVector4 result; 217 | result.m_v1 = m_v1 + rhs.m_v1; 218 | result.m_v2 = m_v2 + rhs.m_v2; 219 | result.m_v3 = m_v3 + rhs.m_v3; 220 | result.m_v4 = m_v4 + rhs.m_v4; 221 | 222 | return result; 223 | } 224 | 225 | template 226 | qbVector4 qbVector4::operator- (const qbVector4 &rhs) const 227 | { 228 | qbVector4 result; 229 | result.m_v1 = m_v1 - rhs.m_v1; 230 | result.m_v2 = m_v2 - rhs.m_v2; 231 | result.m_v3 = m_v3 - rhs.m_v3; 232 | result.m_v4 = m_v4 - rhs.m_v4; 233 | 234 | return result; 235 | } 236 | 237 | template 238 | qbVector4 qbVector4::operator* (const T &rhs) const 239 | { 240 | qbVector4 result; 241 | result.m_v1 = m_v1 * rhs; 242 | result.m_v2 = m_v2 * rhs; 243 | result.m_v3 = m_v3 * rhs; 244 | result.m_v4 = m_v4 * rhs; 245 | 246 | return result; 247 | } 248 | 249 | /* ************************************************************************************************** 250 | THE ASSIGNMENT (=) OPERATOR 251 | /* *************************************************************************************************/ 252 | template 253 | qbVector4 qbVector4::operator= (const qbVector &rhs) 254 | { 255 | if (rhs.GetNumDims() != 4) 256 | throw std::invalid_argument("Cannot assign qbVector to qbVector4 - assignment dimension mismatch."); 257 | 258 | m_v1 = rhs.GetElement(0); 259 | m_v2 = rhs.GetElement(1); 260 | m_v3 = rhs.GetElement(2); 261 | m_v4 = rhs.GetElement(3); 262 | 263 | return *this; 264 | } 265 | 266 | template 267 | qbVector4 qbVector4::operator= (const std::vector &rhs) 268 | { 269 | if (rhs.size() != 4) 270 | throw std::invalid_argument("Cannot assign std::vector to qbVector4 - assignment dimension mismatch."); 271 | 272 | m_v1 = rhs.at(0); 273 | m_v2 = rhs.at(1); 274 | m_v3 = rhs.at(2); 275 | m_v4 = rhs.at(3); 276 | 277 | return *this; 278 | } 279 | 280 | template 281 | qbVector4 qbVector4::operator= (const qbVector4 &rhs) 282 | { 283 | m_v1 = rhs.m_v1; 284 | m_v2 = rhs.m_v2; 285 | m_v3 = rhs.m_v3; 286 | m_v4 = rhs.m_v4; 287 | 288 | return *this; 289 | } 290 | 291 | /* ************************************************************************************************** 292 | FRIEND FUNCTIONS 293 | /* *************************************************************************************************/ 294 | template 295 | qbVector4 operator* (const T &lhs, const qbVector4 &rhs) 296 | { 297 | // Perform scalar multiplication. 298 | qbVector4 result; 299 | result.m_v1 = lhs * rhs.m_v1; 300 | result.m_v2 = lhs * rhs.m_v2; 301 | result.m_v3 = lhs * rhs.m_v3; 302 | result.m_v4 = lhs * rhs.m_v4; 303 | 304 | return result; 305 | } 306 | 307 | /* ************************************************************************************************** 308 | STATIC FUNCTIONS 309 | /* *************************************************************************************************/ 310 | template 311 | T qbVector4::dot(const qbVector4 &a, const qbVector4 &b) 312 | { 313 | T cumulativeSum = (a.m_v1 * b.m_v1) + (a.m_v2 * b.m_v2) + (a.m_v3 * b.m_v3) + (a.m_v4 * b.m_v4); 314 | 315 | return cumulativeSum; 316 | } 317 | 318 | /* ************************************************************************************************** 319 | FUNCTIONS FOR COMPATIBILITY 320 | /* *************************************************************************************************/ 321 | template 322 | int qbVector4::GetNumDims() const 323 | { 324 | return 4; 325 | } 326 | 327 | template 328 | T qbVector4::GetElement(int index) const 329 | { 330 | if (index == 0) 331 | { 332 | return m_v1; 333 | } 334 | else if (index == 1) 335 | { 336 | return m_v2; 337 | } 338 | else if (index == 2) 339 | { 340 | return m_v3; 341 | } 342 | else if (index == 3) 343 | { 344 | return m_v4; 345 | } 346 | else 347 | { 348 | throw std::invalid_argument("Attempt to get invalid element index."); 349 | } 350 | return 0; 351 | } 352 | 353 | template 354 | void qbVector4::SetElement(int index, T value) 355 | { 356 | if (index == 0) 357 | { 358 | m_v1 = value; 359 | } 360 | else if (index == 1) 361 | { 362 | m_v2 = value; 363 | } 364 | else if (index == 2) 365 | { 366 | m_v3 = value; 367 | } 368 | else if (index == 3) 369 | { 370 | m_v4 = value; 371 | } 372 | else 373 | { 374 | throw std::invalid_argument("Attempt to set invalid element index."); 375 | } 376 | } 377 | 378 | #endif 379 | -------------------------------------------------------------------------------- /qbVector.h: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | 3 | /* 4 | MIT License 5 | Copyright (c) 2023 Michael Bennett 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 8 | and associated documentation files (the "Software"), to deal in the Software without restriction, 9 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 10 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or 14 | substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 17 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER 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 SOFTWARE. 21 | */ 22 | 23 | #ifndef QBVECTOR_H 24 | #define QBVECTOR_H 25 | 26 | /* ************************************************************************************************* 27 | 28 | qbVector 29 | 30 | Class to provide capability to handle vectors. 31 | 32 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 33 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 34 | YouTube channel at: 35 | 36 | www.youtube.com/c/QuantitativeBytes 37 | 38 | ************************************************************************************************* */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | template 47 | class qbVector 48 | { 49 | public: 50 | // Define the various constructors. 51 | // Default. 52 | qbVector(); 53 | 54 | // With a single integer specifying the number of dimensions. 55 | qbVector(int numDims); 56 | 57 | // With input data (std::vector). 58 | qbVector(std::vector inputData); 59 | 60 | // And the destructor. 61 | ~qbVector(); 62 | 63 | // Functions to return parameters of the vector. 64 | int GetNumDims() const; 65 | 66 | // Functions to handle elements of the vector. 67 | T GetElement(int index) const; 68 | void SetElement(int index, T value); 69 | 70 | // Functions to perform computations on the vector. 71 | // Return the length of the vector. 72 | T norm(); 73 | 74 | // Return a normalized copy of the vector. 75 | qbVector Normalized(); 76 | 77 | // Normalize the vector in place. 78 | void Normalize(); 79 | 80 | // Overloaded operators. 81 | qbVector operator+ (const qbVector &rhs) const; 82 | qbVector operator- (const qbVector &rhs) const; 83 | qbVector operator* (const T &rhs) const; 84 | 85 | // Friend functions. 86 | template friend qbVector operator* (const U &lhs, const qbVector &rhs); 87 | 88 | // Static functions. 89 | static T dot(const qbVector &a, const qbVector &b); 90 | static qbVector cross(const qbVector &a, const qbVector &b); 91 | 92 | private: 93 | std::vector m_vectorData; 94 | int m_nDims; 95 | 96 | }; 97 | 98 | /* ************************************************************************************************** 99 | CONSTRUCTOR / DESTRUCTOR FUNCTIONS 100 | /* *************************************************************************************************/ 101 | // The default constructor. 102 | template 103 | qbVector::qbVector() 104 | { 105 | m_nDims = 0; 106 | m_vectorData = std::vector(); 107 | } 108 | 109 | template 110 | qbVector::qbVector(int numDims) 111 | { 112 | m_nDims = numDims; 113 | m_vectorData = std::vector(numDims, static_cast(0.0)); 114 | } 115 | 116 | template 117 | qbVector::qbVector(std::vector inputData) 118 | { 119 | m_nDims = inputData.size(); 120 | m_vectorData = inputData; 121 | } 122 | 123 | template 124 | qbVector::~qbVector() 125 | { 126 | // For now, we don't need to do anything in the destructor. 127 | } 128 | 129 | /* ************************************************************************************************** 130 | FUNCTIONS TO RETURN PARAMETERS 131 | /* *************************************************************************************************/ 132 | template 133 | int qbVector::GetNumDims() const 134 | { 135 | return m_nDims; 136 | } 137 | 138 | /* ************************************************************************************************** 139 | FUNCTIONS TO HANDLE ELEMENTS OF THE VECTOR 140 | /* *************************************************************************************************/ 141 | template 142 | T qbVector::GetElement(int index) const 143 | { 144 | return m_vectorData[index]; 145 | } 146 | 147 | template 148 | void qbVector::SetElement(int index, T value) 149 | { 150 | m_vectorData[index] = value; 151 | } 152 | 153 | /* ************************************************************************************************** 154 | FUNCTIONS TO PERFORM COMPUTATIONS ON THE VECTOR 155 | /* *************************************************************************************************/ 156 | // Compute the length of the vector,known as the 'norm'. 157 | template 158 | T qbVector::norm() 159 | { 160 | T cumulativeSum = static_cast(0.0); 161 | for (int i=0; i 169 | qbVector qbVector::Normalized() 170 | { 171 | // Compute the vector norm. 172 | T vecNorm = this->norm(); 173 | 174 | // Compute the normalized version of the vector. 175 | qbVector result(m_vectorData); 176 | return result * (static_cast(1.0) / vecNorm); 177 | } 178 | 179 | // Normalize the vector in place. 180 | template 181 | void qbVector::Normalize() 182 | { 183 | // Compute the vector norm. 184 | T vecNorm = this->norm(); 185 | 186 | // Compute the elements of the normalized version of the vector. 187 | for (int i=0; i(1.0) / vecNorm); 190 | m_vectorData.at(i) = temp; 191 | } 192 | } 193 | 194 | /* ************************************************************************************************** 195 | OVERLOADED OPERATORS 196 | Note the changes that have been made since the videos about this first code were made. The original 197 | code has been left, but commented out. Ultimately it was necessary to make some changes to 198 | improve the performance of these functions in terms of run time. 199 | See this episode of my ray tracing in C++ series for further information: 200 | https://youtu.be/-5kLk7_bs0U 201 | /* *************************************************************************************************/ 202 | template 203 | qbVector qbVector::operator+ (const qbVector &rhs) const 204 | { 205 | // Check that the number of dimensions match. 206 | if (m_nDims != rhs.m_nDims) 207 | throw std::invalid_argument("Vector dimensions do not match."); 208 | 209 | /* 210 | std::vector resultData; 211 | for (int i=0; i result(resultData); 215 | return result; 216 | */ 217 | qbVector resultData (m_nDims); 218 | for (int i=0; i 225 | qbVector qbVector::operator- (const qbVector &rhs) const 226 | { 227 | // Check that the number of dimensions match. 228 | if (m_nDims != rhs.m_nDims) 229 | throw std::invalid_argument("Vector dimensions do not match."); 230 | 231 | /* 232 | std::vector resultData; 233 | for (int i=0; i result(resultData); 237 | return result; 238 | */ 239 | qbVector resultData (m_nDims); 240 | for (int i=0; i 247 | qbVector qbVector::operator* (const T &rhs) const 248 | { 249 | // Perform scalar multiplication. 250 | /* 251 | std::vector resultData; 252 | for (int i=0; i result(resultData); 256 | return result; 257 | */ 258 | qbVector resultData (m_nDims); 259 | for (int i=0; i 269 | qbVector operator* (const T &lhs, const qbVector &rhs) 270 | { 271 | // Perform scalar multiplication. 272 | /* 273 | std::vector resultData; 274 | for (int i=0; i result(resultData); 278 | return result; 279 | */ 280 | std::vector resultData (rhs.m_nDims); 281 | for (int i=0; i result(resultData); 285 | return result; 286 | } 287 | 288 | /* ************************************************************************************************** 289 | STATIC FUNCTIONS 290 | /* *************************************************************************************************/ 291 | template 292 | T qbVector::dot(const qbVector &a, const qbVector &b) 293 | { 294 | // Check that the number of dimensions match. 295 | if (a.m_nDims != b.m_nDims) 296 | throw std::invalid_argument("Vector dimensions must match for the dot-product to be computed."); 297 | 298 | // Compute the dot product. 299 | T cumulativeSum = static_cast(0.0); 300 | for (int i=0; i 307 | qbVector qbVector::cross(const qbVector &a, const qbVector &b) 308 | { 309 | // Check that the number of dimensions match. 310 | if (a.m_nDims != b.m_nDims) 311 | throw std::invalid_argument("Vector dimensions must match for the cross-product to be computed."); 312 | 313 | // Check that the number of dimensions is 3. 314 | /* Although the cross-product is also defined for 7 dimensions, we are 315 | not going to consider that case at this time. */ 316 | if (a.m_nDims != 3) 317 | throw std::invalid_argument("The cross-product can only be computed for three-dimensional vectors."); 318 | 319 | // Compute the cross product. 320 | std::vector resultData; 321 | resultData.push_back((a.m_vectorData.at(1) * b.m_vectorData.at(2)) - (a.m_vectorData.at(2) * b.m_vectorData.at(1))); 322 | resultData.push_back(-((a.m_vectorData.at(0) * b.m_vectorData.at(2)) - (a.m_vectorData.at(2) * b.m_vectorData.at(0)))); 323 | resultData.push_back((a.m_vectorData.at(0) * b.m_vectorData.at(1)) - (a.m_vectorData.at(1) * b.m_vectorData.at(0))); 324 | 325 | qbVector result(resultData); 326 | return result; 327 | } 328 | 329 | #endif 330 | -------------------------------------------------------------------------------- /qbMatrix44.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | 3 | /* 4 | MIT License 5 | Copyright (c) 2023 Michael Bennett 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 8 | and associated documentation files (the "Software"), to deal in the Software without restriction, 9 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 10 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or 14 | substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 17 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER 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 SOFTWARE. 21 | */ 22 | 23 | #ifndef qbMatrix44_H 24 | #define qbMatrix44_H 25 | 26 | /* ************************************************************************************************* 27 | 28 | qbMatrix44 29 | 30 | Class to provide capability to handle two-dimensional matrices. 31 | 32 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 33 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 34 | YouTube channel at: 35 | 36 | www.youtube.com/c/QuantitativeBytes 37 | 38 | ************************************************************************************************* */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include "qbVector.h" 47 | #include "qbVector3.hpp" 48 | #include "qbVector4.hpp" 49 | #include "qbMatrix33.hpp" 50 | 51 | template 52 | class qbMatrix44 53 | { 54 | public: 55 | // Define the various constructors. 56 | qbMatrix44(); 57 | qbMatrix44(const qbMatrix44 &inputMatrix); 58 | qbMatrix44(const std::vector &inputData); 59 | 60 | // And the destructor. 61 | ~qbMatrix44(); 62 | 63 | // Configuration methods. 64 | void SetToIdentity(); 65 | 66 | // Element access methods. 67 | T GetElement(int row, int col) const; 68 | bool SetElement(int row, int col, T elementValue); 69 | int GetNumRows() const; 70 | int GetNumCols() const; 71 | 72 | // Manipulation methods. 73 | // Compute matrix inverse. 74 | bool Inverse(); 75 | 76 | // Return the transpose. 77 | qbMatrix44 Transpose() const; 78 | 79 | // Compute determinant. 80 | T Determinant(); 81 | 82 | // Overload == operator. 83 | bool operator== (const qbMatrix44& rhs); 84 | bool Compare (const qbMatrix44& matrix1, double tolerance); 85 | 86 | // Overload the assignment operator. 87 | qbMatrix44 operator= (const qbMatrix44 &rhs); 88 | 89 | // Overload +, - and * operators (friends). 90 | template friend qbMatrix44 operator+ (const qbMatrix44& lhs, const qbMatrix44& rhs); 91 | template friend qbMatrix44 operator+ (const U& lhs, const qbMatrix44& rhs); 92 | template friend qbMatrix44 operator+ (const qbMatrix44& lhs, const U& rhs); 93 | 94 | template friend qbMatrix44 operator- (const qbMatrix44& lhs, const qbMatrix44& rhs); 95 | template friend qbMatrix44 operator- (const U& lhs, const qbMatrix44& rhs); 96 | template friend qbMatrix44 operator- (const qbMatrix44& lhs, const U& rhs); 97 | 98 | template friend qbMatrix44 operator* (const qbMatrix44& lhs, const qbMatrix44& rhs); 99 | template friend qbMatrix44 operator* (const U& lhs, const qbMatrix44& rhs); 100 | template friend qbMatrix44 operator* (const qbMatrix44& lhs, const U& rhs); 101 | 102 | // qbMatrix44 * qbVector4. 103 | template friend qbVector4 operator* (const qbMatrix44& lhs, const qbVector4& rhs); 104 | 105 | qbMatrix33 FindSubMatrix(int rowNum, int colNum); 106 | 107 | private: 108 | int Sub2Ind(int row, int col) const; 109 | bool CloseEnough(T f1, T f2); 110 | 111 | public: 112 | //T *m_matrixData; 113 | T m_matrixData[16]; 114 | int m_nRows = 4; 115 | int m_nCols = 4; 116 | int m_nElements = 16; 117 | }; 118 | 119 | /* ************************************************************************************************** 120 | CONSTRUCTOR / DESTRUCTOR FUNCTIONS 121 | /* *************************************************************************************************/ 122 | // The default constructor. 123 | template 124 | qbMatrix44::qbMatrix44() 125 | { 126 | 127 | } 128 | 129 | // The copy constructor. 130 | template 131 | qbMatrix44::qbMatrix44(const qbMatrix44 &inputMatrix) 132 | { 133 | for (int i=0; i<16; i++) 134 | m_matrixData[i] = inputMatrix.m_matrixData[i]; 135 | } 136 | 137 | // Construct from std::vector. 138 | template 139 | qbMatrix44::qbMatrix44(const std::vector &inputData) 140 | { 141 | if (inputData.size() != 16) 142 | throw std::invalid_argument("Cannot assign std::vector to qbMatrix44 - assignment dimension mismatch."); 143 | 144 | for (int i=0; i<16; ++i) 145 | m_matrixData[i] = inputData.at(i); 146 | } 147 | 148 | template 149 | qbMatrix44::~qbMatrix44() 150 | { 151 | 152 | } 153 | 154 | /* ************************************************************************************************** 155 | CONFIGURATION FUNCTIONS 156 | /* *************************************************************************************************/ 157 | // Function to convert the existing matrix into an identity matrix. 158 | template 159 | void qbMatrix44::SetToIdentity() 160 | { 161 | for (int row=0; row 177 | T qbMatrix44::GetElement(int row, int col) const 178 | { 179 | int linearIndex = Sub2Ind(row, col); 180 | if (linearIndex >= 0) 181 | return m_matrixData[linearIndex]; 182 | else 183 | return 0.0; 184 | 185 | } 186 | 187 | template 188 | bool qbMatrix44::SetElement(int row, int col, T elementValue) 189 | { 190 | int linearIndex = Sub2Ind(row, col); 191 | if (linearIndex >= 0) 192 | { 193 | m_matrixData[linearIndex] = elementValue; 194 | return true; 195 | } 196 | else 197 | { 198 | return false; 199 | } 200 | } 201 | 202 | template 203 | int qbMatrix44::GetNumRows() const 204 | { 205 | return m_nRows; 206 | } 207 | 208 | template 209 | int qbMatrix44::GetNumCols() const 210 | { 211 | return m_nCols; 212 | } 213 | 214 | template 215 | bool qbMatrix44::Compare(const qbMatrix44& matrix1, double tolerance) 216 | { 217 | // First, check that the matrices have the same dimensions. 218 | int numRows1 = matrix1.m_nRows; 219 | int numCols1 = matrix1.m_nCols; 220 | if ((numRows1 != m_nRows) || (numCols1 != m_nCols)) 221 | return false; 222 | 223 | // Loop over all the elements and compute the sum-of-squared-differences. 224 | double cumulativeSum = 0.0; 225 | for (int i=0; i 247 | qbMatrix44 operator+ (const qbMatrix44& lhs, const qbMatrix44& rhs) 248 | { 249 | qbMatrix44 result; 250 | for (int i=0; i<16; ++i) 251 | result.m_matrixData[i] = lhs.m_matrixData[i] + rhs.m_matrixData[i]; 252 | 253 | return result; 254 | } 255 | 256 | // scaler + matrix 257 | template 258 | qbMatrix44 operator+ (const T& lhs, const qbMatrix44& rhs) 259 | { 260 | qbMatrix44 result; 261 | for (int i=0; i<16; ++i) 262 | result.m_matrixData[i] = lhs + rhs.m_matrixData[i]; 263 | 264 | return result; 265 | } 266 | 267 | // matrix + scaler 268 | template 269 | qbMatrix44 operator+ (const qbMatrix44& lhs, const T& rhs) 270 | { 271 | qbMatrix44 result; 272 | for (int i=0; i<16; ++i) 273 | result.m_matrixData[i] = lhs.m_matrixData[i] + rhs; 274 | 275 | return result; 276 | } 277 | 278 | /* ************************************************************************************************** 279 | THE - OPERATOR 280 | /* *************************************************************************************************/ 281 | // matrix - matrix 282 | template 283 | qbMatrix44 operator- (const qbMatrix44& lhs, const qbMatrix44& rhs) 284 | { 285 | qbMatrix44 result; 286 | for (int i=0; i<16; ++i) 287 | result.m_matrixData[i] = lhs.m_matrixData[i] - rhs.m_matrixData[i]; 288 | 289 | return result; 290 | } 291 | 292 | // scaler - matrix 293 | template 294 | qbMatrix44 operator- (const T& lhs, const qbMatrix44& rhs) 295 | { 296 | qbMatrix44 result; 297 | for (int i=0; i<16; ++i) 298 | result.m_matrixData[i] = lhs - rhs.m_matrixData[i]; 299 | 300 | return result; 301 | } 302 | 303 | // matrix - scaler 304 | template 305 | qbMatrix44 operator- (const qbMatrix44& lhs, const T& rhs) 306 | { 307 | qbMatrix44 result; 308 | for (int i=0; i<16; ++i) 309 | result.m_matrixData[i] = lhs.m_matrixData[i] - rhs; 310 | 311 | return result; 312 | } 313 | 314 | /* ************************************************************************************************** 315 | THE * OPERATOR 316 | /* *************************************************************************************************/ 317 | // matrix * qbVector4 318 | template 319 | qbVector4 operator* (const qbMatrix44& lhs, const qbVector4& rhs) 320 | { 321 | // Setup the vector for the output. 322 | qbVector4 result; 323 | 324 | // Loop over rows and columns and perform the multiplication operation element-by-element. 325 | for (int row=0; row(0.0); 328 | cumulativeSum += (lhs.GetElement(row,0) * rhs.m_v1); 329 | cumulativeSum += (lhs.GetElement(row,1) * rhs.m_v2); 330 | cumulativeSum += (lhs.GetElement(row,2) * rhs.m_v3); 331 | cumulativeSum += (lhs.GetElement(row,3) * rhs.m_v4); 332 | 333 | result.SetElement(row, cumulativeSum); 334 | } 335 | 336 | return result; 337 | } 338 | 339 | // scaler * matrix 340 | template 341 | qbMatrix44 operator* (const T& lhs, const qbMatrix44& rhs) 342 | { 343 | qbMatrix44 result; 344 | for (int i=0; i<16; ++i) 345 | result.m_matrixData[i] = lhs * rhs.m_matrixData[i]; 346 | 347 | return result; 348 | } 349 | 350 | // matrix * scaler 351 | template 352 | qbMatrix44 operator* (const qbMatrix44& lhs, const T& rhs) 353 | { 354 | qbMatrix44 result; 355 | for (int i=0; i<16; ++i) 356 | result.m_matrixData[i] = lhs.m_matrixData[i] * rhs; 357 | 358 | return result; 359 | } 360 | 361 | // matrix * matrix 362 | template 363 | qbMatrix44 operator* (const qbMatrix44& lhs, const qbMatrix44& rhs) 364 | { 365 | int r_numRows = rhs.m_nRows; 366 | int r_numCols = rhs.m_nCols; 367 | int l_numRows = lhs.m_nRows; 368 | int l_numCols = lhs.m_nCols; 369 | 370 | // This is the standard matrix multiplication condition. 371 | // The output will be the same size as the RHS. 372 | qbMatrix44 result; 373 | 374 | // Loop through each row of the LHS. 375 | for (int lhsRow=0; lhsRow(0.0); 381 | 382 | // Loop through each element of this LHS row. 383 | for (int lhsCol=0; lhsCol 407 | bool qbMatrix44::operator== (const qbMatrix44& rhs) 408 | { 409 | // Check if the elements are equal. 410 | bool flag = true; 411 | for (int i=0; i<16; ++i) 412 | { 413 | //if (this->m_matrixData[i] != rhs.m_matrixData[i]) 414 | if (!CloseEnough(this->m_matrixData[i], rhs.m_matrixData[i])) 415 | flag = false; 416 | } 417 | return flag; 418 | } 419 | 420 | /* ************************************************************************************************** 421 | THE ASSIGNMENT (=) OPERATOR 422 | /* *************************************************************************************************/ 423 | template 424 | qbMatrix44 qbMatrix44::operator= (const qbMatrix44 &rhs) 425 | { 426 | // Make sure we're not assigning to ourself. 427 | if (this != &rhs) 428 | { 429 | for (int i=0; i 440 | qbMatrix44 qbMatrix44::Transpose() const 441 | { 442 | qbMatrix44 result; 443 | 444 | for (int i=0; i<4; ++i) 445 | { 446 | for (int j=0; j<4; ++j) 447 | { 448 | result.SetElement(j, i, this->GetElement(i, j)); 449 | } 450 | } 451 | 452 | return result; 453 | } 454 | 455 | /* ************************************************************************************************** 456 | INVERSE 457 | /* *************************************************************************************************/ 458 | template 459 | bool qbMatrix44::Inverse() 460 | { 461 | // Construct the adjucate matrix. 462 | qbMatrix44 adjugate; 463 | 464 | int sign = 1; 465 | int rowSign = 1; 466 | for (int row=0; row<4; ++row) 467 | { 468 | for (int col=0; col<4; ++col) 469 | { 470 | qbMatrix33 subMatrix = FindSubMatrix(row, col); 471 | T subMatrixDeterminant = subMatrix.Determinant(); 472 | adjugate.SetElement(row, col, static_cast(sign) * subMatrixDeterminant); 473 | sign *= -1; 474 | } 475 | rowSign *= -1; 476 | sign = rowSign; 477 | } 478 | 479 | qbMatrix44 adjT = adjugate.Transpose(); 480 | 481 | // Compute the inverse. 482 | T determinant = Determinant(); 483 | if (CloseEnough(determinant, 0.0)) 484 | return false; 485 | 486 | qbMatrix44 result = (1.0 / determinant) * adjT; 487 | 488 | // And store 'in place'. 489 | for (int i=0; i<16; ++i) 490 | m_matrixData[i] = result.m_matrixData[i]; 491 | 492 | return true; 493 | } 494 | 495 | /* ************************************************************************************************** 496 | DETERMINANT 497 | /* *************************************************************************************************/ 498 | template 499 | T qbMatrix44::Determinant() 500 | { 501 | T cumulativeSum = static_cast(0.0); 502 | int sign = 1; 503 | 504 | int row = 0; 505 | for (int col=0; col<4; ++col) 506 | { 507 | qbMatrix33 subMatrix = FindSubMatrix(row, col); 508 | T subMatrixDeterminant = subMatrix.Determinant(); 509 | cumulativeSum += (static_cast(sign) * GetElement(row, col) * subMatrixDeterminant); 510 | sign *= -1; 511 | } 512 | 513 | return cumulativeSum; 514 | } 515 | 516 | /* ************************************************************************************************** 517 | PRIVATE FUNCTIONS 518 | /* *************************************************************************************************/ 519 | // Function to return the linear index corresponding to the supplied row and column values. 520 | template 521 | int qbMatrix44::Sub2Ind(int row, int col) const 522 | { 523 | if ((row < m_nRows) && (row >= 0) && (col < m_nCols) && (col >= 0)) 524 | return (row * m_nCols) + col; 525 | else 526 | return -1; 527 | } 528 | 529 | // Function to find the sub-matrix for the given element. 530 | template 531 | qbMatrix33 qbMatrix44::FindSubMatrix(int rowNum, int colNum) 532 | { 533 | // Create a new matrix to store the sub-matrix. 534 | // Note that this is one row and one column smaller than the original. 535 | qbMatrix33 subMatrix; 536 | 537 | // Loop over the elements of the existing matrix and copy to sub-matrix as appropriate. 538 | int count = 0; 539 | for (int i=0; iGetElement(i,j); 547 | count++; 548 | } 549 | } 550 | } 551 | 552 | return subMatrix; 553 | } 554 | 555 | template 556 | bool qbMatrix44::CloseEnough(T f1, T f2) 557 | { 558 | return fabs(f1-f2) < 1e-9; 559 | } 560 | 561 | #endif 562 | -------------------------------------------------------------------------------- /qbMatrix33.hpp: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | 3 | /* 4 | MIT License 5 | Copyright (c) 2023 Michael Bennett 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 8 | and associated documentation files (the "Software"), to deal in the Software without restriction, 9 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 10 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or 14 | substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 17 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER 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 SOFTWARE. 21 | */ 22 | 23 | #ifndef qbMatrix33_H 24 | #define qbMatrix33_H 25 | 26 | /* ************************************************************************************************* 27 | 28 | qbMatrix33 29 | 30 | Class to provide capability to handle two-dimensional matrices. 31 | 32 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 33 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 34 | YouTube channel at: 35 | 36 | www.youtube.com/c/QuantitativeBytes 37 | 38 | ************************************************************************************************* */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include "qbVector.h" 47 | #include "qbVector3.hpp" 48 | #include "qbVector4.hpp" 49 | 50 | template 51 | class qbMatrix33 52 | { 53 | public: 54 | // Define the various constructors. 55 | qbMatrix33(); 56 | qbMatrix33(const qbMatrix33 &inputMatrix); 57 | qbMatrix33(const std::vector &inputData); 58 | 59 | // And the destructor. 60 | ~qbMatrix33(); 61 | 62 | // Configuration methods. 63 | void SetToIdentity(); 64 | 65 | // Element access methods. 66 | T GetElement(int row, int col) const; 67 | bool SetElement(int row, int col, T elementValue); 68 | int GetNumRows() const; 69 | int GetNumCols() const; 70 | 71 | // Manipulation methods. 72 | // Compute matrix inverse. 73 | bool Inverse(); 74 | 75 | // Return the transpose. 76 | qbMatrix33 Transpose() const; 77 | 78 | // Compute determinant. 79 | T Determinant(); 80 | 81 | // Overload == operator. 82 | bool operator== (const qbMatrix33& rhs); 83 | bool Compare (const qbMatrix33& matrix1, double tolerance); 84 | 85 | // Overload the assignment operator. 86 | qbMatrix33 operator= (const qbMatrix33 &rhs); 87 | 88 | // Overload +, - and * operators (friends). 89 | template friend qbMatrix33 operator+ (const qbMatrix33& lhs, const qbMatrix33& rhs); 90 | template friend qbMatrix33 operator+ (const U& lhs, const qbMatrix33& rhs); 91 | template friend qbMatrix33 operator+ (const qbMatrix33& lhs, const U& rhs); 92 | 93 | template friend qbMatrix33 operator- (const qbMatrix33& lhs, const qbMatrix33& rhs); 94 | template friend qbMatrix33 operator- (const U& lhs, const qbMatrix33& rhs); 95 | template friend qbMatrix33 operator- (const qbMatrix33& lhs, const U& rhs); 96 | 97 | template friend qbMatrix33 operator* (const qbMatrix33& lhs, const qbMatrix33& rhs); 98 | template friend qbMatrix33 operator* (const U& lhs, const qbMatrix33& rhs); 99 | template friend qbMatrix33 operator* (const qbMatrix33& lhs, const U& rhs); 100 | 101 | // qbMatrix33 * qbVector3. 102 | template friend qbVector3 operator* (const qbMatrix33& lhs, const qbVector3& rhs); 103 | 104 | private: 105 | int Sub2Ind(int row, int col) const; 106 | T cofactorDeterminant(T e1, T e2, T e3, T e4); 107 | bool CloseEnough(T f1, T f2); 108 | 109 | public: 110 | //T *m_matrixData; 111 | T m_matrixData[9]; 112 | int m_nRows = 3; 113 | int m_nCols = 3; 114 | int m_nElements = 9; 115 | }; 116 | 117 | /* ************************************************************************************************** 118 | CONSTRUCTOR / DESTRUCTOR FUNCTIONS 119 | /* *************************************************************************************************/ 120 | // The default constructor. 121 | template 122 | qbMatrix33::qbMatrix33() 123 | { 124 | 125 | } 126 | 127 | // The copy constructor. 128 | template 129 | qbMatrix33::qbMatrix33(const qbMatrix33 &inputMatrix) 130 | { 131 | for (int i=0; i<9; i++) 132 | m_matrixData[i] = inputMatrix.m_matrixData[i]; 133 | } 134 | 135 | // Construct from std::vector. 136 | template 137 | qbMatrix33::qbMatrix33(const std::vector &inputData) 138 | { 139 | if (inputData.size() != 9) 140 | throw std::invalid_argument("Cannot assign std::vector to qbMatrix33 - assignment dimension mismatch."); 141 | 142 | for (int i=0; i<9; ++i) 143 | m_matrixData[i] = inputData.at(i); 144 | } 145 | 146 | template 147 | qbMatrix33::~qbMatrix33() 148 | { 149 | 150 | } 151 | 152 | /* ************************************************************************************************** 153 | CONFIGURATION FUNCTIONS 154 | /* *************************************************************************************************/ 155 | // Function to convert the existing matrix into an identity matrix. 156 | template 157 | void qbMatrix33::SetToIdentity() 158 | { 159 | for (int row=0; row 175 | T qbMatrix33::GetElement(int row, int col) const 176 | { 177 | int linearIndex = Sub2Ind(row, col); 178 | if (linearIndex >= 0) 179 | return m_matrixData[linearIndex]; 180 | else 181 | return 0.0; 182 | 183 | } 184 | 185 | template 186 | bool qbMatrix33::SetElement(int row, int col, T elementValue) 187 | { 188 | int linearIndex = Sub2Ind(row, col); 189 | if (linearIndex >= 0) 190 | { 191 | m_matrixData[linearIndex] = elementValue; 192 | return true; 193 | } 194 | else 195 | { 196 | return false; 197 | } 198 | } 199 | 200 | template 201 | int qbMatrix33::GetNumRows() const 202 | { 203 | return m_nRows; 204 | } 205 | 206 | template 207 | int qbMatrix33::GetNumCols() const 208 | { 209 | return m_nCols; 210 | } 211 | 212 | template 213 | bool qbMatrix33::Compare(const qbMatrix33& matrix1, double tolerance) 214 | { 215 | // First, check that the matrices have the same dimensions. 216 | int numRows1 = matrix1.m_nRows; 217 | int numCols1 = matrix1.m_nCols; 218 | if ((numRows1 != m_nRows) || (numCols1 != m_nCols)) 219 | return false; 220 | 221 | // Loop over all the elements and compute the sum-of-squared-differences. 222 | double cumulativeSum = 0.0; 223 | for (int i=0; i 245 | qbMatrix33 operator+ (const qbMatrix33& lhs, const qbMatrix33& rhs) 246 | { 247 | qbMatrix33 result; 248 | for (int i=0; i<9; ++i) 249 | result.m_matrixData[i] = lhs.m_matrixData[i] + rhs.m_matrixData[i]; 250 | 251 | return result; 252 | } 253 | 254 | // scaler + matrix 255 | template 256 | qbMatrix33 operator+ (const T& lhs, const qbMatrix33& rhs) 257 | { 258 | qbMatrix33 result; 259 | for (int i=0; i<9; ++i) 260 | result.m_matrixData[i] = lhs + rhs.m_matrixData[i]; 261 | 262 | return result; 263 | } 264 | 265 | // matrix + scaler 266 | template 267 | qbMatrix33 operator+ (const qbMatrix33& lhs, const T& rhs) 268 | { 269 | qbMatrix33 result; 270 | for (int i=0; i<9; ++i) 271 | result.m_matrixData[i] = lhs.m_matrixData[i] + rhs; 272 | 273 | return result; 274 | } 275 | 276 | /* ************************************************************************************************** 277 | THE - OPERATOR 278 | /* *************************************************************************************************/ 279 | // matrix - matrix 280 | template 281 | qbMatrix33 operator- (const qbMatrix33& lhs, const qbMatrix33& rhs) 282 | { 283 | qbMatrix33 result; 284 | for (int i=0; i<9; ++i) 285 | result.m_matrixData[i] = lhs.m_matrixData[i] - rhs.m_matrixData[i]; 286 | 287 | return result; 288 | } 289 | 290 | // scaler - matrix 291 | template 292 | qbMatrix33 operator- (const T& lhs, const qbMatrix33& rhs) 293 | { 294 | qbMatrix33 result; 295 | for (int i=0; i<9; ++i) 296 | result.m_matrixData[i] = lhs - rhs.m_matrixData[i]; 297 | 298 | return result; 299 | } 300 | 301 | // matrix - scaler 302 | template 303 | qbMatrix33 operator- (const qbMatrix33& lhs, const T& rhs) 304 | { 305 | qbMatrix33 result; 306 | for (int i=0; i<9; ++i) 307 | result.m_matrixData[i] = lhs.m_matrixData[i] - rhs; 308 | 309 | return result; 310 | } 311 | 312 | /* ************************************************************************************************** 313 | THE * OPERATOR 314 | /* *************************************************************************************************/ 315 | // matrix * qbVector3 316 | template 317 | qbVector3 operator* (const qbMatrix33& lhs, const qbVector3& rhs) 318 | { 319 | // Setup the vector for the output. 320 | qbVector3 result; 321 | 322 | // Loop over rows and columns and perform the multiplication operation element-by-element. 323 | for (int row=0; row(0.0); 326 | cumulativeSum += (lhs.GetElement(row,0) * rhs.m_x); 327 | cumulativeSum += (lhs.GetElement(row,1) * rhs.m_y); 328 | cumulativeSum += (lhs.GetElement(row,2) * rhs.m_z); 329 | 330 | result.SetElement(row, cumulativeSum); 331 | } 332 | 333 | return result; 334 | } 335 | 336 | // scaler * matrix 337 | template 338 | qbMatrix33 operator* (const T& lhs, const qbMatrix33& rhs) 339 | { 340 | qbMatrix33 result; 341 | for (int i=0; i<9; ++i) 342 | result.m_matrixData[i] = lhs * rhs.m_matrixData[i]; 343 | 344 | return result; 345 | } 346 | 347 | // matrix * scaler 348 | template 349 | qbMatrix33 operator* (const qbMatrix33& lhs, const T& rhs) 350 | { 351 | qbMatrix33 result; 352 | for (int i=0; i<9; ++i) 353 | result.m_matrixData[i] = lhs.m_matrixData[i] * rhs; 354 | 355 | return result; 356 | } 357 | 358 | // matrix * matrix 359 | template 360 | qbMatrix33 operator* (const qbMatrix33& lhs, const qbMatrix33& rhs) 361 | { 362 | int r_numRows = rhs.m_nRows; 363 | int r_numCols = rhs.m_nCols; 364 | int l_numRows = lhs.m_nRows; 365 | int l_numCols = lhs.m_nCols; 366 | 367 | // This is the standard matrix multiplication condition. 368 | // The output will be the same size as the RHS. 369 | //T *tempResult = new T[lhs.m_nRows * rhs.m_nCols]; 370 | qbMatrix33 result; 371 | 372 | // Loop through each row of the LHS. 373 | for (int lhsRow=0; lhsRow(0.0); 379 | 380 | // Loop through each element of this LHS row. 381 | for (int lhsCol=0; lhsCol 406 | bool qbMatrix33::operator== (const qbMatrix33& rhs) 407 | { 408 | // Check if the elements are equal. 409 | bool flag = true; 410 | for (int i=0; i<9; ++i) 411 | { 412 | if (!CloseEnough(this->m_matrixData[i], rhs.m_matrixData[i])) 413 | flag = false; 414 | } 415 | return flag; 416 | } 417 | 418 | /* ************************************************************************************************** 419 | THE ASSIGNMENT (=) OPERATOR 420 | /* *************************************************************************************************/ 421 | template 422 | qbMatrix33 qbMatrix33::operator= (const qbMatrix33 &rhs) 423 | { 424 | // Make sure we're not assigning to ourself. 425 | if (this != &rhs) 426 | { 427 | for (int i=0; i 438 | qbMatrix33 qbMatrix33::Transpose() const 439 | { 440 | qbMatrix33 result; 441 | 442 | for (int i=0; i<3; ++i) 443 | { 444 | for (int j=0; j<3; ++j) 445 | { 446 | result.SetElement(j, i, this->GetElement(i, j)); 447 | } 448 | } 449 | 450 | return result; 451 | } 452 | 453 | /* ************************************************************************************************** 454 | INVERSE 455 | /* *************************************************************************************************/ 456 | template 457 | bool qbMatrix33::Inverse() 458 | { 459 | // Compute the adjucate matrix. 460 | qbMatrix33 adjugate; 461 | adjugate.SetElement(0, 0, cofactorDeterminant(m_matrixData[4], m_matrixData[5], m_matrixData[7], m_matrixData[8])); 462 | adjugate.SetElement(0, 1, -cofactorDeterminant(m_matrixData[3], m_matrixData[5], m_matrixData[6], m_matrixData[8])); 463 | adjugate.SetElement(0, 2, cofactorDeterminant(m_matrixData[3], m_matrixData[4], m_matrixData[6], m_matrixData[7])); 464 | 465 | adjugate.SetElement(1, 0, -cofactorDeterminant(m_matrixData[1], m_matrixData[2], m_matrixData[7], m_matrixData[8])); 466 | adjugate.SetElement(1, 1, cofactorDeterminant(m_matrixData[0], m_matrixData[2], m_matrixData[6], m_matrixData[8])); 467 | adjugate.SetElement(1, 2, -cofactorDeterminant(m_matrixData[0], m_matrixData[1], m_matrixData[6], m_matrixData[7])); 468 | 469 | adjugate.SetElement(2, 0, cofactorDeterminant(m_matrixData[1], m_matrixData[2], m_matrixData[4], m_matrixData[5])); 470 | adjugate.SetElement(2, 1, -cofactorDeterminant(m_matrixData[0], m_matrixData[2], m_matrixData[3],m_matrixData[5])); 471 | adjugate.SetElement(2, 2, cofactorDeterminant(m_matrixData[0], m_matrixData[1], m_matrixData[3], m_matrixData[4])); 472 | 473 | // And transpose. 474 | qbMatrix33 adjT = adjugate.Transpose(); 475 | 476 | // Compute the inverse. 477 | T determinant = Determinant(); 478 | if (CloseEnough(determinant, 0.0)) 479 | return false; 480 | 481 | qbMatrix33 result = (1.0 / determinant) * adjT; 482 | 483 | // And store 'in place'. 484 | for (int i=0; i<9; ++i) 485 | m_matrixData[i] = result.m_matrixData[i]; 486 | 487 | return true; 488 | } 489 | 490 | /* ************************************************************************************************** 491 | DETERMINANT 492 | /* *************************************************************************************************/ 493 | template 494 | T qbMatrix33::Determinant() 495 | { 496 | T result = (m_matrixData[0] * m_matrixData[4] * m_matrixData[8]) + 497 | (m_matrixData[1] * m_matrixData[5] * m_matrixData[6]) + 498 | (m_matrixData[2] * m_matrixData[3] * m_matrixData[7]) - 499 | (m_matrixData[2] * m_matrixData[4] * m_matrixData[6]) - 500 | (m_matrixData[1] * m_matrixData[3] * m_matrixData[8]) - 501 | (m_matrixData[0] * m_matrixData[5] * m_matrixData[7]); 502 | 503 | return result; 504 | } 505 | 506 | /* ************************************************************************************************** 507 | PRIVATE FUNCTIONS 508 | /* *************************************************************************************************/ 509 | // Function to return the linear index corresponding to the supplied row and column values. 510 | template 511 | int qbMatrix33::Sub2Ind(int row, int col) const 512 | { 513 | if ((row < m_nRows) && (row >= 0) && (col < m_nCols) && (col >= 0)) 514 | return (row * m_nCols) + col; 515 | else 516 | return -1; 517 | } 518 | 519 | // Function to compute the determinant of a 2x2 cofactor matrix. 520 | template 521 | T qbMatrix33::cofactorDeterminant(T e1, T e2, T e3, T e4) 522 | { 523 | return (e1 * e4) - (e2 * e3); 524 | } 525 | 526 | template 527 | bool qbMatrix33::CloseEnough(T f1, T f2) 528 | { 529 | return fabs(f1-f2) < 1e-9; 530 | } 531 | 532 | #endif 533 | -------------------------------------------------------------------------------- /qbMatrix.h: -------------------------------------------------------------------------------- 1 | // This file is part of the qbLinAlg linear algebra library. 2 | 3 | /* 4 | MIT License 5 | Copyright (c) 2023 Michael Bennett 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 8 | and associated documentation files (the "Software"), to deal in the Software without restriction, 9 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 10 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or 14 | substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 17 | BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER 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 SOFTWARE. 21 | */ 22 | 23 | #ifndef QBMATRIX2_H 24 | #define QBMATRIX2_H 25 | 26 | /* ************************************************************************************************* 27 | 28 | qbMatrix2 29 | 30 | Class to provide capability to handle two-dimensional matrices. 31 | 32 | Created as part of the qbLinAlg linear algebra library, which is intended to be primarily for 33 | educational purposes. For more details, see the corresponding videos on the QuantitativeBytes 34 | YouTube channel at: 35 | 36 | www.youtube.com/c/QuantitativeBytes 37 | 38 | ************************************************************************************************* */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include "qbVector.h" 47 | #include "qbVector3.hpp" 48 | #include "qbVector4.hpp" 49 | 50 | template 51 | class qbMatrix2 52 | { 53 | public: 54 | // Define the various constructors. 55 | qbMatrix2(); 56 | qbMatrix2(int nRows, int nCols); 57 | qbMatrix2(int nRows, int nCols, const T *inputData); 58 | qbMatrix2(const qbMatrix2 &inputMatrix); 59 | qbMatrix2(int nRows, int nCols, const std::vector &inputData); 60 | 61 | // And the destructor. 62 | ~qbMatrix2(); 63 | 64 | // Configuration methods. 65 | bool Resize(int numRows, int numCols); 66 | void SetToIdentity(); 67 | 68 | // Element access methods. 69 | T GetElement(int row, int col) const; 70 | bool SetElement(int row, int col, T elementValue); 71 | int GetNumRows() const; 72 | int GetNumCols() const; 73 | 74 | // Manipulation methods. 75 | // Compute matrix inverse. 76 | bool Inverse(); 77 | // Convert to row echelon form. 78 | qbMatrix2 RowEchelon(); 79 | // Return the transpose. 80 | qbMatrix2 Transpose() const; 81 | 82 | // Compute determinant. 83 | T Determinant(); 84 | 85 | // Overload == operator. 86 | bool operator== (const qbMatrix2& rhs); 87 | bool Compare (const qbMatrix2& matrix1, double tolerance); 88 | 89 | // Overload the assignment operator. 90 | qbMatrix2 operator= (const qbMatrix2 &rhs); 91 | 92 | // Overload +, - and * operators (friends). 93 | template friend qbMatrix2 operator+ (const qbMatrix2& lhs, const qbMatrix2& rhs); 94 | template friend qbMatrix2 operator+ (const U& lhs, const qbMatrix2& rhs); 95 | template friend qbMatrix2 operator+ (const qbMatrix2& lhs, const U& rhs); 96 | 97 | template friend qbMatrix2 operator- (const qbMatrix2& lhs, const qbMatrix2& rhs); 98 | template friend qbMatrix2 operator- (const U& lhs, const qbMatrix2& rhs); 99 | template friend qbMatrix2 operator- (const qbMatrix2& lhs, const U& rhs); 100 | 101 | template friend qbMatrix2 operator* (const qbMatrix2& lhs, const qbMatrix2& rhs); 102 | template friend qbMatrix2 operator* (const U& lhs, const qbMatrix2& rhs); 103 | template friend qbMatrix2 operator* (const qbMatrix2& lhs, const U& rhs); 104 | 105 | // qbMatrix2 * qbVector. 106 | template friend qbVector operator* (const qbMatrix2& lhs, const qbVector& rhs); 107 | 108 | // qbMatrix2 * qbVector3. 109 | template friend qbVector3 operator* (const qbMatrix2& lhs, const qbVector3& rhs); 110 | 111 | // qbMatrix2 * qbVector4. 112 | template friend qbVector4 operator* (const qbMatrix2& lhs, const qbVector4& rhs); 113 | 114 | bool Separate(qbMatrix2 &matrix1, qbMatrix2 &matrix2, int colNum); 115 | bool Join(const qbMatrix2& matrix2); 116 | qbMatrix2 FindSubMatrix(int rowNum, int colNum); 117 | 118 | // Function to return the rank of the matrix. 119 | int Rank(); 120 | 121 | bool IsSquare(); 122 | bool IsRowEchelon(); 123 | bool IsNonZero(); 124 | bool IsSymmetric(); 125 | void PrintMatrix(); 126 | void PrintMatrix(int precision); 127 | 128 | private: 129 | int Sub2Ind(int row, int col) const; 130 | bool CloseEnough(T f1, T f2); 131 | void SwapRow(int i, int j); 132 | void MultAdd(int i, int j, T multFactor); 133 | void MultRow(int i, T multFactor); 134 | int FindRowWithMaxElement(int colNumber, int startingRow); 135 | 136 | private: 137 | T *m_matrixData; 138 | int m_nRows, m_nCols, m_nElements; 139 | }; 140 | 141 | /* ************************************************************************************************** 142 | CONSTRUCTOR / DESTRUCTOR FUNCTIONS 143 | /* *************************************************************************************************/ 144 | // The default constructor. 145 | template 146 | qbMatrix2::qbMatrix2() 147 | { 148 | m_nRows = 1; 149 | m_nCols = 1; 150 | m_nElements = 1; 151 | m_matrixData = nullptr; 152 | } 153 | 154 | // Construct empty matrix (all elements 0) 155 | template 156 | qbMatrix2::qbMatrix2(int nRows, int nCols) 157 | { 158 | m_nRows = nRows; 159 | m_nCols = nCols; 160 | m_nElements = m_nRows * m_nCols; 161 | m_matrixData = new T[m_nElements]; 162 | for (int i=0; i 168 | qbMatrix2::qbMatrix2(int nRows, int nCols, const T *inputData) 169 | { 170 | m_nRows = nRows; 171 | m_nCols = nCols; 172 | m_nElements = m_nRows * m_nCols; 173 | m_matrixData = new T[m_nElements]; 174 | for (int i=0; i 180 | qbMatrix2::qbMatrix2(const qbMatrix2 &inputMatrix) 181 | { 182 | m_nRows = inputMatrix.m_nRows; 183 | m_nCols = inputMatrix.m_nCols; 184 | m_nElements = inputMatrix.m_nElements; 185 | 186 | m_matrixData = new T[m_nElements]; 187 | for (int i=0; i 193 | qbMatrix2::qbMatrix2(int nRows, int nCols, const std::vector &inputData) 194 | { 195 | m_nRows = nRows; 196 | m_nCols = nCols; 197 | m_nElements = m_nRows * m_nCols; 198 | m_matrixData = new T[m_nElements]; 199 | for (int i=0; i 204 | qbMatrix2::~qbMatrix2() 205 | { 206 | // Destructor. 207 | if (m_matrixData) 208 | delete[] m_matrixData; 209 | 210 | m_matrixData = nullptr; 211 | } 212 | 213 | /* ************************************************************************************************** 214 | CONFIGURATION FUNCTIONS 215 | /* *************************************************************************************************/ 216 | template 217 | bool qbMatrix2::Resize(int numRows, int numCols) 218 | { 219 | m_nRows = numRows; 220 | m_nCols = numCols; 221 | m_nElements = (m_nRows * m_nCols); 222 | delete[] m_matrixData; 223 | m_matrixData = new T[m_nElements]; 224 | if (m_matrixData != nullptr) 225 | { 226 | for (int i=0; i 239 | void qbMatrix2::SetToIdentity() 240 | { 241 | if (!IsSquare()) 242 | throw std::invalid_argument("Cannot form an identity matrix that is not square."); 243 | 244 | for (int row=0; row 260 | T qbMatrix2::GetElement(int row, int col) const 261 | { 262 | int linearIndex = Sub2Ind(row, col); 263 | if (linearIndex >= 0) 264 | return m_matrixData[linearIndex]; 265 | else 266 | return 0.0; 267 | 268 | } 269 | 270 | template 271 | bool qbMatrix2::SetElement(int row, int col, T elementValue) 272 | { 273 | int linearIndex = Sub2Ind(row, col); 274 | if (linearIndex >= 0) 275 | { 276 | m_matrixData[linearIndex] = elementValue; 277 | return true; 278 | } 279 | else 280 | { 281 | return false; 282 | } 283 | } 284 | 285 | template 286 | int qbMatrix2::GetNumRows() const 287 | { 288 | return m_nRows; 289 | } 290 | 291 | template 292 | int qbMatrix2::GetNumCols() const 293 | { 294 | return m_nCols; 295 | } 296 | 297 | template 298 | bool qbMatrix2::Compare(const qbMatrix2& matrix1, double tolerance) 299 | { 300 | // First, check that the matrices have the same dimensions. 301 | int numRows1 = matrix1.m_nRows; 302 | int numCols1 = matrix1.m_nCols; 303 | if ((numRows1 != m_nRows) || (numCols1 != m_nCols)) 304 | return false; 305 | 306 | // Loop over all the elements and compute the sum-of-squared-differences. 307 | double cumulativeSum = 0.0; 308 | for (int i=0; i 330 | qbMatrix2 operator+ (const qbMatrix2& lhs, const qbMatrix2& rhs) 331 | { 332 | int numRows = lhs.m_nRows; 333 | int numCols = lhs.m_nCols; 334 | int numElements = numRows * numCols; 335 | T *tempResult = new T[numElements]; 336 | for (int i=0; i result(numRows, numCols, tempResult); 340 | delete[] tempResult; 341 | return result; 342 | } 343 | 344 | // scaler + matrix 345 | template 346 | qbMatrix2 operator+ (const T& lhs, const qbMatrix2& rhs) 347 | { 348 | int numRows = rhs.m_nRows; 349 | int numCols = rhs.m_nCols; 350 | int numElements = numRows * numCols; 351 | T *tempResult = new T[numElements]; 352 | for (int i=0; i result(numRows, numCols, tempResult); 356 | delete[] tempResult; 357 | return result; 358 | } 359 | 360 | // matrix + scaler 361 | template 362 | qbMatrix2 operator+ (const qbMatrix2& lhs, const T& rhs) 363 | { 364 | int numRows = lhs.m_nRows; 365 | int numCols = lhs.m_nCols; 366 | int numElements = numRows * numCols; 367 | T *tempResult = new T[numElements]; 368 | for (int i=0; i result(numRows, numCols, tempResult); 372 | delete[] tempResult; 373 | return result; 374 | } 375 | 376 | /* ************************************************************************************************** 377 | THE - OPERATOR 378 | /* *************************************************************************************************/ 379 | // matrix - matrix 380 | template 381 | qbMatrix2 operator- (const qbMatrix2& lhs, const qbMatrix2& rhs) 382 | { 383 | int numRows = lhs.m_nRows; 384 | int numCols = lhs.m_nCols; 385 | int numElements = numRows * numCols; 386 | T *tempResult = new T[numElements]; 387 | for (int i=0; i 397 | qbMatrix2 operator- (const T& lhs, const qbMatrix2& rhs) 398 | { 399 | int numRows = rhs.m_nRows; 400 | int numCols = rhs.m_nCols; 401 | int numElements = numRows * numCols; 402 | T *tempResult = new T[numElements]; 403 | for (int i=0; i result(numRows, numCols, tempResult); 407 | delete[] tempResult; 408 | return result; 409 | } 410 | 411 | // matrix - scaler 412 | template 413 | qbMatrix2 operator- (const qbMatrix2& lhs, const T& rhs) 414 | { 415 | int numRows = lhs.m_nRows; 416 | int numCols = lhs.m_nCols; 417 | int numElements = numRows * numCols; 418 | T *tempResult = new T[numElements]; 419 | for (int i=0; i result(numRows, numCols, tempResult); 423 | delete[] tempResult; 424 | return result; 425 | } 426 | 427 | /* ************************************************************************************************** 428 | THE * OPERATOR 429 | /* *************************************************************************************************/ 430 | // matrix * qbVector4 431 | template 432 | qbVector4 operator* (const qbMatrix2& lhs, const qbVector4& rhs) 433 | { 434 | // Verify the dimensions of the inputs. 435 | if (lhs.m_nCols != 4) 436 | { 437 | std::cout << "*** qbMatrix.h ***" << std::endl; 438 | std::cout << "Vector of 3 elements." << std::endl; 439 | std::cout << "Matrix of " << lhs.m_nCols << " columns." << std::endl; 440 | std::cout << "Are these equal: " << (lhs.m_nCols == rhs.GetNumDims()) << std::endl; 441 | throw std::invalid_argument("Number of columns in matrix must equal number of rows in vector."); 442 | } 443 | 444 | // Setup the vector for the output. 445 | qbVector4 result; 446 | 447 | // Loop over rows and columns and perform the multiplication operation element-by-element. 448 | for (int row=0; row(0.0); 451 | cumulativeSum += (lhs.GetElement(row,0) * rhs.m_v1); 452 | cumulativeSum += (lhs.GetElement(row,1) * rhs.m_v2); 453 | cumulativeSum += (lhs.GetElement(row,2) * rhs.m_v3); 454 | cumulativeSum += (lhs.GetElement(row,3) * rhs.m_v4); 455 | 456 | result.SetElement(row, cumulativeSum); 457 | } 458 | 459 | return result; 460 | } 461 | 462 | // matrix * qbVector3 463 | template 464 | qbVector3 operator* (const qbMatrix2& lhs, const qbVector3& rhs) 465 | { 466 | // Verify the dimensions of the inputs. 467 | if (lhs.m_nCols != 3) 468 | { 469 | std::cout << "*** qbMatrix.h ***" << std::endl; 470 | std::cout << "Vector of 3 elements." << std::endl; 471 | std::cout << "Matrix of " << lhs.m_nCols << " columns." << std::endl; 472 | std::cout << "Are these equal: " << (lhs.m_nCols == rhs.GetNumDims()) << std::endl; 473 | throw std::invalid_argument("Number of columns in matrix must equal number of rows in vector."); 474 | } 475 | 476 | // Setup the vector for the output. 477 | qbVector3 result; 478 | 479 | // Loop over rows and columns and perform the multiplication operation element-by-element. 480 | for (int row=0; row(0.0); 483 | cumulativeSum += (lhs.GetElement(row,0) * rhs.m_x); 484 | cumulativeSum += (lhs.GetElement(row,1) * rhs.m_y); 485 | cumulativeSum += (lhs.GetElement(row,2) * rhs.m_z); 486 | 487 | result.SetElement(row, cumulativeSum); 488 | } 489 | 490 | return result; 491 | } 492 | 493 | // matrix * vector 494 | template 495 | qbVector operator* (const qbMatrix2& lhs, const qbVector& rhs) 496 | { 497 | // Verify the dimensions of the inputs. 498 | if (lhs.m_nCols != rhs.GetNumDims()) 499 | throw std::invalid_argument("Number of columns in matrix must equal number of rows in vector."); 500 | 501 | // Setup the vector for the output. 502 | qbVector result(lhs.m_nRows); 503 | 504 | // Loop over rows and columns and perform the multiplication operation element-by-element. 505 | for (int row=0; row(0.0); 508 | for (int col=0; col 520 | qbMatrix2 operator* (const T& lhs, const qbMatrix2& rhs) 521 | { 522 | int numRows = rhs.m_nRows; 523 | int numCols = rhs.m_nCols; 524 | int numElements = numRows * numCols; 525 | T *tempResult = new T[numElements]; 526 | for (int i=0; i result(numRows, numCols, tempResult); 530 | delete[] tempResult; 531 | return result; 532 | } 533 | 534 | // matrix * scaler 535 | template 536 | qbMatrix2 operator* (const qbMatrix2& lhs, const T& rhs) 537 | { 538 | int numRows = lhs.m_nRows; 539 | int numCols = lhs.m_nCols; 540 | int numElements = numRows * numCols; 541 | T *tempResult = new T[numElements]; 542 | for (int i=0; i result(numRows, numCols, tempResult); 546 | delete[] tempResult; 547 | return result; 548 | } 549 | 550 | // matrix * matrix 551 | template 552 | qbMatrix2 operator* (const qbMatrix2& lhs, const qbMatrix2& rhs) 553 | { 554 | int r_numRows = rhs.m_nRows; 555 | int r_numCols = rhs.m_nCols; 556 | int l_numRows = lhs.m_nRows; 557 | int l_numCols = lhs.m_nCols; 558 | 559 | if (l_numCols == r_numRows) 560 | { 561 | // This is the standard matrix multiplication condition. 562 | // The output will be the same size as the RHS. 563 | T *tempResult = new T[lhs.m_nRows * rhs.m_nCols]; 564 | 565 | // Loop through each row of the LHS. 566 | for (int lhsRow=0; lhsRow(0.0); 572 | // Loop through each element of this LHS row. 573 | for (int lhsCol=0; lhsCol result(l_numRows, r_numCols, tempResult); 592 | delete[] tempResult; 593 | return result; 594 | } 595 | else 596 | { 597 | qbMatrix2 result(1, 1); 598 | return result; 599 | } 600 | } 601 | 602 | /* ************************************************************************************************** 603 | THE == OPERATOR 604 | /* *************************************************************************************************/ 605 | template 606 | bool qbMatrix2::operator== (const qbMatrix2& rhs) 607 | { 608 | // Check if the matricies are the same size, if not return false. 609 | if ((this->m_nRows != rhs.m_nRows) || (this->m_nCols != rhs.m_nCols)) 610 | return false; 611 | 612 | // Check if the elements are equal. 613 | bool flag = true; 614 | for (int i=0; im_nElements; ++i) 615 | { 616 | //if (this->m_matrixData[i] != rhs.m_matrixData[i]) 617 | if (!CloseEnough(this->m_matrixData[i], rhs.m_matrixData[i])) 618 | flag = false; 619 | } 620 | return flag; 621 | } 622 | 623 | /* ************************************************************************************************** 624 | THE ASSIGNMENT (=) OPERATOR 625 | Note the changes that have been made since the videos about this first code were made. The original 626 | code has been left, but commented out. Ultimately it was necessary to make some changes to 627 | improve the performance of these functions in terms of run time. 628 | See this episode of my ray tracing in C++ series for further information: 629 | https://youtu.be/-5kLk7_bs0U 630 | /* *************************************************************************************************/ 631 | /* 632 | template 633 | qbMatrix2 qbMatrix2::operator= (const qbMatrix2 &rhs) 634 | { 635 | // Make sure we're not assigning to ourself. 636 | if (this != &rhs) 637 | { 638 | m_nRows = rhs.m_nRows; 639 | m_nCols = rhs.m_nCols; 640 | m_nElements = rhs.m_nElements; 641 | 642 | if (m_matrixData) 643 | delete[] m_matrixData; 644 | 645 | m_matrixData = new T[m_nElements]; 646 | for (int i=0; i 655 | qbMatrix2 qbMatrix2::operator= (const qbMatrix2 &rhs) 656 | { 657 | // Make sure we're not assigning to ourself. 658 | if (this != &rhs) 659 | { 660 | // If the dimensions are the same, we only need to copy the elements, 661 | // there is no need to delete and re-allocate memory. 662 | if ((m_nRows == rhs.m_nRows) && (m_nCols == rhs.m_nCols)) 663 | { 664 | for (int i=0; i pointers in the input argument list) 689 | /* *************************************************************************************************/ 690 | template 691 | bool qbMatrix2::Separate(qbMatrix2 &matrix1, qbMatrix2 &matrix2, int colNum) 692 | { 693 | // Compute the sizes of the new matrices. 694 | int numRows = m_nRows; 695 | int numCols1 = colNum; 696 | int numCols2 = m_nCols - colNum; 697 | 698 | // Resize the two matrices to the proper dimensions. 699 | matrix1.Resize(numRows, numCols1); 700 | matrix2.Resize(numRows, numCols1); 701 | 702 | // Loop over the original matrix and store data into the appropriate elements of the two 703 | // output matrices. 704 | for (int row=0; rowGetElement(row, col)); 711 | } 712 | else 713 | { 714 | matrix2.SetElement(row, col-colNum, this->GetElement(row, col)); 715 | } 716 | } 717 | } 718 | return true; 719 | } 720 | 721 | /* ************************************************************************************************** 722 | JOIN TwO MATRICES TOGETHER 723 | /* *************************************************************************************************/ 724 | template 725 | bool qbMatrix2::Join (const qbMatrix2& matrix2) 726 | { 727 | // Extract the information that we need from both matrices 728 | int numRows1 = m_nRows; 729 | int numRows2 = matrix2.m_nRows; 730 | int numCols1 = m_nCols; 731 | int numCols2 = matrix2.m_nCols; 732 | 733 | // If the matrices have different numbers of rows, then this operation makes no sense. 734 | if (numRows1 != numRows2) 735 | throw std::invalid_argument("Attempt to join matrices with different numbers of rows is invalid."); 736 | 737 | // Allocate memory for the result. 738 | // Note that only the number of columns increases. 739 | T* newMatrixData = new T[numRows1*(numCols1+numCols2)]; 740 | 741 | // Copy the two matrices into the new one. 742 | int linearIndex, resultLinearIndex; 743 | for (int i=0; i 780 | T qbMatrix2::Determinant() 781 | { 782 | // Check if the matrix is square. 783 | if (!IsSquare()) 784 | throw std::invalid_argument("Cannot compute the determinant of a matrix that is not square."); 785 | 786 | // If the matrix is 2x2, we can just compute the determinant directly. 787 | T determinant; 788 | if (m_nRows == 2) 789 | { 790 | determinant = (m_matrixData[0] * m_matrixData[3]) - (m_matrixData[1] * m_matrixData[2]); 791 | } 792 | else 793 | { 794 | /* Otherwise we extract the sub-matrices and then recursively call this function 795 | until we get to 2x2 matrices. */ 796 | 797 | // We will find the sub-matrices for row 0. 798 | // So, loop over each column. 799 | T cumulativeSum = 0.0; 800 | T sign = 1.0; 801 | for (int j=0; j subMatrix = this->FindSubMatrix(0, j); 805 | 806 | /* Cumulatively multiply the determinant of the sub-matrix by the 807 | current element of this matrix and the sign variable (note the 808 | recursive calling of the Determinant() method). */ 809 | cumulativeSum += this->GetElement(0, j) * subMatrix.Determinant() * sign; 810 | sign = -sign; 811 | } 812 | determinant = cumulativeSum; 813 | } 814 | 815 | return determinant; 816 | } 817 | 818 | /* ************************************************************************************************** 819 | COMPUTE MATRIX INVERSE (USING GAUSS-JORDAN ELIMINATION) 820 | /* *************************************************************************************************/ 821 | template 822 | bool qbMatrix2::Inverse() 823 | { 824 | // Check if the matrix is square (we cannot compute the inverse if it isn't). 825 | if (!IsSquare()) 826 | throw std::invalid_argument("Cannot compute the inverse of a matrix that is not square."); 827 | 828 | // If we get to here, the matrix is square so we can continue. 829 | 830 | // Form an identity matrix with the same dimensions as the matrix we wish to invert. 831 | qbMatrix2 identityMatrix(m_nRows, m_nCols); 832 | identityMatrix.SetToIdentity(); 833 | 834 | // Join the identity matrix to the existing matrix. 835 | int originalNumCols = m_nCols; 836 | Join(identityMatrix); 837 | 838 | // Begin the main part of the process. 839 | int cRow, cCol; 840 | int maxCount = 100; 841 | int count = 0; 842 | bool completeFlag = false; 843 | while ((!completeFlag) && (count < maxCount)) 844 | { 845 | for (int diagIndex=0; diagIndex leftHalf; 938 | qbMatrix2 rightHalf; 939 | this->Separate(leftHalf, rightHalf, originalNumCols); 940 | 941 | // When the process is complete, the left half should be the identity matrix. 942 | if (leftHalf == identityMatrix) 943 | { 944 | // Set completedFlag to true to indicate that the process has completed. 945 | completeFlag = true; 946 | 947 | // Rebuild the matrix with just the right half, which now contains the result. 948 | m_nCols = originalNumCols; 949 | m_nElements = m_nRows * m_nCols; 950 | delete[] m_matrixData; 951 | m_matrixData = new T[m_nElements]; 952 | for (int i=0; i 968 | qbMatrix2 qbMatrix2::Transpose() const 969 | { 970 | // Form the output matrix. 971 | // Note that we reverse the order of rows and columns, as this will be the transpose. 972 | qbMatrix2 resultMatrix(m_nCols, m_nRows); 973 | 974 | // Now loop through the elements and copy in the appropriate order. 975 | for (int i=0; iGetElement(i, j)); 980 | } 981 | } 982 | 983 | return resultMatrix; 984 | } 985 | 986 | /* ************************************************************************************************** 987 | CONVERT TO ROW ECHELON FORM (USING GAUSSIAN ELIMINATION) 988 | /* *************************************************************************************************/ 989 | template 990 | qbMatrix2 qbMatrix2::RowEchelon() 991 | { 992 | /* The current matrix must have at least as many columns as rows, but note that we don't 993 | actually require it to be square since we assume that the user may have combined a 994 | square matrix with a vector. They would do this, for example, if they were trying to 995 | solve a system of linear equations. */ 996 | if (m_nCols < m_nRows) 997 | throw std::invalid_argument("The matrix must have at least as many columns as rows."); 998 | 999 | /* Make a copy of the matrix data before we start. We do this because the procedure below 1000 | will make changes to the stored matrix data (it operates 'in place') and we don't want 1001 | this behaviour. Therefore we take a copy at the beginning and then we will replace the 1002 | modified matrix data with this copied data at the end, thus preserving the original. */ 1003 | T *tempMatrixData; 1004 | tempMatrixData = new T[m_nRows * m_nCols]; 1005 | for (int i=0; i<(m_nRows*m_nCols); ++i) 1006 | tempMatrixData[i] = m_matrixData[i]; 1007 | 1008 | // Begin the main part of the process. 1009 | int cRow, cCol; 1010 | int maxCount = 100; 1011 | int count = 0; 1012 | bool completeFlag = false; 1013 | while ((!completeFlag) && (count < maxCount)) 1014 | { 1015 | for (int diagIndex=0; diagIndexIsRowEchelon(); 1054 | 1055 | // Increment the counter. 1056 | count++; 1057 | } 1058 | 1059 | // Form the output matrix. 1060 | qbMatrix2 outputMatrix(m_nRows, m_nCols, m_matrixData); 1061 | 1062 | // Restore the original matrix data from the copy. 1063 | for (int i=0; i<(m_nRows * m_nCols); ++i) 1064 | m_matrixData[i] = tempMatrixData[i]; 1065 | 1066 | // Delete the copy. 1067 | delete[] tempMatrixData; 1068 | 1069 | return outputMatrix; 1070 | } 1071 | 1072 | /* ************************************************************************************************** 1073 | COMPUTE THE RANK OF THE PROVIDED MATRIX 1074 | /* *************************************************************************************************/ 1075 | template 1076 | int qbMatrix2::Rank() 1077 | { 1078 | // Convert the matrix to row-echelon form. 1079 | qbMatrix2 matrixCopy = this->RowEchelon(); 1080 | 1081 | /* If this didn't work, then we compute the rank by finding 1082 | the largest non-zero sub-matrix with a non-zero determinant. 1083 | 1084 | Note that this method is slower for large matrices and therefore 1085 | it is better to use the RowEchelon method if possible. */ 1086 | int numNonZeroRows = 0; 1087 | if (!matrixCopy.IsRowEchelon()) 1088 | { 1089 | // Setup a std::vector to store the sub-matrices as we go. 1090 | std::vector> subMatrixVector; 1091 | 1092 | // Store the current matrix into the array first. 1093 | subMatrixVector.push_back(*this); 1094 | 1095 | /* Loop through the subMatrixVector until either we have tested every 1096 | sub-matrix or the completeFlag is set. */ 1097 | bool completeFlag = false; 1098 | int subMatrixCount = 0; 1099 | while ((subMatrixCount < subMatrixVector.size()) && (!completeFlag)) 1100 | { 1101 | // Extract the currentMatrix to work with. 1102 | qbMatrix2 currentMatrix = subMatrixVector[subMatrixCount]; 1103 | subMatrixCount++; 1104 | 1105 | // Test if this matrix is non-zero. 1106 | if (currentMatrix.IsNonZero()) 1107 | { 1108 | // If the determinant is non-zero, then we have our result. 1109 | T currentMatrixDet = currentMatrix.Determinant(); 1110 | if (!CloseEnough(currentMatrixDet, 0.0)) 1111 | { 1112 | completeFlag = true; 1113 | numNonZeroRows = currentMatrix.GetNumRows(); 1114 | } 1115 | else 1116 | { 1117 | // Extract and store each sub-matrix (if larger than 2x2). 1118 | if ((currentMatrix.GetNumRows() > 2) && (currentMatrix.GetNumCols() > 2)) 1119 | { 1120 | for (int i=0; i 0) 1157 | numNonZeroRows++; 1158 | } 1159 | 1160 | } 1161 | // The rank of the matrix is simply the number of non-zero rows. 1162 | return numNonZeroRows; 1163 | 1164 | } 1165 | 1166 | /* ************************************************************************************************** 1167 | PRIVATE FUNCTIONS 1168 | /* *************************************************************************************************/ 1169 | // Function to return the linear index corresponding to the supplied row and column values. 1170 | template 1171 | int qbMatrix2::Sub2Ind(int row, int col) const 1172 | { 1173 | if ((row < m_nRows) && (row >= 0) && (col < m_nCols) && (col >= 0)) 1174 | return (row * m_nCols) + col; 1175 | else 1176 | return -1; 1177 | } 1178 | 1179 | // Function to test whether the matrix is square. 1180 | template 1181 | bool qbMatrix2::IsSquare() 1182 | { 1183 | if (m_nCols == m_nRows) 1184 | return true; 1185 | else 1186 | return false; 1187 | } 1188 | 1189 | // Function to test whether the matrix is non-zero. 1190 | template 1191 | bool qbMatrix2::IsNonZero() 1192 | { 1193 | // Loop over every element. 1194 | int numNonZero = 0; 1195 | for (int i=0; i 1211 | bool qbMatrix2::IsRowEchelon() 1212 | { 1213 | /* We do this by testing that the sum of all the elements in the 1214 | lower triangular matrix is zero. */ 1215 | // Loop over each row, except the first one (which doesn't need to have any zero elements). 1216 | T cumulativeSum = static_cast(0.0); 1217 | for (int i=1; i 1235 | bool qbMatrix2::IsSymmetric() 1236 | { 1237 | /* First test that the matrix is square, if it is 1238 | not, then it cannot by symmetric. */ 1239 | if (!this->IsSquare()) 1240 | return false; 1241 | 1242 | // Now test for symmetry about the diagonal. 1243 | T currentRowElement = static_cast(0.0); 1244 | T currentColElement = static_cast(0.0); 1245 | bool returnFlag = true; 1246 | int diagIndex = 0; 1247 | while ((diagIndex < m_nCols) && returnFlag) 1248 | { 1249 | int rowIndex = diagIndex + 1; 1250 | while ((rowIndex < m_nRows) && returnFlag) 1251 | { 1252 | currentRowElement = this->GetElement(rowIndex, diagIndex); 1253 | currentColElement = this->GetElement(diagIndex, rowIndex); 1254 | 1255 | // Compare the row and column elements. 1256 | if (!CloseEnough(currentRowElement, currentColElement)) 1257 | returnFlag = false; 1258 | 1259 | // Increment row index. 1260 | rowIndex++; 1261 | } 1262 | 1263 | // Increment diagIndex. 1264 | diagIndex++; 1265 | 1266 | } 1267 | 1268 | // Return the result. 1269 | return returnFlag; 1270 | 1271 | } 1272 | 1273 | // Function to swap rows i and j (in place). 1274 | template 1275 | void qbMatrix2::SwapRow(int i, int j) 1276 | { 1277 | // Store a tempory copy of row i. 1278 | T *tempRow = new T[m_nCols]; 1279 | for (int k=0; k 1296 | void qbMatrix2::MultAdd(int i, int j, T multFactor) 1297 | { 1298 | for (int k=0; k 1305 | int qbMatrix2::FindRowWithMaxElement(int colNumber, int startingRow) 1306 | { 1307 | T tempValue = m_matrixData[Sub2Ind(startingRow, colNumber)]; 1308 | int rowIndex = startingRow; 1309 | for (int k=startingRow+1; k fabs(tempValue)) 1312 | { 1313 | rowIndex = k; 1314 | tempValue = m_matrixData[Sub2Ind(k, colNumber)]; 1315 | } 1316 | } 1317 | return rowIndex; 1318 | } 1319 | 1320 | // Function to multiply a row by the given value. 1321 | template 1322 | void qbMatrix2::MultRow(int i, T multFactor) 1323 | { 1324 | for (int k=0; k 1330 | void qbMatrix2::PrintMatrix() 1331 | { 1332 | int nRows = this->GetNumRows(); 1333 | int nCols = this->GetNumCols(); 1334 | for (int row = 0; rowGetElement(row, col) << " "; 1339 | } 1340 | std::cout << std::endl; 1341 | } 1342 | } 1343 | 1344 | // A simple function to print a matrix to stdout, with specified precision. 1345 | template 1346 | void qbMatrix2::PrintMatrix(int precision) 1347 | { 1348 | int nRows = this->GetNumRows(); 1349 | int nCols = this->GetNumCols(); 1350 | for (int row = 0; rowGetElement(row, col) << " "; 1355 | } 1356 | std::cout << std::endl; 1357 | } 1358 | } 1359 | 1360 | template 1361 | bool qbMatrix2::CloseEnough(T f1, T f2) 1362 | { 1363 | return fabs(f1-f2) < 1e-9; 1364 | } 1365 | 1366 | // Function to find the sub-matrix for the given element. 1367 | template 1368 | qbMatrix2 qbMatrix2::FindSubMatrix(int rowNum, int colNum) 1369 | { 1370 | // Create a new matrix to store the sub-matrix. 1371 | // Note that this is one row and one column smaller than the original. 1372 | qbMatrix2 subMatrix(m_nRows-1, m_nCols-1); 1373 | 1374 | // Loop over the elements of the existing matrix and copy to sub-matrix as appropriate. 1375 | int count = 0; 1376 | for (int i=0; iGetElement(i,j); 1384 | count++; 1385 | } 1386 | } 1387 | } 1388 | 1389 | return subMatrix; 1390 | } 1391 | #endif 1392 | --------------------------------------------------------------------------------