LCOV - code coverage report
Current view: top level - tools - Matrix.h (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 251 301 83.4 %
Date: 2026-11-05 10:19:04 Functions: 26 27 96.3 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2011-2023 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #ifndef __PLUMED_tools_Matrix_h
      23             : #define __PLUMED_tools_Matrix_h
      24             : #include <vector>
      25             : #include <string>
      26             : #include <set>
      27             : #include <cmath>
      28             : #include "Exception.h"
      29             : #include "MatrixSquareBracketsAccess.h"
      30             : #include "Tools.h"
      31             : #include "Log.h"
      32             : #include "lapack/lapack.h"
      33             : 
      34             : namespace PLMD {
      35             : 
      36             : /// Calculate the dot product between two vectors
      37             : template <typename T> T dotProduct( const std::vector<T>& A, const std::vector<T>& B ) {
      38             :   plumed_assert( A.size()==B.size() );
      39             :   T val{};
      40             :   for(unsigned i=0; i<A.size(); ++i) {
      41             :     val+=A[i]*B[i];
      42             :   }
      43             :   return val;
      44             : }
      45             : 
      46             : /// Calculate the dot product between a vector and itself
      47          86 : template <typename T> T norm( const std::vector<T>& A ) {
      48             :   T val{};
      49         608 :   for(unsigned i=0; i<A.size(); ++i) {
      50         522 :     val+=A[i]*A[i];
      51             :   }
      52          86 :   return val;
      53             : }
      54             : 
      55             : /// This class stores a full matrix and allows one to do some simple matrix operations
      56             : template <typename T>
      57     1455812 : class Matrix:
      58             :   public MatrixSquareBracketsAccess<Matrix<T>,T> {
      59             :   /// Multiply matrix by scalar
      60             :   template <typename U> friend Matrix<U> operator*(U&, const Matrix<U>& );
      61             :   /// Matrix matrix multiply
      62             :   template <typename U> friend void mult( const Matrix<U>&, const Matrix<U>&, Matrix<U>& );
      63             :   /// Matrix times a std::vector
      64             :   template <typename U> friend void mult( const Matrix<U>&, const std::vector<U>&, std::vector<U>& );
      65             :   /// std::vector times a Matrix
      66             :   template <typename U> friend void mult( const std::vector<U>&, const Matrix<U>&, std::vector<U>& );
      67             :   /// Matrix transpose
      68             :   template <typename U> friend void transpose( const Matrix<U>&, Matrix<U>& );
      69             :   /// Output the entire matrix on a single line
      70             :   template <typename U> friend Log& operator<<(Log&, const Matrix<U>& );
      71             :   /// Output the Matrix in matrix form
      72             :   template <typename U> friend void matrixOut( Log&, const Matrix<U>& );
      73             :   /// Diagonalize a symmetric matrix - returns zero if diagonalization worked
      74             :   template <typename U> friend int diagMat( const Matrix<U>&, std::vector<U>&, Matrix<U>& );
      75             :   /// Diagonalize a symmetric matrix computing only the eigenpairs from first to last (0-based, ascending eigenvalues) - returns zero if diagonalization worked
      76             :   template <typename U> friend int diagMatRange( const Matrix<U>&, unsigned, unsigned, std::vector<U>&, Matrix<U>& );
      77             :   /// Calculate the Moore-Penrose Pseudoinverse of a matrix
      78             :   template <typename U> friend int pseudoInvert( const Matrix<U>&, Matrix<U>& );
      79             :   /// Calculate the logarithm of the determinant of a symmetric matrix - returns zero if successful
      80             :   template <typename U> friend int logdet( const Matrix<U>&, U& );
      81             :   /// Invert a matrix (works for both symmetric and asymmetric matrices) - returns zero if sucesfull
      82             :   template <typename U> friend int Invert( const Matrix<U>&, Matrix<U>& );
      83             :   /// Do a cholesky decomposition of a matrix
      84             :   template <typename U> friend void cholesky( const Matrix<U>&, Matrix<U>& );
      85             :   /// Solve a system of equations using the cholesky decomposition
      86             :   template <typename U> friend void chol_elsolve( const Matrix<U>&, const std::vector<U>&, std::vector<U>& );
      87             : private:
      88             :   /// Number of elements in matrix (nrows*ncols)
      89             :   unsigned sz;
      90             :   /// Number of rows in matrix
      91             :   unsigned rw;
      92             :   /// Number of columns in matrix
      93             :   unsigned cl;
      94             :   /// The data in the matrix
      95             :   std::vector<T> data;
      96             :   template <typename Y>
      97             :   struct  openAccVectorHelper {
      98             :     using pointer = Y*;
      99             :     size_t sz=0;
     100             :     pointer data=nullptr;
     101             :     void toACCDevice() const {
     102             : #pragma acc enter data copyin(this[0:1],sz,data[0:sz])
     103             :     }
     104             :     void removeFromACCDevice() const {
     105             : #pragma acc exit data delete(data[0:sz],sz,this[0:1])
     106             :     }
     107             :     void update(std::vector<Y>&x) {
     108     1786104 :       data = x.data();
     109     1786104 :       sz = x.size();
     110             :     }
     111             :   };
     112             :   openAccVectorHelper<T> helper{};
     113             : 
     114             : public:
     115     1635225 :   explicit Matrix(const unsigned nr=0, const unsigned nc=0 )  : sz(nr*nc), rw(nr), cl(nc), data(nr*nc) {
     116             :     helper.update(data);
     117     1635225 :   }
     118             :   void toACCDevice() const {
     119             : #pragma acc enter data copyin(this[0:1])
     120             :     helper.toACCDevice();
     121             :   }
     122             :   void removeFromACCDevice() const {
     123             :     helper.removeFromACCDevice();
     124             :     // and delete
     125             : #pragma acc exit data delete( this[0:1])
     126             :   }
     127         612 :   Matrix(const Matrix<T>& t) : sz(t.sz), rw(t.rw), cl(t.cl), data(t.data) {
     128             :     helper.update(data);
     129         612 :   }
     130             :   /// Resize the matrix
     131        1542 :   void resize( const unsigned nr, const unsigned nc ) {
     132        1542 :     rw=nr;
     133        1542 :     cl=nc;
     134        1542 :     sz=nr*nc;
     135        1542 :     data.resize(sz);
     136             :     helper.update(data);
     137        1542 :   }
     138             :   /// Return the number of rows
     139             :   inline unsigned nrows() const {
     140    16901187 :     return rw;
     141             :   }
     142             :   /// Return the number of columns
     143             :   inline unsigned ncols() const {
     144    11483960 :     return cl;
     145             :   }
     146             :   /// Return the contents of the matrix as a vector of length rw*cl
     147             :   inline std::vector<T>& getVector() {
     148             :     return data;
     149             :   }
     150             :   /// Set the matrix from a vector input
     151             :   inline void setFromVector( const std::vector<T>& vecin ) {
     152             :     plumed_assert( vecin.size()==sz );
     153             :     for(unsigned i=0; i<sz; ++i) {
     154             :       data[i]=vecin[i];
     155             :     }
     156             :   }
     157             :   /// Return element i,j of the matrix
     158             :   inline const T& operator () (const unsigned& i, const unsigned& j) const {
     159    18246495 :     return helper.data[j+i*cl];
     160             :   }
     161             :   /// Return a referenre to element i,j of the matrix
     162             :   inline T& operator () (const unsigned& i, const unsigned& j)      {
     163    68390578 :     return helper.data[j+i*cl];
     164             :   }
     165             :   /// Set all elements of the matrix equal to the value of v
     166             :   Matrix<T>& operator=(const T& v) {
     167  3010243975 :     for(unsigned i=0; i<sz; ++i) {
     168  3010178103 :       data[i]=v;
     169             :     }
     170             :     return *this;
     171             :   }
     172             :   /// Set the Matrix equal to another Matrix
     173      148725 :   Matrix<T>& operator=(const Matrix<T>& m) {
     174      148725 :     sz=m.sz;
     175      148725 :     rw=m.rw;
     176      148725 :     cl=m.cl;
     177      148725 :     data=m.data;
     178             :     helper.update(data);
     179      148725 :     return *this;
     180             :   }
     181             :   /// Set the Matrix equal to the value of a standard vector - used for readin
     182             :   Matrix<T>& operator=(const std::vector<T>& v) {
     183             :     plumed_dbg_assert( v.size()==sz );
     184             :     for(unsigned i=0; i<sz; ++i) {
     185             :       data[i]=v[i];
     186             :     }
     187             :     return *this;
     188             :   }
     189             :   /// Add v to all elements of the Matrix
     190             :   Matrix<T> operator+=(const T& v) {
     191             :     for(unsigned i=0; i<sz; ++i) {
     192             :       data[i]+=v;
     193             :     }
     194             :     return *this;
     195             :   }
     196             :   /// Multiply all elements by v
     197         125 :   Matrix<T> operator*=(const T& v) {
     198       55038 :     for(unsigned i=0; i<sz; ++i) {
     199       54913 :       data[i]*=v;
     200             :     }
     201         125 :     return *this;
     202             :   }
     203             :   /// Matrix addition
     204             :   Matrix<T>& operator+=(const Matrix<T>& m) {
     205             :     plumed_dbg_assert( m.rw==rw && m.cl==cl );
     206             :     data+=m.data;
     207             :     return *this;
     208             :   }
     209             :   /// Subtract v from all elements of the Matrix
     210             :   Matrix<T> operator-=(const T& v) {
     211             :     for(unsigned i=0; i<sz; ++i) {
     212             :       data-=v;
     213             :     }
     214             :     return *this;
     215             :   }
     216             :   /// Matrix subtraction
     217             :   Matrix<T>& operator-=(const Matrix<T>& m) {
     218             :     plumed_dbg_assert( m.rw==rw && m.cl==cl );
     219             :     data-=m.data;
     220             :     return *this;
     221             :   }
     222             :   /// Test if the matrix is symmetric or not
     223      160688 :   unsigned isSymmetric() const {
     224      160688 :     if (rw!=cl) {
     225             :       return 0;
     226             :     }
     227             :     unsigned sym=1;
     228      319266 :     for(unsigned i=1; i<rw; ++i)
     229      374415 :       for(unsigned j=0; j<i; ++j)
     230      350119 :         if( std::fabs(data[i+j*cl]-data[j+i*cl])>1.e-10 ) {
     231             :           sym=0;
     232             :           break;
     233             :         }
     234             :     return sym;
     235             :   }
     236             : };
     237             : 
     238             : /// Multiply matrix by scalar
     239             : template <typename T> Matrix<T> operator*(T& v, const Matrix<T>& m ) {
     240             :   Matrix<T> new_m(m);
     241             :   new_m*=v;
     242             :   return new_m;
     243             : }
     244             : 
     245        8572 : template <typename T> void mult( const Matrix<T>& A, const Matrix<T>& B, Matrix<T>& C ) {
     246        8572 :   plumed_assert(A.cl==B.rw);
     247        8572 :   if( A.rw !=C.rw  || B.cl !=C.cl ) {
     248          36 :     C.resize( A.rw, B.cl );
     249             :   }
     250             :   C=static_cast<T>( 0 );
     251             :   // i-k-j loop order: the innermost loop runs over contiguous elements of B and C,
     252             :   // so it is cache friendly and can be vectorized. For each element of C the
     253             :   // products are summed in the same order as with the i-j-k loop order.
     254             :   const T* a=A.data.data();
     255             :   const T* b=B.data.data();
     256             :   T* c=C.data.data();
     257       71524 :   for(unsigned i=0; i<A.rw; ++i) {
     258       62952 :     T* ci=c+i*B.cl;
     259     7594384 :     for(unsigned k=0; k<A.cl; ++k) {
     260     7531432 :       const T aik=a[i*A.cl+k];
     261     7531432 :       const T* bk=b+k*B.cl;
     262    24111134 :       for(unsigned j=0; j<B.cl; ++j) {
     263    16579702 :         ci[j]+=aik*bk[j];
     264             :       }
     265             :     }
     266             :   }
     267        8572 : }
     268             : 
     269      136364 : template <typename T> void mult( const Matrix<T>& A, const std::vector<T>& B, std::vector<T>& C) {
     270      136364 :   plumed_assert( A.cl==B.size() );
     271      136364 :   if( C.size()!=A.rw  ) {
     272          94 :     C.resize(A.rw);
     273             :   }
     274      439992 :   for(unsigned i=0; i<A.rw; ++i) {
     275      303628 :     C[i]= static_cast<T>( 0 );
     276             :   }
     277      439992 :   for(unsigned i=0; i<A.rw; ++i)
     278     1484656 :     for(unsigned k=0; k<A.cl; ++k) {
     279     1181028 :       C[i]+=A(i,k)*B[k] ;
     280             :     }
     281      136364 : }
     282             : 
     283             : template <typename T> void mult( const std::vector<T>& A, const Matrix<T>& B, std::vector<T>& C) {
     284             :   plumed_assert( B.rw==A.size() );
     285             :   if( C.size()!=B.cl ) {
     286             :     C.resize( B.cl );
     287             :   }
     288             :   for(unsigned i=0; i<B.cl; ++i) {
     289             :     C[i]=static_cast<T>( 0 );
     290             :   }
     291             :   // loop on the rows of B in the outer loop, so that the inner loop is on contiguous elements
     292             :   for(unsigned k=0; k<B.rw; ++k)
     293             :     for(unsigned i=0; i<B.cl; ++i) {
     294             :       C[i]+=A[k]*B(k,i);
     295             :     }
     296             : }
     297             : 
     298         766 : template <typename T> void transpose( const Matrix<T>& A, Matrix<T>& AT ) {
     299         766 :   if( A.rw!=AT.cl || A.cl!=AT.rw ) {
     300          36 :     AT.resize( A.cl, A.rw );
     301             :   }
     302        3482 :   for(unsigned i=0; i<A.cl; ++i)
     303       70854 :     for(unsigned j=0; j<A.rw; ++j) {
     304       68138 :       AT(i,j)=A(j,i);
     305             :     }
     306         766 : }
     307             : 
     308             : template <typename T> Log& operator<<(Log& ostr, const Matrix<T>& mat) {
     309             :   for(unsigned i=0; i<mat.sz; ++i) {
     310             :     ostr<<mat.data[i]<<" ";
     311             :   }
     312             :   return ostr;
     313             : }
     314             : 
     315          33 : template <typename T> void matrixOut( Log& ostr, const Matrix<T>& mat) {
     316         111 :   for(unsigned i=0; i<mat.rw; ++i) {
     317         270 :     for(unsigned j=0; j<mat.cl; ++j) {
     318         192 :       ostr<<mat(i,j)<<" ";
     319             :     }
     320          78 :     ostr<<"\n";
     321             :   }
     322          33 :   return;
     323             : }
     324             : 
     325             : /// Cholesky factorization A=L*L^T of a symmetric positive definite n x n matrix.
     326             : /// Only the upper triangle of A is read (as done by diagMat). L is returned as a lower
     327             : /// triangular matrix stored row by row in a vector of size n*n.
     328             : /// Returns false if A is not positive definite (or contains NaNs).
     329       15606 : template <typename T> bool choleskyFactorization( const Matrix<T>& A, std::vector<T>& L ) {
     330             :   const unsigned n=A.nrows();
     331       15606 :   plumed_assert( A.ncols()==n );
     332             :   // Right-looking factorization A=U^T*U working on the rows of the upper triangle,
     333             :   // so that the innermost loop is a vectorizable update of contiguous elements
     334       15606 :   std::vector<T> U(static_cast<std::size_t>(n)*n,static_cast<T>(0));
     335       43170 :   for(unsigned j=0; j<n; ++j)
     336       67087 :     for(unsigned i=j; i<n; ++i) {
     337       39523 :       U[static_cast<std::size_t>(j)*n+i]=A(j,i);
     338             :     }
     339       43170 :   for(unsigned j=0; j<n; ++j) {
     340       27564 :     T* Uj=U.data()+static_cast<std::size_t>(j)*n;
     341       27564 :     if( !(Uj[j]>0) ) {
     342             :       return false;
     343             :     }
     344       27564 :     Uj[j]=std::sqrt(Uj[j]);
     345       27564 :     const T invd=static_cast<T>(1)/Uj[j];
     346       39523 :     for(unsigned i=j+1; i<n; ++i) {
     347       11959 :       Uj[i]*=invd;
     348             :     }
     349       39523 :     for(unsigned i=j+1; i<n; ++i) {
     350       11959 :       const T uji=Uj[i];
     351       11959 :       T* Ui=U.data()+static_cast<std::size_t>(i)*n;
     352       23919 :       for(unsigned k=i; k<n; ++k) {
     353       11960 :         Ui[k]-=uji*Uj[k];
     354             :       }
     355             :     }
     356             :   }
     357       15606 :   L.assign(static_cast<std::size_t>(n)*n,static_cast<T>(0));
     358       43170 :   for(unsigned i=0; i<n; ++i)
     359       67087 :     for(unsigned j=0; j<=i; ++j) {
     360       39523 :       L[static_cast<std::size_t>(i)*n+j]=U[static_cast<std::size_t>(j)*n+i];
     361             :     }
     362             :   return true;
     363             : }
     364             : 
     365             : /// Inverse of a symmetric positive definite matrix from its Cholesky factorization.
     366             : /// Returns false (and leaves inverse untouched) if A is not positive definite.
     367       15606 : template <typename T> bool choleskyInvert( const Matrix<T>& A, Matrix<T>& inverse ) {
     368             :   const unsigned n=A.nrows();
     369       15606 :   std::vector<T> L;
     370       15606 :   if( !choleskyFactorization( A, L ) ) {
     371             :     return false;
     372             :   }
     373             :   // Compute M=L^{-1} (lower triangular) row by row: M_i = (e_i - sum_{k<i} L_ik M_k) / L_ii
     374       15606 :   std::vector<T> M(static_cast<std::size_t>(n)*n,static_cast<T>(0));
     375       43170 :   for(unsigned i=0; i<n; ++i) {
     376       27564 :     const T* Li=L.data()+static_cast<std::size_t>(i)*n;
     377       27564 :     T* Mi=M.data()+static_cast<std::size_t>(i)*n;
     378       27564 :     Mi[i]=static_cast<T>(1);
     379       39523 :     for(unsigned k=0; k<i; ++k) {
     380       11959 :       const T lik=Li[k];
     381       11959 :       const T* Mk=M.data()+static_cast<std::size_t>(k)*n;
     382       23919 :       for(unsigned j=0; j<=k; ++j) {
     383       11960 :         Mi[j]-=lik*Mk[j];
     384             :       }
     385             :     }
     386       27564 :     const T invd=static_cast<T>(1)/Li[i];
     387       67087 :     for(unsigned j=0; j<=i; ++j) {
     388       39523 :       Mi[j]*=invd;
     389             :     }
     390             :   }
     391             :   // A^{-1} = M^T M = sum_k (row k of M)^T (row k of M); compute the lower triangle
     392       15606 :   if( inverse.nrows()!=n || inverse.ncols()!=n ) {
     393           0 :     inverse.resize(n,n);
     394             :   }
     395             :   inverse=static_cast<T>(0);
     396       43170 :   for(unsigned k=0; k<n; ++k) {
     397       27564 :     const T* Mk=M.data()+static_cast<std::size_t>(k)*n;
     398       67087 :     for(unsigned p=0; p<=k; ++p) {
     399       39523 :       const T mkp=Mk[p];
     400       91006 :       for(unsigned q=0; q<=p; ++q) {
     401       51483 :         inverse(p,q)+=mkp*Mk[q];
     402             :       }
     403             :     }
     404             :   }
     405       43170 :   for(unsigned p=0; p<n; ++p)
     406       39523 :     for(unsigned q=0; q<p; ++q) {
     407       11959 :       inverse(q,p)=inverse(p,q);
     408             :     }
     409             :   return true;
     410             : }
     411             : 
     412        7146 : template <typename T> int diagMatRange( const Matrix<T>& A, unsigned first, unsigned last, std::vector<T>& eigenvals, Matrix<T>& eigenvecs ) {
     413             : 
     414             :   // Check matrix is square and symmetric
     415        7146 :   plumed_assert( A.rw==A.cl );
     416        7146 :   plumed_assert( A.isSymmetric()==1 );
     417        7146 :   if( eigenvals.size()!=A.cl ) {
     418           9 :     eigenvals.resize( A.cl );
     419             :   }
     420        7146 :   if( eigenvecs.rw!=A.rw || eigenvecs.cl!=A.cl ) {
     421           9 :     eigenvecs.resize( A.rw, A.cl );
     422             :   }
     423        7146 :   if( A.cl==0 ) {
     424             :     return 0;
     425             :   }
     426        7146 :   plumed_assert( first<=last && last<A.cl );
     427        7146 :   std::vector<T> da(A.sz);
     428             :   unsigned k=0;
     429        7146 :   std::vector<T> evals(A.cl);
     430             :   // Transfer the matrix to the local array
     431       24969 :   for (unsigned i=0; i<A.cl; ++i)
     432      440076 :     for (unsigned j=0; j<A.rw; ++j) {
     433      422253 :       da[k++]=static_cast<T>( A(j,i) );
     434             :     }
     435             : 
     436        7146 :   int n=A.cl;
     437        7146 :   int lwork=-1;
     438        7146 :   int liwork=-1;
     439             :   int m;
     440        7146 :   int info=0;
     441             : #ifdef __PLUMED_HAS_EXTERNAL_LAPACK
     442             :   const bool partial=true;
     443             : #else
     444             :   // The internal dsyevr reads out of bounds when only a subset of the eigenpairs is
     445             :   // requested, so all of them are computed and then the requested ones are copied
     446             :   const bool partial=false;
     447             : #endif
     448             :   // dsyevr numbers the eigenvalues from 1, in ascending order
     449             :   const unsigned offset=( partial ? first : 0 );
     450        7146 :   int il=offset+1;
     451        7146 :   int iu=( partial ? last+1 : A.cl );
     452             :   const unsigned nev=iu-il+1;
     453        7146 :   std::vector<T> work(A.cl);
     454        7146 :   std::vector<int> iwork(A.cl);
     455        7146 :   T vl, vu, abstol=0.0;
     456        7146 :   std::vector<int> isup(2*nev);
     457        7146 :   std::vector<T> evecs(static_cast<std::size_t>(A.cl)*nev);
     458             :   if constexpr (std::is_same_v<T,double>) {
     459        7146 :     plumed_lapack_dsyevr("V", "I", "U", &n, da.data(), &n, &vl, &vu, &il, &iu,
     460             :                          &abstol, &m, evals.data(), evecs.data(), &n,
     461             :                          isup.data(), work.data(), &lwork, iwork.data(), &liwork, &info);
     462             :   } else if constexpr (std::is_same_v<T,float>) {
     463             :     plumed_lapack_ssyevr("V", "I", "U", &n, da.data(), &n, &vl, &vu, &il, &iu,
     464             :                          &abstol, &m, evals.data(), evecs.data(), &n,
     465             :                          isup.data(), work.data(), &lwork, iwork.data(), &liwork, &info);
     466             :   } else {
     467             :     static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "diagMat is only compatible with float and double");
     468             :   }
     469        7146 :   if (info!=0) {
     470             :     return info;
     471             :   }
     472             : 
     473             :   // Retrieve correct sizes for work and iwork then reallocate
     474        7146 :   liwork=iwork[0];
     475        7146 :   iwork.resize(liwork);
     476        7146 :   lwork=static_cast<int>( work[0] );
     477        7146 :   work.resize(lwork);
     478             : 
     479             :   if constexpr (std::is_same_v<T,double>) {
     480        7146 :     plumed_lapack_dsyevr("V", "I", "U", &n, da.data(), &n, &vl, &vu, &il, &iu,
     481             :                          &abstol, &m, evals.data(), evecs.data(), &n,
     482             :                          isup.data(), work.data(), &lwork, iwork.data(), &liwork, &info);
     483             :   } else if constexpr (std::is_same_v<T,float>) {
     484             :     plumed_lapack_ssyevr("V", "I", "U", &n, da.data(), &n, &vl, &vu, &il, &iu,
     485             :                          &abstol, &m, evals.data(), evecs.data(), &n,
     486             :                          isup.data(), work.data(), &lwork, iwork.data(), &liwork, &info);
     487             :   } else {
     488             :     static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "diagMat is only compatible with float and double");
     489             :   }
     490        7146 :   if (info!=0) {
     491             :     return info;
     492             :   }
     493             : 
     494        7146 :   k=(first-offset)*A.rw;
     495       23653 :   for(unsigned i=first; i<=last; ++i) {
     496       16507 :     eigenvals[i]=evals[i-offset];
     497             :     // N.B. For ease of producing projectors we store the eigenvectors
     498             :     // ROW-WISE in the eigenvectors matrix.  The first index is the
     499             :     // eigenvector number and the second the component
     500       79211 :     for(unsigned j=0; j<A.rw; ++j) {
     501       62704 :       eigenvecs(i,j)=evecs[k++];
     502             :     }
     503             :   }
     504             : 
     505             :   // This changes eigenvectors so that the first non-null element
     506             :   // of each of them is positive
     507             :   // We can do it because the phase is arbitrary, and helps making
     508             :   // the result reproducible
     509       23653 :   for(unsigned i=first; i<=last; ++i) {
     510             :     int j;
     511       16561 :     for(j=0; j<n; j++)
     512       16561 :       if(eigenvecs(i,j)*eigenvecs(i,j)>1e-14) {
     513             :         break;
     514             :       }
     515       16507 :     if(j<n)
     516       16507 :       if(eigenvecs(i,j)<0.0)
     517       22853 :         for(j=0; j<n; j++) {
     518       19682 :           eigenvecs(i,j)*=-1;
     519             :         }
     520             :   }
     521             :   return 0;
     522           0 : }
     523             : 
     524        7127 : template <typename T> int diagMat( const Matrix<T>& A, std::vector<T>& eigenvals, Matrix<T>& eigenvecs ) {
     525        7127 :   if( A.cl==0 ) {
     526           0 :     return diagMatRange( A, 0, 0, eigenvals, eigenvecs );
     527             :   }
     528        7127 :   return diagMatRange( A, 0, A.cl-1, eigenvals, eigenvecs );
     529             : }
     530             : 
     531           1 : template <typename T> int pseudoInvert( const Matrix<T>& A, Matrix<T>& pseudoinverse ) {
     532           1 :   const unsigned nrows=A.rw, ncols=A.cl;
     533           1 :   const unsigned nsv=std::min(nrows,ncols);
     534           1 :   if( pseudoinverse.rw!=ncols || pseudoinverse.cl!=nrows ) {
     535           0 :     pseudoinverse.resize( ncols, nrows );
     536             :   }
     537             :   pseudoinverse=static_cast<T>( 0 );
     538           1 :   if( nsv==0 ) {
     539             :     return 0;
     540             :   }
     541             : 
     542           1 :   std::vector<T> da(A.sz);
     543             :   unsigned k=0;
     544             :   // Transfer the matrix to the local array
     545         501 :   for (unsigned i=0; i<A.cl; ++i)
     546      250500 :     for (unsigned j=0; j<A.rw; ++j) {
     547      250000 :       da[k++]=static_cast<T>( A(j,i) );
     548             :     }
     549             : 
     550           1 :   int info=0;
     551           1 :   int nr=nrows, nc=ncols;
     552             : 
     553             :   // Create some containers for stuff from the singular value decomposition A=U*S*VT
     554             :   // (U is nrows x nrows and VT is ncols x ncols, both stored column by column).
     555             :   // The thin decomposition (JOBZ='S') would be enough, but the internal dgesdd
     556             :   // writes out of bounds with JOBZ='S', so the full one is used.
     557           1 :   std::vector<T> S(nsv);
     558           1 :   std::vector<T> U(static_cast<std::size_t>(nrows)*nrows);
     559           1 :   std::vector<T> VT(static_cast<std::size_t>(ncols)*ncols);
     560           1 :   std::vector<int> iwork(8*nsv);
     561             : 
     562             :   // This optimizes the size of the work array used in lapack singular value decomposition
     563           1 :   int lwork=-1;
     564           1 :   std::vector<T> work(1);
     565             :   if constexpr (std::is_same_v<T,double> ) {
     566           1 :     plumed_lapack_dgesdd( "A", &nr, &nc, da.data(), &nr, S.data(),
     567             :                           U.data(), &nr, VT.data(), &nc, work.data(),
     568             :                           &lwork, iwork.data(), &info );
     569             :   } else if constexpr (std::is_same_v<T,float>) {
     570             :     plumed_lapack_sgesdd( "A", &nr, &nc, da.data(), &nr, S.data(),
     571             :                           U.data(), &nr, VT.data(), &nc, work.data(),
     572             :                           &lwork, iwork.data(), &info );
     573             :   } else {
     574             :     static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "pseudoInvert works only for float and double");
     575             :   }
     576           1 :   if(info!=0) {
     577             :     return info;
     578             :   }
     579             : 
     580             :   // Retrieve correct sizes for work and rellocate
     581           1 :   lwork=(int) work[0];
     582           1 :   work.resize(lwork);
     583             : 
     584             :   // This does the singular value decomposition
     585             :   if constexpr (std::is_same_v<T,double> ) {
     586           1 :     plumed_lapack_dgesdd( "A", &nr, &nc, da.data(), &nr, S.data(),
     587             :                           U.data(), &nr, VT.data(), &nc, work.data(),
     588             :                           &lwork, iwork.data(), &info );
     589             :   } else if constexpr (std::is_same_v<T,float>) {
     590             :     plumed_lapack_sgesdd( "A", &nr, &nc, da.data(), &nr, S.data(),
     591             :                           U.data(), &nr, VT.data(), &nc, work.data(),
     592             :                           &lwork, iwork.data(), &info );
     593             :   } else {
     594             :     static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "pseudoInvert works only for float and double");
     595             :   }
     596           1 :   if(info!=0) {
     597             :     return info;
     598             :   }
     599             : 
     600             :   // Compute the tolerance on the singular values ( machine epsilon * number of singular values * maximum singular value )
     601             :   T tol;
     602           1 :   tol=S[0];
     603         500 :   for(unsigned i=1; i<nsv; ++i) {
     604         499 :     if( S[i]>tol ) {
     605             :       tol=S[i];
     606             :     }
     607             :   }
     608           1 :   tol*=nsv*epsilon;
     609             : 
     610             :   // pseudoinverse = V * S^{-1} * U^T = sum_k (column k of V) (column k of U)^T / S_k,
     611             :   // summing only over the singular values larger than the tolerance
     612         501 :   for(unsigned isv=0; isv<nsv; ++isv) {
     613         500 :     if( !(S[isv]>tol) ) {
     614           1 :       continue;
     615             :     }
     616         499 :     const T inv=static_cast<T>(1)/S[isv];
     617         499 :     const T* Uk=U.data()+static_cast<std::size_t>(isv)*nrows;
     618      249999 :     for(unsigned i=0; i<ncols; ++i) {
     619      249500 :       const T a=inv*VT[isv+static_cast<std::size_t>(i)*ncols];
     620   124999500 :       for(unsigned j=0; j<nrows; ++j) {
     621   124750000 :         pseudoinverse(i,j)+=a*Uk[j];
     622             :       }
     623             :     }
     624             :   }
     625             : 
     626             :   return 0;
     627             : }
     628             : 
     629      149887 : template <typename T> int Invert( const Matrix<T>& A, Matrix<T>& inverse ) {
     630             : 
     631      149887 :   if( A.isSymmetric()==1 ) {
     632             :     // Symmetric positive definite matrices (e.g. covariance and metric matrices) are inverted
     633             :     // with a Cholesky factorization, which is several times faster than diagonalizing them.
     634       15606 :     if( choleskyInvert( A, inverse ) ) {
     635       15606 :       return 0;
     636             :     }
     637             :     // GAT -- I only ever use symmetric matrices so I can invert them like this.
     638             :     // I choose to do this as I have had problems with the more general way of doing this that
     639             :     // is implemented below.
     640           0 :     std::vector<T> eval(A.rw);
     641           0 :     Matrix<T> evec(A.rw,A.cl), tevec(A.rw,A.cl);
     642             :     int err;
     643           0 :     err=diagMat( A, eval, evec );
     644           0 :     if(err!=0) {
     645             :       return err;
     646             :     }
     647           0 :     for (unsigned i=0; i<A.rw; ++i)
     648           0 :       for (unsigned j=0; j<A.cl; ++j) {
     649           0 :         tevec(i,j)=evec(j,i)/eval[j];
     650             :       }
     651           0 :     mult(tevec,evec,inverse);
     652             :   } else {
     653      134281 :     std::vector<T> da(A.sz);
     654      134281 :     std::vector<int> ipiv(A.cl);
     655             :     unsigned k=0;
     656      134281 :     int n=A.rw;
     657      134281 :     int info=0;
     658      402846 :     for(unsigned i=0; i<A.cl; ++i)
     659      805710 :       for(unsigned j=0; j<A.rw; ++j) {
     660      537145 :         da[k++]=static_cast<T>( A(j,i) );
     661             :       }
     662             :     if constexpr (std::is_same_v<T,double> ) {
     663      134281 :       plumed_lapack_dgetrf(&n,&n,da.data(),&n,ipiv.data(),&info);
     664             :     } else if constexpr (std::is_same_v<T,float>) {
     665             :       plumed_lapack_sgetrf(&n,&n,da.data(),&n,ipiv.data(),&info);
     666             :     } else {
     667             :       static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "Invert works only for float and double");
     668             :     }
     669      134281 :     if(info!=0) {
     670             :       return info;
     671             :     }
     672             : 
     673      134281 :     int lwork=-1;
     674      134281 :     std::vector<T> work(A.cl);
     675             :     if constexpr (std::is_same_v<T,double> ) {
     676      134281 :       plumed_lapack_dgetri(&n,da.data(),&n,ipiv.data(),work.data(),&lwork,&info);
     677             :     } else if constexpr (std::is_same_v<T,float>) {
     678             :       plumed_lapack_sgetri(&n,da.data(),&n,ipiv.data(),work.data(),&lwork,&info);
     679             :     } else {
     680             :       static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "Invert works only for float and double");
     681             :     }
     682      134281 :     if(info!=0) {
     683             :       return info;
     684             :     }
     685             : 
     686      134281 :     lwork=static_cast<int>( work[0] );
     687      134281 :     work.resize(lwork);
     688             :     if constexpr (std::is_same_v<T,double> ) {
     689      134281 :       plumed_lapack_dgetri(&n,da.data(),&n,ipiv.data(),work.data(),&lwork,&info);
     690             :     } else if constexpr (std::is_same_v<T,float>) {
     691             :       plumed_lapack_sgetri(&n,da.data(),&n,ipiv.data(),work.data(),&lwork,&info);
     692             :     } else {
     693             :       static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "Invert works only for float and double");
     694             :     }
     695      134281 :     if(info!=0) {
     696             :       return info;
     697             :     }
     698             : 
     699      134281 :     if( inverse.cl!=A.cl || inverse.rw!=A.rw ) {
     700           0 :       inverse.resize(A.rw,A.cl);
     701             :     }
     702             :     k=0;
     703      402846 :     for(unsigned i=0; i<A.rw; ++i)
     704      805710 :       for(unsigned j=0; j<A.cl; ++j) {
     705      537145 :         inverse(j,i)=da[k++];
     706             :       }
     707           0 :   }
     708             : 
     709             :   return 0;
     710             : }
     711             : 
     712        3655 : template <typename T> void cholesky( const Matrix<T>& A, Matrix<T>& B ) {
     713             : 
     714        3655 :   plumed_assert( A.rw==A.cl && A.isSymmetric() );
     715        3655 :   Matrix<T> L(A.rw,A.cl);
     716             :   L=0.;
     717        3655 :   std::vector<T> D(A.rw,0.);
     718        8969 :   for(unsigned i=0; i<A.rw; ++i) {
     719        5314 :     L(i,i)=static_cast<T>( 1 );
     720        6973 :     for (unsigned j=0; j<i; ++j) {
     721        1659 :       L(i,j)=A(i,j);
     722        1659 :       for (unsigned k=0; k<j; ++k) {
     723           0 :         L(i,j)-=L(i,k)*L(j,k)*D[k];
     724             :       }
     725        1659 :       if (D[j]!=0.) {
     726        1659 :         L(i,j)/=D[j];
     727             :       } else {
     728           0 :         L(i,j)=static_cast<T>( 0 );
     729             :       }
     730             :     }
     731        5314 :     D[i]=A(i,i);
     732        6973 :     for (unsigned k=0; k<i; ++k) {
     733        1659 :       D[i]-=L(i,k)*L(i,k)*D[k];
     734             :     }
     735             :   }
     736             : 
     737        8969 :   for(unsigned i=0; i<A.rw; ++i) {
     738        5314 :     D[i]=(D[i]>0.?std::sqrt(D[i]):0.);
     739             :   }
     740        3655 :   if( B.rw!=A.rw || B.cl!=A.cl ) {
     741           0 :     B.resize( A.rw, A.cl);
     742             :   }
     743             :   B=0.;
     744        8969 :   for(unsigned i=0; i<A.rw; ++i)
     745       12287 :     for(unsigned j=0; j<=i; ++j) {
     746        6973 :       B(i,j)+=L(i,j)*D[j];
     747             :     }
     748        3655 : }
     749             : 
     750             : template <typename T> void chol_elsolve( const Matrix<T>& M, const std::vector<T>& b, std::vector<T>& y ) {
     751             : 
     752             :   plumed_assert( M.rw==M.cl && M(0,1)==0.0 && b.size()==M.rw );
     753             :   if( y.size()!=M.rw ) {
     754             :     y.resize( M.rw );
     755             :   }
     756             :   for(unsigned i=0; i<M.rw; ++i) {
     757             :     y[i]=b[i];
     758             :     for(unsigned j=0; j<i; ++j) {
     759             :       y[i]-=M(i,j)*y[j];
     760             :     }
     761             :     y[i]*=1.0/M(i,i);
     762             :   }
     763             : }
     764             : 
     765           0 : template <typename T> int logdet( const Matrix<T>& M, T& ldet ) {
     766             :   // Check matrix is square and symmetric
     767           0 :   plumed_assert( M.rw==M.cl || M.isSymmetric() );
     768             : 
     769             :   // For a positive definite matrix, log(det(M)) = 2 sum_i log(L_ii) with M=L*L^T
     770             :   {
     771           0 :     std::vector<T> L;
     772           0 :     if( choleskyFactorization( M, L ) ) {
     773           0 :       ldet=0;
     774           0 :       for(unsigned i=0; i<M.rw; ++i) {
     775           0 :         ldet+=2*std::log(L[static_cast<std::size_t>(i)*M.rw+i]);
     776             :       }
     777             :       return 0;
     778             :     }
     779             :   }
     780             : 
     781           0 :   std::vector<T> da(M.sz);
     782             :   unsigned k=0;
     783           0 :   std::vector<T> evals(M.cl);
     784             :   // Transfer the matrix to the local array
     785           0 :   for (unsigned i=0; i<M.rw; ++i)
     786           0 :     for (unsigned j=0; j<M.cl; ++j) {
     787           0 :       da[k++]=static_cast<T>( M(j,i) );
     788             :     }
     789             : 
     790           0 :   int n=M.cl;
     791           0 :   int lwork=-1;
     792           0 :   int liwork=-1;
     793           0 :   int info=0;
     794             :   int m;
     795           0 :   int one=1;
     796           0 :   std::vector<T> work(M.rw);
     797           0 :   std::vector<int> iwork(M.rw);
     798           0 :   T vl, vu, abstol=0.0;
     799           0 :   std::vector<int> isup(2*M.rw);
     800           0 :   std::vector<T> evecs(M.sz);
     801             :   if constexpr (std::is_same_v<T,double> ) {
     802           0 :     plumed_lapack_dsyevr("N", "I", "U", &n, da.data(), &n, &vl, &vu, &one, &n,
     803             :                          &abstol, &m, evals.data(), evecs.data(), &n,
     804             :                          isup.data(), work.data(), &lwork, iwork.data(), &liwork, &info);
     805             :   } else if constexpr (std::is_same_v<T,float>) {
     806             :     plumed_lapack_ssyevr("N", "I", "U", &n, da.data(), &n, &vl, &vu, &one, &n,
     807             :                          &abstol, &m, evals.data(), evecs.data(), &n,
     808             :                          isup.data(), work.data(), &lwork, iwork.data(), &liwork, &info);
     809             :   } else {
     810             :     static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "logdet works only for float and double");
     811             :   }
     812           0 :   if (info!=0) {
     813             :     return info;
     814             :   }
     815             : 
     816             :   // Retrieve correct sizes for work and iwork then reallocate
     817           0 :   lwork=static_cast<int>( work[0] );
     818           0 :   work.resize(lwork);
     819           0 :   liwork=iwork[0];
     820           0 :   iwork.resize(liwork);
     821             :   if constexpr (std::is_same_v<T,double> ) {
     822           0 :     plumed_lapack_dsyevr("N", "I", "U", &n, da.data(), &n, &vl, &vu, &one, &n,
     823             :                          &abstol, &m, evals.data(), evecs.data(), &n,
     824             :                          isup.data(), work.data(), &lwork, iwork.data(), &liwork, &info);
     825             :   } else if constexpr (std::is_same_v<T,float>) {
     826             :     plumed_lapack_ssyevr("N", "I", "U", &n, da.data(), &n, &vl, &vu, &one, &n,
     827             :                          &abstol, &m, evals.data(), evecs.data(), &n,
     828             :                          isup.data(), work.data(), &lwork, iwork.data(), &liwork, &info);
     829             :   } else {
     830             :     static_assert(std::is_same_v<T,double> || std::is_same_v<T,float>, "logdet works only for float and double");
     831             :   }
     832           0 :   if (info!=0) {
     833             :     return info;
     834             :   }
     835             : 
     836             :   // Transfer the eigenvalues and eigenvectors to the output
     837           0 :   ldet=0;
     838           0 :   for(unsigned i=0; i<M.cl; i++) {
     839           0 :     ldet+=log(evals[i]);
     840             :   }
     841             : 
     842             :   return 0;
     843             : }
     844             : 
     845             : 
     846             : 
     847             : }
     848             : #endif

Generated by: LCOV version 1.16