/* Copyright (c) 2008-2022 Jan W. Krieger () last modification: $LastChangedDate$ (revision $Rev$) This software is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation, either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License (LGPL) for more details. You should have received a copy of the GNU Lesser General Public License (LGPL) along with this program. If not, see . */ #ifndef JKQTPLINALGTOOLS_H_INCLUDED #define JKQTPLINALGTOOLS_H_INCLUDED #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "jkqtcommon/jkqtcommon_imexport.h" #include "jkqtcommon/jkqtparraytools.h" #include "jkqtcommon/jkqtpmathtools.h" #include "jkqtcommon/jkqtpstringtools.h" #ifdef _OPENMP # include #endif #ifndef JKQTP_ALIGNMENT_BYTES #define JKQTP_ALIGNMENT_BYTES 32 #endif #ifdef JKQTP_STATISTICS_TOOLS_MAY_USE_EIGEN3 # include # include # include # include # include #endif /** \brief calculate the index of the entry in line \a l and column \a c * in a row-major matrix with \a C columns * \ingroup jkqtptools_math_linalg * * You can use this to access a matrix with L rows and C columns: * \code * for (long l=0; l inline void jkqtplinalgPrintMatrix(T* matrix, long L, long C, int width=9, int precision=3, char mode='f') { for (long l=0; l0) std::cout<<", "; std::cout.precision(precision); std::cout.width(width); if (mode=='f') std::cout< inline std::string jkqtplinalgMatrixToString(T* matrix, long L, long C, int width=9, int precision=3, const std::string& mode=std::string("g")) { std::string format="%"+jkqtp_inttostr(width)+std::string(".")+jkqtp_inttostr(precision)+std::string("l")+mode; std::ostringstream ost; for (long l=0; l0) ost<<", "; char buf[500]; sprintf(buf, format.c_str(), jkqtp_todouble(matrix[jkqtplinalgMatIndex(l,c,C)])); ost<=-1) { r=jkqtp_boundedRoundTo(0,255.0-fval*127.0,255); g=jkqtp_boundedRoundTo(0,255.0-fval*127.0,255); } else if (val>0 && val<=1) { b=jkqtp_boundedRoundTo(0,255.0-fval*127.0,255); g=jkqtp_boundedRoundTo(0,255.0-fval*127.0,255); } else if (val<-1) { r=127; g=127; b=255; } else if (val>1) { r=255; g=127; b=127; } } /** \brief maps the number range -1 ... +1 to a non-linear color-scale lightblue - white - lightred (used for coloring matrices!) * \ingroup jkqtptools_math_linalg * * \param val the value to convert * \param[out] r returns the red value (0..255) * \param[out] g returns the green value (0..255) * \param[out] b returns the blue value (0..255) * \param gamma a gamma-value for non-linear color scaling */ inline void jkqtplinalgPM1ToNonlinRWBColors(double val, uint8_t& r, uint8_t& g, uint8_t& b, double gamma=0.5){ if (val<0) { jkqtplinalgPM1ToRWBColors(-1.0*pow(-val,gamma),r,g,b); } else { jkqtplinalgPM1ToRWBColors(pow(val,gamma),r,g,b); } } /** \brief convert the given LxC matrix to std::string, encoded as HTML table * \ingroup jkqtptools_math_linalg * * * \tparam type of the matrix cells (typically double or float) * \param matrix the matrix to convert * \param L number of lines/rows in the matrix * \param C number of columns in the matrix * \param width width (in characters) of each cell in the output (used for formatting) * \param precision precision (in digits) for string-conversions in the output (used for formatting) * \param mode the (printf()) string conversion mode for output of the cell values * \param tableformat this is inserted into the \c tag (may contain HTML property definitions) * \param prenumber this is inserted before each number (may contain HTML markup) * \param postnumber this is inserted after each number (may contain HTML markup) * \param colorcoding if \c true, teh cell backgrounds are color-coded * \param zeroIsWhite if \c the color-coding is forced to white for 0 and then encodes in positive/negative direction with colors (red and blue) * \param[out] colorlabel outputs a label explaining the auto-generated color-coding * \param nonlinColors if \c true, a non-linear color-coding is used * \param nonlinColorsGamma gamma-value for a non-linear color-coding * \param colortableformat lie \a tableformat, but for the legend table output in \a colorLabel * * \see jkqtplinalgPM1ToNonlinRWBColors() and jkqtplinalgPM1ToRWBColors() */ template inline std::string jkqtplinalgMatrixToHTMLString(T* matrix, long L, long C, int width=9, int precision=3, const std::string& mode=std::string("g"), const std::string& tableformat=std::string(), const std::string& prenumber=std::string(), const std::string& postnumber=std::string(), bool colorcoding=false, bool zeroIsWhite=true, std::string* colorlabel=nullptr, bool nonlinColors=false, double nonlinColorsGamma=0.25, const std::string& colortableformat=std::string()) { std::ostringstream ost; ost<<"\n"; std::string format="%"+jkqtp_inttostr(width)+std::string(".")+jkqtp_inttostr(precision)+std::string("l")+mode; double minv=0, maxv=0; if (colorcoding) { jkqtpstatMinMax(matrix, L*C, minv, maxv); } for (long l=0; l"; for (long c=0; c0) valrel=fabs(val/maxv); } else { valrel=((val-minv)/(maxv-minv)-0.5)*2.0; } if (nonlinColors) { jkqtplinalgPM1ToNonlinRWBColors(valrel, r,g,b, nonlinColorsGamma); } else { jkqtplinalgPM1ToRWBColors(valrel, r,g,b); } char buf[500]; sprintf(buf, " bgcolor=\"#%02X%02X%02X\"", int(r), int(g), int(b)); cols=buf; } ost<<""; } ost<<"\n"; } ost<<"
"; ost.precision(precision); ost.width(width); char buf[500]; sprintf(buf, format.c_str(), val); ost<
"; if (colorcoding && colorlabel) { char buf[8192]; uint8_t rm=255,gm=255,bm=255; uint8_t rmc=255,gmc=255,bmc=255; uint8_t rc=255,gc=255,bc=255; uint8_t rcp=255,gcp=255,bcp=255; uint8_t rp=255,gp=255,bp=255; double vm=minv; double vc=0; double vp=maxv; if (!zeroIsWhite) { vc=(maxv+minv)/2.0; } if (nonlinColors) { jkqtplinalgPM1ToNonlinRWBColors(-1, rm, gm, bm, nonlinColorsGamma); jkqtplinalgPM1ToNonlinRWBColors(-0.5, rmc, gmc, bmc, nonlinColorsGamma); jkqtplinalgPM1ToNonlinRWBColors(0, rc, gc, bc, nonlinColorsGamma); jkqtplinalgPM1ToNonlinRWBColors(0.5, rcp, gcp, bcp, nonlinColorsGamma); jkqtplinalgPM1ToNonlinRWBColors(1, rp, gp, bp, nonlinColorsGamma); } else { jkqtplinalgPM1ToRWBColors(-1, rm, gm, bm); jkqtplinalgPM1ToRWBColors(-0.5, rmc, gmc, bmc); jkqtplinalgPM1ToRWBColors(0, rc, gc, bc); jkqtplinalgPM1ToRWBColors(0.5, rcp, gcp, bcp); jkqtplinalgPM1ToRWBColors(1, rp, gp, bp); } sprintf(buf, "
" "" "" "" "" "" "
 %9.3lg    —    %9.3lg    —    %9.3lg 
", colortableformat.c_str(), int(rm), int(gm), int(bm), vm, int(rmc), int(gmc), int(bmc), int(rc), int(gc), int(bc), vc, int(rcp), int(gcp), int(bcp), int(rp), int(gp), int(bp), vp); (*colorlabel)=std::string(buf); } return ost.str(); } /** \brief dot-product between two vectors \a vec1 and \a vec2, each with a length of \a N entries * \ingroup jkqtptools_math_linalg * * \tparam T of the vector cells (typically double or float) * \param vec1 first vector * \param vec2 second vector * \param N number of entries in \a vec1 and \a vec2 */ template inline T jkqtplinalgDotProduct(const T* vec1, const T* vec2, long N) { T res=0; for (long l=0; l inline void jkqtplinalgTransposeMatrix(T* matrix, long N) { for (long l=0; l inline void jkqtplinalgTransposeMatrix(T* matrix, long L, long C) { JKQTPArrayScopedPointer t(jkqtpArrayDuplicate(matrix, L*C)); for (long l=0; l inline void jkqtplinalgMatrixSwapLines(T* m, long l1, long l2, long C) { for (long c=0; c inline void jkqtplinalgMatrixProduct(const T* M1, long L1, long C1, const T* M2, long L2, long C2, T* M) { if (M1!=M &&M2!=M) { for (long l=0; l MM(jkqtpArrayDuplicate(M1, L1*C1)); jkqtplinalgMatrixProduct(MM.data(),L1,C1,M2,L2,C2,M); } else if (M1!=M && M2==M) { JKQTPArrayScopedPointer MM(jkqtpArrayDuplicate(M1, L1*C1)); jkqtplinalgMatrixProduct(M1,L1,C1,MM.data(),L2,C2,M); } else if (M1==M && M2==M) { JKQTPArrayScopedPointer MM(jkqtpArrayDuplicate(M1, L1*C1)); jkqtplinalgMatrixProduct(MM.data(),L1,C1,MM.data(),L2,C2,M); } } /*! \brief matrix-matrix product of two NxN matrices \ingroup jkqtptools_math_linalg \param M1 matrix 1, size: NxN \param M2 matrix 1, size: NxN \param N number os rows/columns in the matrices \a M1, \a M2 and \a M \param[out] M output matrix M=M1*M2, size: NxN */ template inline void jkqtplinalgMatrixProduct(const T* M1, const T* M2, long N, T* M) { jkqtplinalgMatrixProduct(M1,N,N,M2,N,N,M); } /*! \brief performs a Gauss-Jordan eliminaion on a LxC matrix m \ingroup jkqtptools_math_linalg For a matrix equation \f[ A\cdot\vec{x}=\vec{b} \f] the input matrix is \f[ \left[A | \vec{b}\right] \f] for matrix inversion it is \f[ \left[A | I_L\right] \f] where \f$ I_L \f$ is the unit matrix with LxL entries. \tparam T of the matrix cells (typically double or float) \param m the matrix \param L number of rows in the matrix \param C number of columns in the matrix \see http://www.virtual-maxim.de/matrix-invertieren-in-c-plus-plus/ */ template inline bool jkqtplinalgGaussJordan(T* m, long L, long C) { const long N=L; //std::cout<<"\nstep 0:\n"; //linalgPrintMatrix(m, N, C); // first we perform a Gauss-elimination, which transforms the matrix in the left half of m into upper triangular form for (long k=0; k matrix is not invertible // the determinant of an upper triangular marix equals the product of the diagonal elements T det=1.0; for (long k=0; k0; k--) { //std::cout<<"\nstep J"<=0; i--) { const T s=m[jkqtplinalgMatIndex(i,k,C)]/m[jkqtplinalgMatIndex(k,k,C)]; for (long c=k; c inline bool jkqtplinalgMatrixInversion(const T* matrix, T* matrix_out, long N) { #ifdef JKQTP_STATISTICS_TOOLS_MAY_USE_EIGEN3 if (N==2) { Eigen::Map > eA(matrix); Eigen::Map > eO(matrix_out); eO=eA.inverse(); //std::cout<<"\n--------------------------------------\n2x2 input matrix\n"< > eA(matrix); Eigen::Map > eO(matrix_out); //std::cout<<"\n--------------------------------------\n3x3 input matrix\n"< > eA(matrix); Eigen::Map > eO(matrix_out); //std::cout<<"\n--------------------------------------\n4x4 input matrix\n"< > eA(matrix,N,N); Eigen::Map > eO(matrix_out,N,N); eO=eA.inverse(); //std::cout<<"\n--------------------------------------\nNxN input matrix\n"< m; m.resize(msize); for (long i=0; i inline bool jkqtplinalgMatrixInversion(T* matrix, long N) { return jkqtplinalgMatrixInversion(matrix, matrix, N); } /*! \brief solve a system of N linear equations \f$ A\cdot\vec{x}=B \f$ simultaneously for C columns in B \ingroup jkqtptools_math_linalg \param A an NxN matrix of coefficients \param B an NxC marix \param N number of equations \param C number of columns in B \param result_out a NxC matrix with the results after the inversion of the system of equations \return \c true on success \note This function uses the Gauss-Jordan algorithm \note It is save to call \c jkqtpstatLinSolve(A,B,N,C,B) with the same argument for B and result_out. Then the input will be overwritten with the new matrix! */ template inline bool jkqtplinalgLinSolve(const T* A, const T* B, long N, long C, T* result_out) { #if defined(JKQTP_STATISTICS_TOOLS_MAY_USE_EIGEN3) && (!defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_noeigen)) if (N==2 && C==1) { Eigen::Map > eA(A); Eigen::Matrix eB; Eigen::Map > x(result_out); eB=Eigen::Map >(B,2,1); # ifdef STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_fullPivLu x=eA.fullPivLu().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_householderQr) x=eA.householderQr().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_fullPivHouseholderQr) x=eA.fullPivHouseholderQr().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_jacobiSvd) x=eA.jacobiSVD().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_colPivHouseholderQr) x=eA.colPivHouseholderQr().solve(eB); # else x=eA.fullPivLu().solve(eB); # endif } else if (N==3 && C==1) { Eigen::Map > eA(A); Eigen::Matrix eB; Eigen::Map > x(result_out); eB=Eigen::Map >(B,3,1); # ifdef STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_fullPivLu x=eA.fullPivLu().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_householderQr) x=eA.householderQr().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_fullPivHouseholderQr) x=eA.fullPivHouseholderQr().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_jacobiSvd) x=eA.jacobiSVD().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_colPivHouseholderQr) x=eA.colPivHouseholderQr().solve(eB); # else x=eA.fullPivLu().solve(eB); # endif } else { Eigen::Map > eA(A,N,N); Eigen::Matrix eB(N,C); Eigen::Map > x(result_out,N,C); eB=Eigen::Map >(B,N,C); # ifdef STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_fullPivLu x=eA.fullPivLu().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_householderQr) x=eA.householderQr().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_fullPivHouseholderQr) x=eA.fullPivHouseholderQr().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_jacobiSvd) x=eA.jacobiSVD().solve(eB); # elif defined(STATISTICS_TOOLS_linalgLinSolve_EIGENMETHOD_colPivHouseholderQr) x=eA.colPivHouseholderQr().solve(eB); # else x=eA.fullPivLu().solve(eB); # endif } return true; #else // first build a N*(N+C) matrix of the form // // <---- N ----> <---- C ----> // ^ A11 A12 ... | B11 B12 ... // | A21 A22 ... | B21 B22 ... // N ... ... ... | ..... // | ... ... ... | ..... // v ... ... ... | ..... // const long msize=N*(N+C); JKQTPArrayScopedPointer m(static_cast(malloc(msize*sizeof(T)))); // use scoped pointer to ensure, that m is free'd, when the function is ending for (long l=0; l inline bool jkqtplinalgLinSolve(const T* A, T* B, long N, long C) { return jkqtplinalgLinSolve(A,B,N,C,B); } /*! \brief solve a system of N linear equations \f$ A\cdot\vec{x}=\vec{b} \f$ \ingroup jkqtptools_math_linalg \param A an NxN matrix of coefficients \param b an N-entry vector \param N number of rows and columns in \a A \param[out] result_out a N-entry vector with the result \return \c true on success \note This function uses the Gauss-Jordan algorithm \note It is save to call \c jkqtpstatLinSolve(A,B,N,C,B) with the same argument for B and result_out. Then the input will be overwritten with the new matrix! */ template inline bool jkqtplinalgLinSolve(const T* A, const T* b, long N, T* result_out) { return jkqtplinalgLinSolve(A, b, N, 1, result_out); } /*! \brief solve a system of N linear equations \f$ A\cdot\vec{x}=\vec{b} \f$ \ingroup jkqtptools_math_linalg \param A an NxN matrix of coefficients \param[in,out] b an N-entry vector (also receives the result) \param N number of rows and columns in \a A \return \c true on success \note This function uses the Gauss-Jordan algorithm \note It is save to call \c jkqtpstatLinSolve(A,B,N,C,B) with the same argument for B and result_out. Then the input will be overwritten with the new matrix! */ template inline bool jkqtplinalgLinSolve(const T* A, T* b, long N) { return jkqtplinalgLinSolve(A,b,N,1,b); } /*! \brief determinant the given NxN matrix \ingroup jkqtptools_math_linalg \tparam T of the matrix cells (typically double or float) \param a the matrix for which to calculate the determinant \param N number of rows and columns in the matrix \return the determinant of \a a \note for large matrices this algorithm is very slow, think about defining the macro JKQTP_STATISTICS_TOOLS_MAY_USE_EIGEN3 to use faster methods from the EIGEN library! */ template inline T jkqtplinalgMatrixDeterminant(const T* a, long N) { #ifdef JKQTP_STATISTICS_TOOLS_MAY_USE_EIGEN3 if (N < 1) { /* Error */ return NAN; } else if (N == 1) { /* Shouldn't get used */ return a[jkqtplinalgMatIndex(0,0,N)]; } else if (N == 2) { return a[jkqtplinalgMatIndex(0,0,N)] * a[jkqtplinalgMatIndex(1,1,N)] - a[jkqtplinalgMatIndex(1,0,N)] * a[jkqtplinalgMatIndex(0,1,N)]; } else if (N==3){ Eigen::Map > eA(a); return eA.determinant(); } else if (N==4){ Eigen::Map > eA(a); return eA.determinant(); } else { Eigen::Map > eA(a,N,N); return eA.determinant(); } #else long i,j,j1,j2; T det = 0; if (N < 1) { /* Error */ det=NAN; } else if (N == 1) { /* Shouldn't get used */ det = a[jkqtplinalgMatIndex(0,0,N)]; } else if (N == 2) { det = a[jkqtplinalgMatIndex(0,0,N)] * a[jkqtplinalgMatIndex(1,1,N)] - a[jkqtplinalgMatIndex(1,0,N)] * a[jkqtplinalgMatIndex(0,1,N)]; } else { det = 0; for (j1=0;j1 m(static_cast(calloc((N-1)*(N-1),sizeof(T *)))); for (i=1;i