From 74479c1a2761b19beb47d6f49ad86d01898f5df2 Mon Sep 17 00:00:00 2001 From: Jiri Pittner Date: Thu, 5 Nov 2026 23:23:55 +0100 Subject: [PATCH] csrmat claude-coded --- CLAUDE.md | 11 +- csrmat.cc | 965 +++++++++++++++++++++++++++++++++++++++++-- csrmat.h | 222 ++++++---- doc/bib/sparse.tex | 2 + doc/concepts.tex | 4 +- doc/introduction.tex | 2 +- doc/mat.tex | 4 +- doc/nonclass.tex | 6 +- doc/smat.tex | 2 +- doc/sparse.tex | 122 +++++- doc/vec.tex | 4 +- mat.h | 4 + smat.h | 1 + sparsemat.h | 1 + t.cc | 2 - vec.h | 5 + 16 files changed, 1230 insertions(+), 127 deletions(-) create mode 100644 doc/bib/sparse.tex diff --git a/CLAUDE.md b/CLAUDE.md index 69251c3..94f7ea8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -66,6 +66,12 @@ decisions and plans agreed with the author, so that they survive a fresh clone. adds x at (i,j) and conj(x) at (j,i); NRSMat(SparseSMat) takes the lower triangle; operator* is the general product A B (correct for hermitian, complex symmetric exp(iH) and general matrices); SparseSMat::gemm computes A^T B (A^\dagger B with 'c'); otimes_sparse = M + M^\dagger incl. doubled diagonal. +- CSRMat (completed 2026-11): compressed row format, members NRVec rowptr/col/v (share independently, value-only operations keep + the index arrays shared), columns strictly ascending in each row, full matrix always stored; flag symmetric only DECLARES hermitian + (A=A^dagger, for davidson/lanczos), set by CSRMat(NRSMat) and for real types from a symmetric SparseMat, never checked. Built by + conversion (SparseMat, SparseSMat, NRMat, NRSMat, arrays); conversions drop |x|<=sparse_epsilon, arithmetic keeps cancelled zeros + (simplify). dot conjugates the left operand (as NRMat); permuteme_rows permutes only rows; CSR*CSR = Gustavson; types double, float, + complex, complex, CPU only (cuSPARSE format for later). Tests bugtests/core/csrmat1,3,4,5 and iterative/csrmat2. - SparseMat with the symmetric flag is complex symmetric (no conjugation); its permuteme_* members use 0-based matrix indices, NRPerm/CyclePerm are always 1-based. - Permutations: applypermutation(p,v,inverse) uses the same convention as NRVec::permuted (r_{p(i)}=v_i @@ -83,8 +89,7 @@ decisions and plans agreed with the author, so that they survive a fresh clone. - a-X and a/X (scalar minus/divided by object) are deleted on purpose; a single integer constructor is forbidden for NRMat, NRMat_from1, Mat3 (Mat3(double) works). - Real-storage gdiagonalize uses the dgeev convention (Re + i Im). -- The NRMat offset view constructor is a feature. MATPTR is kept (rarely used). CSRMat is unfinished and - documented as unsupported, but kept. NRMat::strassen (2026-11-06): native Strassen-Winograd in strassen.cc, gemm-compatible +- The NRMat offset view constructor is a feature. MATPTR is kept (rarely used). CSRMat: see above. NRMat::strassen (2026-11-06): native Strassen-Winograd in strassen.cc, gemm-compatible parameters + cutoff (default LA_traits::strassen_cutoff()=2048 all types, to be tuned on HPC), explicit only (gemm stays classical), CBLAS leaves on strided quadrants, device primitives separated for CUDA; NO_STRASSEN removed everywhere. CUDA (CUDALA) is deferred/experimental until the author has NVIDIA RTX hardware (known CUDA issues collected in suspected_bugs.md F10). @@ -98,7 +103,7 @@ decisions and plans agreed with the author, so that they survive a fresh clone. conjgrad, gmres, davidson, lanczos, diis: no changes needed beyond the defaults). Float twins are generated from the double specializations (placed right after them). Precision- dependent defaults live in LA_precision (la_traits.h, only double and float; double values unchanged). optimize done 2026-11-05: own trait opt_precision in optimize.h, INSTANTIZE(float), float tests in testoptimize.cc (test_float); - float LM on badly scaled problems needs my_scaling=true. reg/regsurf, csrmat, CUDA stay double-only. + float LM on badly scaled problems needs my_scaling=true. reg/regsurf, CUDA stay double-only. Tensor Tucker bugs fixed 2026-11-06 (complex: factor = vt.transpose(false); unnamed tensors with inverseorder; bugtests/tensor/tucker.cc). BLAS-based NRVec members are meant for floating types only, not integers. - NRSMat: possible bool member `hermitian` toggling hermitian vs complex symmetric behaviour diff --git a/csrmat.cc b/csrmat.cc index 621f942..1b4fa40 100644 --- a/csrmat.cc +++ b/csrmat.cc @@ -1,6 +1,6 @@ /* LA: linear algebra C++ interface library - Copyright (C) 2008 Jiri Pittner or + Copyright (C) 2008-2026 Jiri Pittner or This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,38 +24,955 @@ #include #include #include "csrmat.h" +#include "nonclass.h" +#include namespace LA { +//raw pointers of possibly empty arrays (NULL when empty) +template static inline const U *s_ptr(const NRVec &x) {return x.size()? (const U *)x : NULL;} +template static inline U *s_wptr(NRVec &x) {return x.size()? (U *)x : NULL;} + +template +CSRMat::CSRMat(const SPMatindex n, const SPMatindex m) +: nn(n), mm(m), symmetric(false), rowptr(n+1) +{ +rowptr.clear(); +} + +template +CSRMat::CSRMat(const SPMatindex n) +: nn(n), mm(n), symmetric(false), rowptr(n+1) +{ +rowptr.clear(); +} + +template +CSRMat::CSRMat(const SPMatindex n, const SPMatindex m, const NRVec &rp, const NRVec &c, const NRVec &x) +: nn(n), mm(m), symmetric(false), rowptr(rp), col(c), v(x) +{ +#ifdef DEBUG +if(rp.size()!=(int)n+1 || rp[0]!=0 || (int)rp[n]!=c.size() || c.size()!=x.size()) laerror("inconsistent arrays in CSRMat constructor"); +for(SPMatindex i=0; i=m || (k>rp[i] && c[k]<=c[k-1])) laerror("column index out of range or columns not ascending in CSRMat constructor"); + } +#endif +} + +template +void CSRMat::copyonwrite(bool detachonly, bool deep) +{ +if(rowptr.size()) rowptr.copyonwrite(detachonly); +if(col.size()) col.copyonwrite(detachonly); +if(v.size()) v.copyonwrite(detachonly,deep); +} + +template +void CSRMat::resize(const SPMatindex n, const SPMatindex m) +{ +nn=n; mm=m; symmetric=false; +col.resize(0); v.resize(0); +if(n==0 && m==0) {rowptr.resize(0); return;} +rowptr.resize(n+1); +rowptr.clear(); +} + +//counting sort, O(nnz+n+m); the columns of the result are ascending in each row also if they were not in the input +template +const CSRMat CSRMat::transpose(bool conj) const +{ +CSRMat r(mm,nn); +r.symmetric=symmetric; +const int nz=v.size(); +if(nz==0) return r; +const SPMatindex *rp=s_ptr(rowptr), *c=s_ptr(col); +const T *x=s_ptr(v); +r.col.resize(nz); r.v.resize(nz); +SPMatindex *rrp=r.rowptr, *rc=r.col; +T *rv=r.v; +for(int k=0; k::conjugate(x[k]) : x[k]; + } +for(SPMatindex j=mm; j>0; --j) rrp[j]=rrp[j-1]; //rrp[j] was advanced to the start of row j+1 +rrp[0]=0; +return r; +} + +//removes small elements; also sums duplicates (adjacent in sorted rows, only during the construction from coordinates) +template +void CSRMat::simplify(const typename LA_traits::normtype eps) +{ +const int nz=v.size(); +if(nz==0) return; +copyonwrite(); +SPMatindex *rp=rowptr, *c=col; +T *x=v; +int p=0, k=0; +for(SPMatindex i=0; ieps) {c[p]=cc; x[p]=s; ++p;} + } + rp[i+1]=p; + } +if(p +void CSRMat::fromcoo(const SPMatindex n, const SPMatindex m, const int nz, const SPMatindex *ri, const SPMatindex *ci, const T *vi) +{ +CSRMat t(m,n); +if(nz) + { + t.col.resize(nz); t.v.resize(nz); + SPMatindex *trp=t.rowptr, *tc=t.col; + T *tv=t.v; + for(int k=0; k=n || ci[k]>=m) laerror("index out of range in CSRMat construction"); +#endif + ++trp[ci[k]+1]; + } + for(SPMatindex j=0; j0; --j) trp[j]=trp[j-1]; + trp[0]=0; + } +*this=t.transpose(); +simplify(); +} + +//duplicates summed; a symmetric SparseMat (one triangle, complex symmetric) is unfolded without conjugation +template +CSRMat::CSRMat(const SparseMat &rhs) +: nn(0), mm(0), symmetric(false) +{ +const bool sym=rhs.issymmetric(); +int nz=0; +for(const matel *l=rhs.getlist(); l; l=l->next) nz+= (sym && l->row!=l->col)? 2:1; +NRVec ri(nz), ci(nz); +NRVec vi(nz); +SPMatindex *r=s_wptr(ri), *c=s_wptr(ci); +T *x=s_wptr(vi); +int p=0; +for(const matel *l=rhs.getlist(); l; l=l->next) + { + r[p]=l->row; c[p]=l->col; x[p++]=l->elem; + if(sym && l->row!=l->col) {r[p]=l->col; c[p]=l->row; x[p++]=l->elem;} + } +fromcoo(rhs.nrows(),rhs.ncols(),nz,r,c,x); +symmetric= sym && !LA_traits::is_complex(); +} + +template +CSRMat::CSRMat(const SparseSMat &rhs) +: nn(rhs.nrows()), mm(rhs.ncols()), symmetric(false), rowptr(nn+1) +{ +const typename LA_traits::normtype eps=LA_precision::normtype>::sparse_epsilon(); +typedef typename std::map::const_iterator IT; +int nz=0; +for(SPMatindex i=0; i *l=rhs.line(i); if(l) for(IT q=l->begin(); q!=l->end(); ++q) if(std::abs(q->second)>eps) ++nz;} +col.resize(nz); v.resize(nz); +SPMatindex *rp=rowptr, *c=s_wptr(col); +T *x=s_wptr(v); +int p=0; +rp[0]=0; +for(SPMatindex i=0; i *l=rhs.line(i); + if(l) for(IT q=l->begin(); q!=l->end(); ++q) if(std::abs(q->second)>eps) {c[p]=q->first; x[p++]=q->second;} + rp[i+1]=p; + } +} + +template +CSRMat::CSRMat(const NRMat &rhs) +: nn(rhs.nrows()), mm(rhs.ncols()), symmetric(false), rowptr(nn+1) +{ +const typename LA_traits::normtype eps=LA_precision::normtype>::sparse_epsilon(); +int nz=0; +for(SPMatindex i=0; ieps) ++nz;} +col.resize(nz); v.resize(nz); +SPMatindex *rp=rowptr, *c=s_wptr(col); +T *x=s_wptr(v); +int p=0; +rp[0]=0; +for(SPMatindex i=0; ieps) {c[p]=j; x[p++]=a[j];} + rp[i+1]=p; + } +} + +//hermitian as NRSMat: the upper triangle is the conjugate of the stored lower one +template +CSRMat::CSRMat(const NRSMat &rhs) +: nn(rhs.nrows()), mm(rhs.ncols()), symmetric(true), rowptr(nn+1) +{ +const typename LA_traits::normtype eps=LA_precision::normtype>::sparse_epsilon(); +int nz=0; +for(SPMatindex i=0; ieps) ++nz; +col.resize(nz); v.resize(nz); +SPMatindex *rp=rowptr, *c=s_wptr(col); +T *x=s_wptr(v); +int p=0; +rp[0]=0; +for(SPMatindex i=0; ieps) {c[p]=j; x[p++]= j>i? LA_traits::conjugate(a) : a;} + } + rp[i+1]=p; + } +} + +template +const T CSRMat::operator()(const SPMatindex i, const SPMatindex j) const +{ +#ifdef DEBUG +if(i>=nn || j>=mm) laerror("index out of range in CSRMat::operator()"); +#endif +if(v.size()==0) return (T)0; +const SPMatindex *rp=rowptr, *c=col; +const T *x=v; +const SPMatindex *q=std::lower_bound(c+rp[i],c+rp[i+1],j); +if(q!=c+rp[i+1] && *q==j) return x[q-c]; +return (T)0; +} + +template +void CSRMat::put(int fd, bool dimensions, bool transp, bool orcaformat) const +{ +if(orcaformat) laerror("ORCA format not supported for CSRMat"); +if(transp) {transpose().put(fd,dimensions,false); return;} +errno=0; +if(dimensions) + { + if(sizeof(SPMatindex)!=write(fd,&nn,sizeof(SPMatindex))) laerror("cannot write() 1 in CSRMat::put"); + if(sizeof(SPMatindex)!=write(fd,&mm,sizeof(SPMatindex))) laerror("cannot write() 2 in CSRMat::put"); + } +int sym=symmetric; +if(sizeof(int)!=write(fd,&sym,sizeof(int))) laerror("cannot write() 3 in CSRMat::put"); +rowptr.put(fd,true); +col.put(fd,true); +v.put(fd,true); +} + +template +void CSRMat::get(int fd, bool dimensions, bool transp, bool orcaformat) +{ +if(orcaformat) laerror("ORCA format not supported for CSRMat"); +errno=0; +SPMatindex dim[2]; +if(dimensions) {if(2*sizeof(SPMatindex)!=read(fd,&dim,2*sizeof(SPMatindex))) laerror("read() error 1 in CSRMat::get");} +else {dim[0]= transp?mm:nn; dim[1]= transp?nn:mm;} +int sym; +if(sizeof(int)!=read(fd,&sym,sizeof(int))) laerror("read() error 2 in CSRMat::get"); +rowptr.get(fd,true); +col.get(fd,true); +v.get(fd,true); +nn=dim[0]; mm=dim[1]; symmetric=sym; +if(rowptr.size()!=(nn||mm ? (int)nn+1 : 0) || col.size()!=v.size()) laerror("inconsistent data in CSRMat::get"); +if(transp) transposeme(); +} + +template +CSRMat & CSRMat::operator*=(const T &a) +{ +if(a==(T)0) {clear(); return *this;} +if(LA_traits::imagpart(a)!=0) symmetric=false; +if(v.size()) v*=a; +return *this; +} + +template +CSRMat & CSRMat::operator=(const T &a) +{ +if(nn!=mm) laerror("assignment of scalar to non-square CSRMat"); +resize(nn,mm); +symmetric= LA_traits::imagpart(a)==0; +if(nn==0 || std::abs(a)<=LA_precision::normtype>::sparse_epsilon()) return *this; +col.resize(nn); v.resize(nn); +SPMatindex *rp=rowptr, *c=col; +T *x=v; +for(SPMatindex i=0; i +CSRMat & CSRMat::operator+=(const T &a) +{ +if(nn!=mm) laerror("addition of scalar to non-square CSRMat"); +if(a==(T)0) return *this; +const SPMatindex *rp=s_ptr(rowptr), *c=s_ptr(col); +bool all= v.size()>0; +if(all) for(SPMatindex i=0; i d(nn); + d=a; + const bool sym=symmetric; + axpy((T)1,d); + symmetric= sym && LA_traits::imagpart(a)==0; + return *this; + } +if(LA_traits::imagpart(a)!=0) symmetric=false; +v.copyonwrite(); +T *x=v; +for(SPMatindex i=0; i +void CSRMat::axpy(const T alpha, const CSRMat &x0, const bool transp) +{ +if((!transp && (nn!=x0.nn || mm!=x0.mm)) || (transp && (nn!=x0.mm || mm!=x0.nn))) laerror("incompatible dimensions for axpy"); +if(alpha==(T)0 || x0.v.size()==0) return; +CSRMat xt; +if(transp) xt=x0.transpose(); +const CSRMat &x= transp? xt : x0; +const bool sym= (v.size()==0? nn==mm : symmetric) && x.symmetric && LA_traits::imagpart(alpha)==0; +if(v.size()==0) + { + rowptr=x.rowptr; col=x.col; v=x.v; + if(alpha!=(T)1) v*=alpha; + symmetric=sym; + return; + } +const SPMatindex *rp=rowptr, *c=col, *xrp=x.rowptr, *xc=x.col; +const int nz=v.size(), xnz=x.v.size(); +if(nz==xnz && ((rp==xrp && c==xc) || (!memcmp(rp,xrp,(nn+1)*sizeof(SPMatindex)) && !memcmp(c,xc,nz*sizeof(SPMatindex))))) + { + v.axpy(alpha,x.v); + symmetric=sym; + return; + } +const T *a=v, *b=x.v; +NRVec nrp(nn+1); +SPMatindex *np=nrp; +np[0]=0; +for(SPMatindex i=0; ixc[q]) ++q; else {++p; ++q;} ++cnt;} + np[i+1]=np[i]+cnt+(rp[i+1]-p)+(xrp[i+1]-q); + } +NRVec ncol(np[nn]); +NRVec nv(np[nn]); +SPMatindex *nc=ncol; +T *y=nv; +int k=0; +for(SPMatindex i=0; ixc[q]) {nc[k]=xc[q]; y[k++]=alpha*b[q++];} + else {nc[k]=c[p]; y[k++]=a[p++]+alpha*b[q++];} + } + } +rowptr=nrp; col=ncol; v=nv; +symmetric=sym; +} + +template +const typename LA_traits::normtype CSRMat::norm(const T scalar) const +{ +if(scalar==(T)0) return v.size()? v.norm() : (typename LA_traits::normtype)0; +if(nn!=mm) laerror("subtraction of scalar from non-square CSRMat in norm()"); +const SPMatindex *rp=s_ptr(rowptr), *c=s_ptr(col); +const T *x=s_ptr(v); +typename LA_traits::normtype sum=0; +for(SPMatindex i=0; i::sqrabs(e); + } + if(!d) sum+=LA_traits::sqrabs(scalar); + } +return std::sqrt(sum); +} + +template +const T CSRMat::dot(const CSRMat &rhs) const +{ +if(nn!=rhs.nn || mm!=rhs.mm) laerror("dot of incompatible CSRMat matrices"); +if(v.size()==0 || rhs.v.size()==0) return (T)0; +const SPMatindex *rp=rowptr, *c=col, *xrp=rhs.rowptr, *xc=rhs.col; +if(rp==xrp && c==xc) return v.dot(rhs.v); +const T *a=v, *b=rhs.v; +T s=0; +for(SPMatindex i=0; ixc[q]) ++q; + else s+=LA_traits::conjugate(a[p++])*b[q++]; + } + } +return s; +} + +template +const T CSRMat::dot(const NRMat &rhs) const +{ +if((int)nn!=rhs.nrows() || (int)mm!=rhs.ncols()) laerror("dot of incompatible matrices"); +if(v.size()==0) return (T)0; +const SPMatindex *rp=rowptr, *c=col; +const T *a=v; +T s=0; +for(SPMatindex i=0; i::conjugate(a[k])*b[c[k]]; + } +return s; +} + +template +const T CSRMat::trace() const +{ +if(v.size()==0) return (T)0; +const SPMatindex *rp=rowptr, *c=col; +const T *x=v; +T s=0; +for(SPMatindex i=0; i +void CSRMat::gemm(const T beta, const CSRMat &a0, const char transa, const CSRMat &b0, const char transb, const T alpha) +{ +const char ta=tolower(transa), tb=tolower(transb); +CSRMat at, bt; +if(ta!='n') at=a0.transpose(ta=='c'); +if(tb!='n') bt=b0.transpose(tb=='c'); +const CSRMat &a= ta!='n'? at : a0; +const CSRMat &b= tb!='n'? bt : b0; +if(a.mm!=b.nn) laerror("incompatible matrices in CSRMat::gemm"); +if(beta!=(T)0 && (nn!=a.nn || mm!=b.mm)) laerror("incompatible result matrix in CSRMat::gemm"); +const SPMatindex n=a.nn, m=b.mm; +CSRMat r(n,m); +if(alpha!=(T)0 && a.v.size() && b.v.size()) + { + const SPMatindex *arp=a.rowptr, *ac=a.col, *brp=b.rowptr, *bc=b.col; + const T *av=a.v, *bv=b.v; + NRVec marker(m); + SPMatindex *mk=marker; + for(SPMatindex j=0; j acc(m); + T *ac2=acc; + for(SPMatindex j=0; j +CSRMat & CSRMat::oplusequal(const CSRMat &rhs) +{ +const bool sym= (v.size()==0? nn==mm : symmetric) && rhs.symmetric; +const SPMatindex n=nn+rhs.nn, m=mm+rhs.mm; +const int nz=v.size(), rnz=rhs.v.size(); +NRVec nrp(n+1), ncol(nz+rnz); +NRVec nv(nz+rnz); +SPMatindex *np=nrp, *nc=s_wptr(ncol); +T *y=s_wptr(nv); +const SPMatindex *rp=s_ptr(rowptr), *c=s_ptr(col), *rrp=s_ptr(rhs.rowptr), *rc=s_ptr(rhs.col); +const T *x=s_ptr(v), *rx=s_ptr(rhs.v); +for(SPMatindex i=0; i<=nn; ++i) np[i]= rp? rp[i] : 0; +for(SPMatindex i=1; i<=rhs.nn; ++i) np[nn+i]=nz+rrp[i]; +for(int k=0; k +const CSRMat CSRMat::otimes(const CSRMat &rhs) const +{ +CSRMat r(nn*rhs.nn,mm*rhs.mm); +r.symmetric= symmetric && rhs.symmetric; +const int nz=v.size(), rnz=rhs.v.size(); +if(nz==0 || rnz==0) return r; +const SPMatindex *rp=rowptr, *c=col, *brp=rhs.rowptr, *bc=rhs.col; +const T *x=v, *bx=rhs.v; +r.col.resize(nz*rnz); r.v.resize(nz*rnz); +SPMatindex *np=r.rowptr, *nc=r.col; +T *y=r.v; +int p=0; +for(SPMatindex i=0; i +void CSRMat::permrows(const SPMatindex *to) +{ +const int nz=v.size(); +NRVec nrp(nn+1); +SPMatindex *np=nrp; +const SPMatindex *rp=rowptr; +np[0]=0; +for(SPMatindex r=0; r ncol(nz); + NRVec nv(nz); + SPMatindex *nc=ncol; + T *y=nv; + const SPMatindex *c=col; + const T *x=v; + for(SPMatindex r=0; r +void CSRMat::permuteme_rows(const NRPerm &p, const bool inverse) +{ +if((SPMatindex)p.size()!=nn) laerror("inconsistent dimension in permuteme_rows"); +NRPerm pinv; +if(inverse) pinv=p.inverse(); +const NRPerm &pp= inverse?pinv:p; +NRVec to(nn); +for(SPMatindex r=0; r +void CSRMat::permuteme_cols(const NRPerm &p, const bool inverse) +{ +if((SPMatindex)p.size()!=mm) laerror("inconsistent dimension in permuteme_cols"); +*this=transpose(); +permuteme_rows(p,inverse); +*this=transpose(); +} + +template +void CSRMat::permuteme_both(const NRPerm &p, const NRPerm &q, const bool inverse) +{ +const bool sym= symmetric && p==q; +permuteme_rows(p,inverse); +permuteme_cols(q,inverse); +symmetric=sym; +} + +template +void CSRMat::permuterows(const NRVec &p) +{ +if((SPMatindex)p.size()!=nn) laerror("inconsistent dimension in permuterows"); +if(nn) permrows(p); +symmetric=false; +} + +template +void CSRMat::permutecolumns(const NRVec &p) +{ +if((SPMatindex)p.size()!=mm) laerror("inconsistent dimension in permutecolumns"); +*this=transpose(); +permuterows(p); +*this=transpose(); +} + +template +void CSRMat::permuteindices(const NRVec &p) +{ +if((SPMatindex)p.size()!=nn || (SPMatindex)p.size()!=mm) laerror("inconsistent dimension in permuteindices"); +const bool sym=symmetric; +permuterows(p); +permutecolumns(p); +symmetric=sym; +} + +//square: diagonal (binary search in each row); non-square: diagonal of A^T A (for preconditioning as SparseMat::diagonalof); cache is ignored +template +const T* CSRMat::diagonalof(NRVec &r, const bool divide, bool cache) const +{ +#ifdef DEBUG +if((int)mm!=r.size()) laerror("incompatible vector size in diagonalof()"); +#endif +NOT_GPU(r); +r.copyonwrite(); +T *rr=r; +const SPMatindex *rp=s_ptr(rowptr), *c=s_ptr(col); +const T *x=s_ptr(v); +if(nn==mm) + { + for(SPMatindex i=0; i d(mm); + d.clear(); + T *dd=d; + for(int k=0; k +void NRVec::gemv(const T beta, const CSRMat &a, const char trans, const T alpha, const NRVec &x) +{ +const char tr=tolower(trans); +const bool tp= tr!='n'; +const bool cj= tr=='c' && LA_traits::is_complex(); +if((tp?a.nrows():a.ncols())!=(SPMatindex)x.size()) laerror("incompatible sizes in gemv"); +#ifdef DEBUG +if((tp?a.ncols():a.nrows())!=(SPMatindex)nn) laerror("incompatible result vector in gemv"); +#endif +NOT_GPU(*this); +NOT_GPU(x); +copyonwrite(); +if(beta==(T)0) clear(); +else if(beta!=(T)1) (*this)*=beta; +if(alpha==(T)0 || a.length()==0) return; +const SPMatindex *rp=a.getrowptr(), *c=a.getcol(); +const T *av=a.getval(); +const T *xv=x.v; +const SPMatindex n=a.nrows(); +if(!tp) + for(SPMatindex i=0; i::conjugate(av[k])*ax; + else for(SPMatindex k=rp[i]; k +NRVec::NRVec(const CSRMat &rhs) +: NRVec(rhs.nrows()>1?rhs.nrows():rhs.ncols()) +{ +if(rhs.nrows()>1 && rhs.ncols()>1) laerror("cannot construct a vector from a sparse matrix with more than one row/column"); +clear(); +const SPMatindex *rp=s_ptr(rhs.getrowptr()), *c=s_ptr(rhs.getcol()); +const T *x=s_ptr(rhs.getval()); +if(!x) return; +if(rhs.nrows()>1) {for(SPMatindex i=0; irp[i]) v[i]=x[rp[i]];} +else for(SPMatindex k=0; k +NRMat::NRMat(const CSRMat &rhs) +{ +nn=rhs.nrows(); +mm=rhs.ncols(); +count=new int(1); +#ifdef CUDALA +location=cpu; +#endif +T *p; +#ifdef MATPTR + v= new T*[nn]; + p=v[0] = new T[mm*nn]; + for (int i=1; i< nn; i++) v[i] = v[i-1] + mm; +#else + p= v = new T[mm*nn]; +#endif +memset(p,0,nn*mm*sizeof(T)); +const SPMatindex *rp=s_ptr(rhs.getrowptr()), *c=s_ptr(rhs.getcol()); +const T *x=s_ptr(rhs.getval()); +if(x) for(int i=0; i +NRMat & NRMat::operator+=(const CSRMat &rhs) +{ +if(nn!=(int)rhs.nrows() || mm!=(int)rhs.ncols()) laerror("incompatible matrices in +="); +NOT_GPU(*this); +copyonwrite(); +const SPMatindex *rp=s_ptr(rhs.getrowptr()), *c=s_ptr(rhs.getcol()); +const T *x=s_ptr(rhs.getval()); +if(x) for(int i=0; i +NRMat & NRMat::operator-=(const CSRMat &rhs) +{ +if(nn!=(int)rhs.nrows() || mm!=(int)rhs.ncols()) laerror("incompatible matrices in -="); +NOT_GPU(*this); +copyonwrite(); +const SPMatindex *rp=s_ptr(rhs.getrowptr()), *c=s_ptr(rhs.getcol()); +const T *x=s_ptr(rhs.getval()); +if(x) for(int i=0; i +void NRMat::gemm(const T &beta, const CSRMat &a, const char transa, const NRMat &b, const char transb, const T &alpha) +{ +const char ta=tolower(transa), tb=tolower(transb); +const bool tpa= ta!='n', cja= ta=='c' && LA_traits::is_complex(); +const int an= tpa? a.ncols() : a.nrows(), ak= tpa? a.nrows() : a.ncols(); +const int bk= tb!='n'? b.ncols() : b.nrows(), bm= tb!='n'? b.nrows() : b.ncols(); +if(ak!=bk) laerror("incompatible matrices in NRMat::gemm with CSRMat"); +#ifdef DEBUG +if(nn!=an || mm!=bm) laerror("incompatible result matrix in NRMat::gemm with CSRMat"); +#endif +NOT_GPU(*this); +copyonwrite(); +if(beta==(T)0) clear(); +else if(beta!=(T)1) (*this)*=beta; +if(alpha==(T)0 || a.length()==0) return; +NRMat bt; +if(tb!='n') bt=b.transpose(tb=='c'); +const NRMat &bb= tb!='n'? bt : b; +const SPMatindex *rp=a.getrowptr(), *c=a.getcol(); +const T *x=a.getval(); +for(SPMatindex i=0; i::conjugate(x[k]) : x[k]); + T *d= (*this)[tpa? c[k] : i]; + const T *e= bb[tpa? i : c[k]]; + for(int j=0; j +const NRMat NRMat::operator*(const CSRMat &rhs) const +{ +if((SPMatindex)mm!=rhs.nrows()) laerror("incompatible matrices in NRMat*CSRMat"); +NOT_GPU(*this); +NRMat r(nn,rhs.ncols()); +r.clear(); +if(rhs.length()==0) return r; +const SPMatindex *rp=rhs.getrowptr(), *c=rhs.getcol(); +const T *x=rhs.getval(); +for(int i=0; i +NRSMat::NRSMat(const CSRMat &rhs) +: nn(rhs.nrows()) +{ +if(rhs.nrows()!=rhs.ncols()) laerror("cannot transform rectangular matrix to NRSMat"); +#ifdef CUDALA +location=cpu; +#endif +count=new int(1); +v=new T[NN2]; +memset(v,0,NN2*sizeof(T)); +const SPMatindex *rp=s_ptr(rhs.getrowptr()), *c=s_ptr(rhs.getcol()); +const T *x=s_ptr(rhs.getval()); +if(x) for(int i=0; i +SparseMat::SparseMat(const CSRMat &rhs) +: nn(rhs.nrows()), mm(rhs.ncols()), symmetric(0), nonzero(0), count(new int(1)), list(NULL), rowsorted(NULL), colsorted(NULL) +{ +for(typename CSRMat::iterator p=rhs.begin(); p.notend(); ++p) add(p->row,p->col,p->elem); +} + +template +SparseSMat::SparseSMat(const CSRMat &rhs) +: nn(rhs.nrows()), mm(rhs.ncols()), count(new int(1)) +{ +v= new std::map * [nn]; +memset(v,0,nn*sizeof(std::map *)); +for(typename CSRMat::iterator p=rhs.begin(); p.notend(); ++p) add(p->row,p->col,p->elem,false); +} +//real and imaginary parts, complex matrices from real ones (nonclass.h): the index arrays stay shared, +//unless elements which became zero have to be removed +template +static const CSRMat s_part(const CSRMat > &a, const NRVec &x, const bool sym) +{ +CSRMat r(a.nrows(),a.ncols()); +if(a.length()) + { + r=CSRMat(a.nrows(),a.ncols(),a.getrowptr(),a.getcol(),x); + const R eps=LA_precision::sparse_epsilon(); + const R *y=x; + for(int k=0; k +static const CSRMat > s_cmplx(const CSRMat &a, const bool imag) +{ +CSRMat > r(a.nrows(),a.ncols()); +if(a.length()) r=CSRMat >(a.nrows(),a.ncols(),a.getrowptr(),a.getcol(),NRVec >(a.getval(),imag)); +if(!imag && a.issymmetric()) r.setsymmetric(); +return r; +} + +template<> +const CSRMat realpart > >(const CSRMat > &a) +{return s_part(a,a.length()? realpart(a.getval()) : NRVec(),a.issymmetric());} + +template<> +const CSRMat realpart > >(const CSRMat > &a) +{return s_part(a,a.length()? realpart(a.getval()) : NRVec(),a.issymmetric());} + +template<> +const CSRMat imagpart > >(const CSRMat > &a) +{return s_part(a,a.length()? imagpart(a.getval()) : NRVec(),false);} + +template<> +const CSRMat imagpart > >(const CSRMat > &a) +{return s_part(a,a.length()? imagpart(a.getval()) : NRVec(),false);} + +template<> +const CSRMat > realmatrix >(const CSRMat &a) +{return s_cmplx(a,false);} + +template<> +const CSRMat > realmatrix >(const CSRMat &a) +{return s_cmplx(a,false);} + +template<> +const CSRMat > imagmatrix >(const CSRMat &a) +{return s_cmplx(a,true);} + +template<> +const CSRMat > imagmatrix >(const CSRMat &a) +{return s_cmplx(a,true);} + +//re + i im: a BLAS axpy of the values if both have the same structure, otherwise a merge +template<> +const CSRMat > complexmatrix >(const CSRMat &re, const CSRMat &im) +{CSRMat > r=s_cmplx(re,false); r.axpy(std::complex(0,1),s_cmplx(im,false)); return r;} + +template<> +const CSRMat > complexmatrix >(const CSRMat &re, const CSRMat &im) +{CSRMat > r=s_cmplx(re,false); r.axpy(std::complex(0,1),s_cmplx(im,false)); return r;} -/* - Commented out by Roman for ICC #define INSTANTIZE(T) \ -template void CSRMat::gemm(const T beta, const CSRMat &a, const char transa, const CSRMat &b, const char transb, const T alpha); \ -template CSRMat & CSRMat::operator*=(const T &a); \ -template void CSRMat::gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x) const; \ -template void CSRMat::axpy(const T alpha, const CSRMat &x, const bool transp); \ -template CSRMat & CSRMat::operator=(const T &a); \ -template CSRMat & CSRMat::operator+=(const T &a); \ -template CSRMat & CSRMat::operator-=(const T &a); \ -template LA_traits::normtype CSRMat::norm(const T scalar) const; \ -template const T* CSRMat::diagonalof(NRVec &r, const bool divide, bool cache) const; \ -template void CSRMat::get(int fd, bool dimen, bool transp); \ -template void CSRMat::put(int fd, bool dimen, bool transp) const; \ - +template class CSRMat; \ +template NRMat::NRMat(const CSRMat &rhs); \ +template NRSMat::NRSMat(const CSRMat &rhs); \ +template SparseMat::SparseMat(const CSRMat &rhs); \ +template SparseSMat::SparseSMat(const CSRMat &rhs); \ +template void NRVec::gemv(const T beta, const CSRMat &a, const char trans, const T alpha, const NRVec &x); \ +template NRVec::NRVec(const CSRMat &rhs); \ +template NRMat & NRMat::operator+=(const CSRMat &rhs); \ +template NRMat & NRMat::operator-=(const CSRMat &rhs); \ +template void NRMat::gemm(const T &beta, const CSRMat &a, const char transa, const NRMat &b, const char transb, const T &alpha); \ +template const NRMat NRMat::operator*(const CSRMat &rhs) const; \ INSTANTIZE(double) -INSTANTIZE(std::complex) -*/ - -//// forced instantization of functions in the header in the corresponding object file -//@@@@@template class CSRMat; -//@@@@template class CSRMat >; -//@@@@ unfinished class commented out - - +INSTANTIZE(float) +INSTANTIZE(std::complex) +INSTANTIZE(std::complex) }//namespace diff --git a/csrmat.h b/csrmat.h index d715018..681f446 100644 --- a/csrmat.h +++ b/csrmat.h @@ -1,6 +1,6 @@ /* LA: linear algebra C++ interface library - Copyright (C) 2010 Jiri Pittner or + Copyright (C) 2010-2026 Jiri Pittner or This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,16 +32,13 @@ #include "vec.h" #include "mat.h" #include "smat.h" -#include "qsort.h" - -#ifdef CUDALA -#include "cusparse.h" -#include "cuda_runtime.h" -#endif namespace LA { //compressed row format read-efficient matrix storage +//row i has the elements v[k], column col[k], k=rowptr[i]..rowptr[i+1]-1, columns strictly ascending within a row +//the full matrix is always stored; symmetric only declares A = A^dagger (hermitian, real symmetric) e.g. for davidson, lanczos +//rowptr, col and v share data independently (copy-on-write), e.g. a scaled copy shares the structure with the original template class CSRMat @@ -49,92 +46,157 @@ class CSRMat protected: SPMatindex nn; SPMatindex mm; - SPMatindex nnz; - int *count; - SPMatindex *rowptr; - SPMatindex *col; - T *v; -#ifdef CUDALA - GPUID location; - cusparseMatDescr_t descr; -#endif - + bool symmetric; + NRVec rowptr; + NRVec col; + NRVec v; + void fromcoo(const SPMatindex n, const SPMatindex m, const int nz, const SPMatindex *ri, const SPMatindex *ci, const T *vi); //sorts, sums duplicates, drops small elements + void permrows(const SPMatindex *to); //row r becomes row to[r] public: - CSRMat() : nn(0), mm(0), nnz(0), count(NULL), rowptr(NULL), col(NULL), v(NULL) {}; - CSRMat(const CSRMat &rhs); - explicit CSRMat(const SPMatindex n, const SPMatindex m); - explicit CSRMat(const SPMatindex n); + //iterator over the stored elements in row-major order + class iterator { + private: + const CSRMat *a; + SPMatindex row; + int k; + matel my; + void set() {while(rownn && k>=(int)a->rowptr[row+1]) ++row; if(rownn) {my.row=row; my.col=a->col[k]; my.elem=a->v[k]; my.next=NULL;}} + public: + iterator(): a(NULL), row(0), k(0) {}; + iterator(const CSRMat &rhs): a(&rhs), row(0), k(0) {if(a->nn) set();} + iterator & operator++() {++k; set(); return *this;} + bool notend() const {return a && rownn;} + bool operator==(const iterator &rhs) const {return notend()==rhs.notend() && (!notend() || k==rhs.k);} + bool operator!=(const iterator &rhs) const {return !(*this==rhs);} + const matel & operator*() const {return my;} + const matel * operator->() const {return &my;} + }; + iterator begin() const {return iterator(*this);} + iterator end() const {return iterator();} + + CSRMat() : nn(0), mm(0), symmetric(false) {}; + explicit CSRMat(const SPMatindex n, const SPMatindex m); //zero matrix + explicit CSRMat(const SPMatindex n); //zero square matrix + CSRMat(const SPMatindex n, const SPMatindex m, const NRVec &rowptr, const NRVec &col, const NRVec &v); //shares the arrays explicit CSRMat(const SparseMat &rhs); explicit CSRMat(const SparseSMat &rhs); explicit CSRMat(const NRSMat &rhs); explicit CSRMat(const NRMat &rhs); - CSRMat & operator=(const CSRMat &rhs); - void copyonwrite(); - void resize(const SPMatindex nn, const SPMatindex mm); + void copyonwrite(bool detachonly=false, bool deep=true); + void resize(const SPMatindex n, const SPMatindex m); //zero matrix void dealloc(void) {resize(0,0);} - void moveto(GPUID destination); - void clear(); - ~CSRMat(); - inline int getcount() const {return count?*count:0;} - inline const CSRMat operator*(const T &rhs) const {return CSRMat(*this) *= rhs;} + void clear() {bool sym=symmetric; resize(nn,mm); symmetric=sym;} //zero matrix of the same dimensions and symmetry + void swap(CSRMat &rhs) + { + SPMatindex tmpnn=nn; nn=rhs.nn; rhs.nn=tmpnn; + SPMatindex tmpmm=mm; mm=rhs.mm; rhs.mm=tmpmm; + bool tmpsymmetric=symmetric; symmetric=rhs.symmetric; rhs.symmetric=tmpsymmetric; + rowptr.swap(rhs.rowptr); col.swap(rhs.col); v.swap(rhs.v); + } + inline SPMatindex nrows() const {return nn;} + inline SPMatindex ncols() const {return mm;} + inline int length() const {return v.size();} //number of stored elements + inline bool issymmetric() const {return symmetric;} + inline void setsymmetric() {if(nn!=mm) laerror("non-square cannot be symmetric"); symmetric=true;} //only declares the matrix hermitian + inline void setunsymmetric() {symmetric=false;} + inline const NRVec & getrowptr() const {return rowptr;} + inline const NRVec & getcol() const {return col;} + inline const NRVec & getval() const {return v;} + const T operator()(const SPMatindex i, const SPMatindex j) const; //element (zero if not stored), binary search in the row + void gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x) const {r.gemv(beta,*this,trans,alpha,x);}; + inline const NRVec operator*(const NRVec &rhs) const {NRVec result(nn); result.gemv((T)0,*this,'n',(T)1,rhs); return result;}; + const T* diagonalof(NRVec &r, const bool divide=0, bool cache=false) const; //diagonal (diagonal of A^T A for non-square, as SparseMat), or divide r by it + const CSRMat transpose(bool conj=false) const; + CSRMat & operator*=(const T &a); //multiply by a scalar (the structure stays shared) + CSRMat & operator=(const T &a); //a times the unit matrix + CSRMat & operator+=(const T &a); //add a to the diagonal + CSRMat & operator-=(const T &a) {return *this += -a;} + void identity() {*this = (T)1;} + void axpy(const T alpha, const CSRMat &x, const bool transp=false); //this += alpha x (alpha x^T); elements cancelled to zero stay stored, see simplify() + CSRMat & operator+=(const CSRMat &rhs) {axpy((T)1,rhs); return *this;} + CSRMat & operator-=(const CSRMat &rhs) {axpy((T)-1,rhs); return *this;} + inline const CSRMat operator*(const T &rhs) const {return CSRMat(*this) *= rhs;} inline const CSRMat operator+(const T &rhs) const {return CSRMat(*this) += rhs;} - inline const CSRMat operator-(const T &rhs) const {return CSRMat(*this) -= rhs;} - inline const CSRMat operator+(const CSRMat &rhs) const {return CSRMat(*this) += rhs;} - inline const CSRMat operator-(const CSRMat &rhs) const {return CSRMat(*this) -= rhs;} - CSRMat & operator*=(const T &a); //multiply by a scalar - CSRMat & operator=(const T &a); //assign a to diagonal - CSRMat & operator+=(const T &a); //assign a to diagonal - CSRMat & operator-=(const T &a); //assign a to diagonal - void axpy(const T alpha, const CSRMat &x, const bool transp=0); // this+= a*x - inline CSRMat & operator+=(const CSRMat &rhs) {axpy((T)1,rhs); return *this;}; - inline CSRMat & operator-=(const CSRMat &rhs) {axpy((T)-1,rhs); return *this;}; - const T* diagonalof(NRVec &, const bool divide=0, bool cache=false) const; //get diagonal - void gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x) const; - inline const NRVec operator*(const NRVec &rhs) const {NRVec result(nn); this->gemv((T)0,result,'n',(T)1,rhs); return result;}; - typename LA_traits::normtype norm(const T scalar=(T)0) const; - inline const CSRMat operator*(const CSRMat &rhs) const {CSRMat r(nn,mm); r.gemm(0,*this,'n',rhs,'n',1); return r;}; - void gemm(const T beta, const CSRMat &a, const char transa, const CSRMat &b, const char transb, const T alpha); //this := alpha*op( A )*op( B ) + beta*this - inline SPMatindex length() {return nnz;}; - void transposeme() const {laerror("in-place transposition not implemented for CSRMat");}; - CSRMat transpose(bool conj=false) const {laerror("CSR transpose not implemented");} - void get(int fd, bool dimen, bool transp); - void put(int fd, bool dimen, bool transp) const; - int nrows() const {return nn;} - int ncols() const {return mm;} - - /*class iterator {//not efficient, just for output to ostreams - private: - matel *p; - matel my; - SPMatindex mynn; - SPMatindex mymm; - - - public: - //compiler-generated iterator & operator=(const iterator &rhs); - //compiler-generated iterator(const iterator &rhs); - iterator() - iterator(const CSRMat &rhs) - iterator operator++() - iterator(matel *q) {p=q; col=NULL;}//just for end() - //compiler-generated ~iterator() {}; - bool operator!=(const iterator &rhs) const {return p!=rhs.p;} //only for comparison with end() - bool operator==(const iterator &rhs) const {return p==rhs.p;} //only for comparison with end() - matel & operator*() const {return *p;} - matel * operator->() const {return p;} - bool notend() const {return (bool)p;}; - }; - iterator begin() const {return iterator(*this);} - iterator end() const {return iterator(NULL);} - */ + inline const CSRMat operator-(const T &rhs) const {return CSRMat(*this) -= rhs;} + inline const CSRMat operator+(const CSRMat &rhs) const {return CSRMat(*this) += rhs;} + inline const CSRMat operator-(const CSRMat &rhs) const {return CSRMat(*this) -= rhs;} + const typename LA_traits::normtype norm(const T scalar=(T)0) const; //Frobenius norm of A - scalar I + const T dot(const CSRMat &rhs) const; //\sum_{ij} conj(a_ij) b_ij as NRMat::dot + const T dot(const NRMat &rhs) const; + const T trace() const; + void simplify(const typename LA_traits::normtype sparseepsilon=LA_precision::normtype>::sparse_epsilon()); //removes elements with |x| <= sparseepsilon + inline const NRMat operator*(const NRMat &rhs) const {NRMat r(nn,rhs.ncols()); r.gemm((T)0,*this,'n',rhs,'n',(T)1); return r;}; + void gemm(const T beta, const CSRMat &a, const char transa, const CSRMat &b, const char transb, const T alpha); //this = alpha op(A) op(B) + beta this, sparse result + inline const CSRMat operator*(const CSRMat &rhs) const {CSRMat r; r.gemm((T)0,*this,'n',rhs,'n',(T)1); return r;}; + CSRMat & oplusequal(const CSRMat &rhs); //direct sum + CSRMat & oplusequal(const NRMat &rhs) {return oplusequal(CSRMat(rhs));} + CSRMat & oplusequal(const NRSMat &rhs) {return oplusequal(CSRMat(rhs));} + const CSRMat oplus(const CSRMat &rhs) const {return CSRMat(*this).oplusequal(rhs);} + const CSRMat oplus(const NRMat &rhs) const {return CSRMat(*this).oplusequal(rhs);} + const CSRMat oplus(const NRSMat &rhs) const {return CSRMat(*this).oplusequal(rhs);} + const CSRMat otimes(const CSRMat &rhs) const; //direct product, (A x B)_{i nB+k, j mB+l} = a_ij b_kl as NRMat::operator| + const CSRMat otimes(const NRMat &rhs) const {return otimes(CSRMat(rhs));} + const CSRMat otimes(const NRSMat &rhs) const {return otimes(CSRMat(rhs));} + //permutations: element of row r goes to row p(r) (with inverse p^{-1}); matrix indices from 0, permutation from 1; the full matrix is stored, so rows means only rows + void permuteme_rows(const NRPerm &p, const bool inverse=false); + void permuteme_cols(const NRPerm &p, const bool inverse=false); + void permuteme_both(const NRPerm &p, const NRPerm &q, const bool inverse=false); + const CSRMat permuted_rows(const NRPerm &p, const bool inverse=false) const {CSRMat a(*this); a.permuteme_rows(p,inverse); return a;}; + const CSRMat permuted_cols(const NRPerm &p, const bool inverse=false) const {CSRMat a(*this); a.permuteme_cols(p,inverse); return a;}; + const CSRMat permuted_both(const NRPerm &p, const NRPerm &q, const bool inverse=false) const {CSRMat a(*this); a.permuteme_both(p,q,inverse); return a;}; + void permuterows(const NRVec &p); //as SparseMat: row r goes to row p[r], indices from 0 + void permutecolumns(const NRVec &p); + void permuteindices(const NRVec &p); //rows and columns + void transposeme(bool conj=false) {*this=transpose(conj);} + void get(int fd, bool dimensions=1, bool transp=0, bool orcaformat=false); + void put(int fd, bool dimensions=1, bool transp=0, bool orcaformat=false) const; }; +template +inline const CSRMat commutator(const CSRMat &x, const CSRMat &y, const bool trx=0, const bool tryy=0) +{ +CSRMat r; +r.gemm((T)0,x,trx?'t':'n',y,tryy?'t':'n',(T)1); +r.gemm((T)1,y,tryy?'t':'n',x,trx?'t':'n',(T)-1); +return r; +} + +template +inline const CSRMat anticommutator(const CSRMat &x, const CSRMat &y, const bool trx=0, const bool tryy=0) +{ +CSRMat r; +r.gemm((T)0,x,trx?'t':'n',y,tryy?'t':'n',(T)1); +r.gemm((T)1,y,tryy?'t':'n',x,trx?'t':'n',(T)1); +return r; +} + template -std::ostream & operator<<(std::ostream &s, const CSRMat &x); +inline const NRVec NRVec::operator*(const CSRMat &mat) const +{ +NOT_GPU(*this); +NRVec result(mat.ncols()); +result.gemv((T)0,mat,'t',(T)1,*this); +return result; +} template -std::istream& operator>>(std::istream &s, CSRMat &x); +std::ostream& operator<<(std::ostream &s, const CSRMat &x) //the same text format as SparseMat + { + s << (int)x.nrows() << ' ' << (int)x.ncols() << '\n'; + for(typename CSRMat::iterator p=x.begin(); p.notend(); ++p) + s << (int)p->row << ' ' << (int)p->col << ' ' << (typename LA_traits_io::IOtype)p->elem << '\n'; + s << "-1 -1\n"; + return s; + } +template +std::istream& operator>>(std::istream &s, CSRMat &x) + { + SparseMat a; + s >> a; + x=CSRMat(a); + return s; + } }//namespace #endif //_CSRMAT_H_ diff --git a/doc/bib/sparse.tex b/doc/bib/sparse.tex new file mode 100644 index 0000000..8d42011 --- /dev/null +++ b/doc/bib/sparse.tex @@ -0,0 +1,2 @@ +%bibliography items of the chapter Sparse matrices (sparse.tex), cited in the comments of csrmat.cc +\bibitem{sparse:gustavson1978} F. G. Gustavson, ACM Trans. Math. Softw. \textbf{4}, 250 (1978). diff --git a/doc/concepts.tex b/doc/concepts.tex index 38a7e04..65fd616 100644 --- a/doc/concepts.tex +++ b/doc/concepts.tex @@ -8,7 +8,7 @@ it and describe only what is specific to the class. \subsection{Shared data} An object of \code{NRVec}, \code{NRMat}, \code{NRSMat} and of all other classes holding large data -(\code{SparseMat}, \code{SparseSMat}, \code{Tensor}, \code{fourindex}, \code{Polynomial}, the permutation +(\code{SparseMat}, \code{SparseSMat}, \code{CSRMat}, \code{Tensor}, \code{fourindex}, \code{Polynomial}, the permutation classes, \code{bitvector}, \dots) consists of a small header (dimensions, a pointer to the data and a pointer to a reference counter) and the data block. The copy constructor and the assignment operator between objects of the same type copy only the header and increment the reference counter, so that both objects share the @@ -181,7 +181,7 @@ member & double & float & used by \\ The class templates are explicitly instantiated in the library for \code{double}, \code{std::complex} and the integer types \code{char}, \code{short}, \code{int}, \code{long}, \code{long long} and their unsigned variants; \code{NRVec}, \code{NRMat}, \code{NRSMat}, \code{SparseMat}, \code{SparseSMat}, \code{Tensor}, \code{fourindex} (with \code{unsigned char} indices) and \code{Polynomial} also for \code{float} and -\code{std::complex} (single precision support, 2026, in progress for the other classes). The BLAS and LAPACK backed +\code{std::complex} (single precision support, 2026, in progress for the other classes); \code{CSRMat} only for the four floating point types. The BLAS and LAPACK backed functions are implemented for \code{double} and \code{std::complex} (some also for mixed real--complex arguments) and for their single precision counterparts, which always use the single precision BLAS/LAPACK routines (\code{s}/\code{c}); calling them for other types results in a run-time \code{laerror} (``not implemented for this type'') or a link error. Functions implemented in the headers work diff --git a/doc/introduction.tex b/doc/introduction.tex index 2b254a7..48d41cd 100644 --- a/doc/introduction.tex +++ b/doc/introduction.tex @@ -48,7 +48,7 @@ the Numerical Recipes library. They can be used with it, but are completely inde \header{mat.h} & \code{NRMat}, \code{NRMat_from1} (chapter~\ref{ch:mat}) \\ \header{smat.h} & \code{NRSMat}, \code{NRSMat_from1} (chapter~\ref{ch:smat}) \\ \header{sparsemat.h}, \header{sparsesmat.h} & sparse matrices \code{SparseMat}, \code{SparseSMat} (chapter~\ref{ch:sparse}) \\ -\header{csrmat.h} & compressed row storage sparse matrix (unfinished) \\ +\header{csrmat.h} & compressed row storage sparse matrix \code{CSRMat} (chapter~\ref{ch:sparse}) \\ \header{nonclass.h}, \header{matexp.h} & linear systems, eigenvalue problems, SVD, matrix functions (chapter~\ref{ch:nonclass}) \\ \header{davidson.h}, \header{lanczos.h}, \header{conjgrad.h}, \header{gmres.h}, \header{diis.h}, \header{bisection.h} & iterative methods, DIIS convergence acceleration (chapter~\ref{ch:iterative}) \\ \header{optimize.h} & numerical optimization (chapter~\ref{ch:optimize}) \\ diff --git a/doc/mat.tex b/doc/mat.tex index 6b16b89..49289fb 100644 --- a/doc/mat.tex +++ b/doc/mat.tex @@ -111,7 +111,7 @@ its condition of use is that the vector outlives the matrix, see Pitfalls. The c \lafunc{explicit NRMat(const WeightPermutation &p, const bool direction)}{Weight times permutation matrix.} \lafunc{explicit NRMat(const PermutationAlgebra &p, const bool direction, const int nforce = 0)}{Sum of weighted permutation matrices of the elements of a permutation algebra; size taken from the first element unless \code{nforce>0}. (The header remarks that the permutation-matrix representation of $S_n$ is reducible to only the irreps $[n]$ and $[n-1,1]$, so e.g.\ Young projectors cannot be represented this way.) See chapter~\ref{ch:permutation}.} \lafunc{explicit NRMat(const SparseMat &rhs); explicit NRMat(const SparseSMat &rhs); explicit NRMat(const SemiSparseMat &rhs)}{Dense matrix from sparse ones (chapter~\ref{ch:sparse}).} -\lafunc{explicit NRMat(const CSRMat &rhs)}{Not supported: \code{CSRMat} is unfinished, the declaration is kept for possible future work (no definition, a link error if used).} +\lafunc{explicit NRMat(const CSRMat &rhs)}{Dense matrix from a compressed row sparse one (section~\ref{sec:sparse:csr}).} \lafunc{~NRMat()}{Decrements the reference count, frees the data when it reaches zero.} \lafunc{NRMat & operator=(const NRMat &rhs)}{Shallow assignment (sec.~\ref{sec:concepts:assign}).} \lafunc{NRMat & operator|=(const NRMat &rhs)}{Deep copy.} @@ -153,6 +153,7 @@ Note that scalar addition and subtraction act on the \emph{diagonal} only, i.e.\ \lafunc{void gemm(const T &beta, const SparseMat &a, const char transa, const NRMat &b, const char transb, const T &alpha)}{The same with a sparse first factor; \code{double} and \code{float} only (the complex versions call \code{laerror("not implemented yet")}), see chapter~\ref{ch:sparse}.} \lafunc{const NRMat operator*(const NRSMat &rhs) const}{$AS$ with packed symmetric (real) or hermitian (complex) $S$ (BLAS \code{dspmv}/\code{zhpmv} per row).} \lafunc{SparseSMat operator*(const SparseSMat &rhs) const}{Dense times sparse, the result is sparse (chapter~\ref{ch:sparse}).} +\lafunc{void gemm(const T &beta, const CSRMat &a, const char transa, const NRMat &b, const char transb, const T &alpha); const NRMat operator*(const CSRMat &rhs) const}{$C\leftarrow\beta C+\alpha\,\mathrm{op}(A)\,\mathrm{op}(B)$ with a compressed row sparse $A$ (all types, \code{trans} \code{'n'}, \code{'t'}, \code{'c'}), and dense times compressed row sparse with a dense result (section~\ref{sec:sparse:csr}).} \lafunc{const NRVec operator*(const NRVec &rhs) const; const NRVec > operator*(const NRVec > &rhs) const}{Matrix--vector product via \code{NRVec::gemv} (the second form: real matrix times complex vector).} \lafunc{void gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x) const; void gemv(const T beta, NRVec > &r, const char trans, const T alpha, const NRVec > &x) const}{$r\leftarrow\alpha\,\mathrm{op}(A)x+\beta r$; forwards to \code{r.gemv(beta,*this,trans,alpha,x)}, see chapter~\ref{ch:vec}.} \lafunc{const T dot(const NRMat &rhs) const}{$\sum_{ij}A_{ij}^*B_{ij}$ (Frobenius inner product, conjugating \code{*this}).} @@ -238,6 +239,7 @@ See section~\ref{sec:concepts:io} for the general conventions. Besides the constructors, \code{operator*(const SparseSMat&)} and \code{gemm} with a \code{SparseMat} factor listed above, \code{NRMat} declares \begin{lareference} +\lafunc{NRMat & operator+=(const CSRMat &rhs); NRMat & operator-=(const CSRMat &rhs)}{Add/subtract a compressed row sparse matrix (section~\ref{sec:sparse:csr}).} \lafunc{NRMat & operator+=(const SparseMat &rhs); NRMat & operator-=(const SparseMat &rhs)}{Add/subtract the sparse matrix (defined in \code{sparsemat.h}; a symmetric \code{SparseMat} is unfolded, without conjugation).} \end{lareference} diff --git a/doc/nonclass.tex b/doc/nonclass.tex index a3fedd8..a7f03b2 100644 --- a/doc/nonclass.tex +++ b/doc/nonclass.tex @@ -297,10 +297,10 @@ infinity for a singular matrix. It is LAPACK's estimate, a lower bound that is u \section{Real and imaginary parts}\label{sec:nonclass:complex} \begin{lareference} \lafunc{const LA_traits::realtype realpart(const T &a); const LA_traits::realtype imagpart(const T &a)}{Real or imaginary part as a real object; -specialized for \code{NRVec}, \code{NRMat}, \code{NRSMat} and \code{SparseMat} over \code{complex} and \code{complex}.} +specialized for \code{NRVec}, \code{NRMat}, \code{NRSMat}, \code{SparseMat} and \code{CSRMat} over \code{complex} and \code{complex}.} \lafunc{const LA_traits::complextype realmatrix(const T &a); const LA_traits::complextype imagmatrix(const T &a)}{Complex object $A$ resp.\ $\mathrm{i}A$ from a real one; -for \code{NRMat} and \code{SparseSMat} of \code{double} and \code{float}.} -\lafunc{const LA_traits::complextype complexmatrix(const T &re, const T &im)}{Complex object $\mathrm{re}+\mathrm{i}\,\mathrm{im}$; for \code{NRMat} and \code{SparseSMat} of \code{double} and \code{float}.} +for \code{NRMat}, \code{SparseSMat} and \code{CSRMat} of \code{double} and \code{float}.} +\lafunc{const LA_traits::complextype complexmatrix(const T &re, const T &im)}{Complex object $\mathrm{re}+\mathrm{i}\,\mathrm{im}$; for \code{NRMat}, \code{SparseSMat} and \code{CSRMat} of \code{double} and \code{float}.} \end{lareference} For the same purpose the classes provide \code{complexify()} (chapters~\ref{ch:vec}, \ref{ch:mat}). The real and imaginary part functions are CPU only (sec.~\ref{sec:concepts:gpu}). diff --git a/doc/smat.tex b/doc/smat.tex index f0b873c..556e9d2 100644 --- a/doc/smat.tex +++ b/doc/smat.tex @@ -107,7 +107,7 @@ are CPU only (\code{NOT_GPU}). \lafunc{NRSMat(const typename LA_traits_complex::NRSMat_Noncomplex_type &rhs, bool imagpart = false)}{Complex matrix from a real one: \code{rhs} becomes the real part (\code{imagpart=false}) or the imaginary part (\code{imagpart=true}), the other part is zero. Defined only for \code{T=std::complex} and \code{std::complex}.} \lafunc{explicit NRSMat(const NRMat &rhs)}{Symmetric part $(A+A\Tr)/2$ of a square dense matrix (deep copy; nonsquare input is checked only in DEBUG).} \lafunc{explicit NRSMat(const NRVec &rhs, const int n)}{Interprets a vector of length $n(n+1)/2$ as packed matrix of order $n$. \emph{Shares} the data with \code{rhs} (no copy); length checked only in DEBUG.} -\lafunc{explicit NRSMat(const SparseMat &rhs); explicit NRSMat(const SparseSMat &rhs)}{Dense from sparse, see \ref{ch:sparse}.} +\lafunc{explicit NRSMat(const SparseMat &rhs); explicit NRSMat(const SparseSMat &rhs); explicit NRSMat(const CSRMat &rhs)}{Dense from sparse, see \ref{ch:sparse}; from \code{SparseSMat} and \code{CSRMat} the lower triangle is taken.} \lafunc{~NRSMat()}{Destructor, frees the data when the last reference disappears.} \lafunc{NRSMat & operator=(const NRSMat &rhs)}{Shallow copy, see section~\ref{sec:concepts:assign}.} \lafunc{NRSMat & operator|=(const NRSMat &rhs)}{Deep copy.} diff --git a/doc/sparse.tex b/doc/sparse.tex index e184ecf..ef1d218 100644 --- a/doc/sparse.tex +++ b/doc/sparse.tex @@ -1,8 +1,8 @@ \chapter{Sparse matrices}\label{ch:sparse} -\laheader{sparsemat.h, sparsemat.cc, sparsesmat.h, sparsesmat.cc} +\laheader{sparsemat.h, sparsemat.cc, sparsesmat.h, sparsesmat.cc, csrmat.h, csrmat.cc} \section{Overview}\label{sec:sparse:overview} -\LA{} offers two independent sparse matrix classes with different storage schemes, plus a small helper class: +\LA{} offers three independent sparse matrix classes with different storage schemes, plus a small helper class: \begin{itemize} \item \code{SparseMat} (section~\ref{sec:sparse:sparsemat}): an \emph{unordered linked list} of (row, column, value) triplets. Appending elements is cheap, duplicates are allowed and summed, @@ -15,10 +15,12 @@ exponentiating symmetric matrices (e.g.\ the unitary complex symmetric $\exp(\mathrm{i}H)$ with real symmetric $H$), but despite its name it is a general (also nonsymmetric and rectangular) sparse matrix; symmetry (hermiticity for complex types) is only a convention of some of its operations. +\item \code{CSRMat} (section~\ref{sec:sparse:csr}): the compressed sparse row format (row pointers, column + indices, values), read-efficient for products with vectors and matrices, e.g.\ in iterative solvers; built by + conversion from the other classes. \item \code{SemiSparseMat} (section~\ref{sec:sparse:semisparse}): a dense diagonal plus a \code{SparseMat} off-diagonal part, just enough to be used in iterative eigensolvers. \end{itemize} -\code{CSRMat} (section~\ref{sec:sparse:csr}) is unfinished. Common definitions in \code{sparsemat.h}: \begin{lareference} @@ -88,6 +90,7 @@ Most modifying members call \code{copyonwrite()}: \code{simplify}, \code{operato \lafunc{SparseMat(const SparseMat &rhs)}{Shallow copy; an empty matrix gets its own reference count.} \lafunc{explicit SparseMat(const NRMat &rhs)}{From a dense matrix, elements with $|a_{ij}|>$ $\epsilon_{\rm sp}$.} \lafunc{explicit SparseMat(const NRSMat &rhs)}{From a packed symmetric matrix; symmetric flag set, lower triangle stored.} +\lafunc{explicit SparseMat(const CSRMat &rhs)}{From a \code{CSRMat} (section~\ref{sec:sparse:csr}): all stored elements, not symmetric.} \lafunc{~SparseMat()}{Destructor.} \lafunc{SparseMat & operator=(const SparseMat &rhs)}{Shallow copy (also copies the symmetric flag).} \lafunc{SparseMat & operator=(const T &a)}{Scalar matrix $a\cdot1$; the matrix must be square and dimensioned, old contents are discarded.} @@ -248,7 +251,7 @@ copies). \lafunc{explicit SparseSMat(const NRSMat &rhs)}{From a packed symmetric (complex: hermitian) matrix (both triangles inserted, the upper one conjugated, $|a_{ij}|>$ $\epsilon_{\rm sp}$).} \lafunc{explicit SparseSMat(const NRMat &rhs)}{From a dense $n\times m$ matrix, nonzero elements copied as they are (no symmetrization).} \lafunc{explicit SparseSMat(const SparseMat &rhs)}{From a \code{SparseMat} (same dimensions); duplicates are summed, a symmetric \code{SparseMat} is unfolded without conjugation (complex symmetric, as \code{SparseMat} defines it).} -\lafunc{explicit SparseSMat(const CSRMat &rhs)}{Declared but not implemented (\code{CSRMat} is not supported).} +\lafunc{explicit SparseSMat(const CSRMat &rhs)}{From a \code{CSRMat} (section~\ref{sec:sparse:csr}): all stored elements, without mirroring.} \lafunc{~SparseSMat()}{Destructor.} \lafunc{SparseSMat & operator=(const SparseSMat &rhs)}{Shallow copy.} \lafunc{SparseSMat & operator=(const T &a)}{Scalar matrix $a\cdot1$ (diagonal elements are created even for $a=0$).} @@ -310,11 +313,100 @@ In \ref{ch:nonclass}: \code{complexmatrix()}, \code{realmatrix()} and \code{imag \code{SparseSMat}. \section{CSRMat}\label{sec:sparse:csr} -\laheader{csrmat.h} -\code{CSRMat} is meant to hold a matrix in compressed sparse row format (row pointers, column indices, -values, optionally on the GPU via cuSPARSE) for fast products. The class is unfinished: only the declarations -exist, \code{csrmat.cc} contains no implementation and no instantiation. It is \emph{not supported} and must not be used; -the declarations (also \code{NRMat(const CSRMat&)} and \code{SparseSMat(const CSRMat&)}) are kept for possible future work. +\laheader{csrmat.h, csrmat.cc} +\code{CSRMat} stores a matrix in the compressed sparse row format: the elements of row $i$ are \code{v[k]} in +column \code{col[k]} for $k=$ \code{rowptr[i]}$,\ldots,$\code{rowptr[i+1]}$-1$, with the columns strictly ascending within +each row (an invariant of the class). The format is read-efficient: $Ax$ is a row-wise dot product without any +indirection in the result, which makes it the fastest of the sparse classes in iterative solvers (\ref{ch:iterative}) +and matrix functions with a fixed sparsity pattern. It cannot be built element by element; a matrix is assembled as a +\code{SparseMat} (\code{add}) or a \code{SparseSMat} (or as three arrays) and converted, in $O(n_{\rm nz}+n+m)$. + +The full matrix is always stored, also for symmetric and hermitian matrices. The flag \code{symmetric} only +\emph{declares} the matrix hermitian, $A=A^\dagger$ (real symmetric for real types); it is not checked and never changes the +arithmetic, but it is what \code{issymmetric()} returns to the eigensolvers (\code{davidson} uses the hermitian algorithm, +\code{lanczos} requires it). This is the convention of \code{NRSMat} and \code{SparseSMat}, not the complex symmetric +one of \code{SparseMat}. + +The three arrays are \code{NRVec} members (\code{rowptr} of length $n+1$, \code{col} and \code{v} of length $n_{\rm nz}$, the +number of stored elements); each of them shares its data independently (section~\ref{sec:concepts:refcount}). Operations which +change only the values keep the index arrays shared, e.g.\ \code{B=A*2.}, \code{A+=a} with all diagonal elements stored, or +\code{realmatrix(A)}; \code{copyonwrite()} copies all three. Conversions store only elements with $|a_{ij}|>\epsilon_{\rm sp}$, +whereas arithmetic keeps elements which cancel to zero, until \code{simplify()} is called. $n_{\rm nz}$ is limited to $2^{31}-1$ +(the \code{NRVec} length). The class is instantiated for \code{double}, \code{float}, \code{std::complex} and +\code{std::complex}; all operations are CPU only (\code{laerror} for vectors and matrices on the GPU), the device-dependent +format is the one of cuSPARSE, so a GPU version can be added later. + +\subsection{Construction, conversion, access} +\begin{lareference} +\lafunc{CSRMat()}{Empty $0\times0$ matrix.} +\lafunc{explicit CSRMat(const SPMatindex n, const SPMatindex m); explicit CSRMat(const SPMatindex n)}{Zero $n\times m$ resp.\ $n\times n$ matrix.} +\lafunc{CSRMat(const SPMatindex n, const SPMatindex m, const NRVec &rowptr, const NRVec &col, const NRVec &v)}{From the three arrays, which are shared (not copied). In DEBUG their consistency, the column range and the ascending columns are checked.} +\lafunc{explicit CSRMat(const SparseMat &rhs)}{Duplicates are summed, elements with $|a_{ij}|\le\epsilon_{\rm sp}$ dropped. A symmetric \code{SparseMat} is unfolded without conjugation (complex symmetric, as \code{SparseMat} defines it); the flag is set for the real types only.} +\lafunc{explicit CSRMat(const SparseSMat &rhs); explicit CSRMat(const NRMat &rhs)}{Elements with $|a_{ij}|>\epsilon_{\rm sp}$; the flag is not set (call \code{setsymmetric()} if appropriate).} +\lafunc{explicit CSRMat(const NRSMat &rhs)}{Hermitian as \code{NRSMat}: the upper triangle is the conjugate of the stored lower one; the flag is set.} +\lafunc{void copyonwrite(bool detachonly=false, bool deep=true); void resize(const SPMatindex n, const SPMatindex m); void dealloc(); void clear(); void swap(CSRMat &rhs)}{As for the other classes; \code{resize} gives a zero matrix and clears the flag, \code{clear} keeps it.} +\lafunc{SPMatindex nrows() const; SPMatindex ncols() const; int length() const}{Dimensions and $n_{\rm nz}$.} +\lafunc{bool issymmetric() const; void setsymmetric(); void setunsymmetric()}{The hermitian flag; \code{setsymmetric} only declares (square matrices only).} +\lafunc{const NRVec & getrowptr() const; const NRVec & getcol() const; const NRVec & getval() const}{Read access to the arrays.} +\lafunc{const T operator()(const SPMatindex i, const SPMatindex j) const}{Element $a_{ij}$ by binary search in row $i$, zero if not stored. There is no write access to single elements.} +\lafunc{iterator begin() const; iterator end() const}{Read-only iterator over the stored elements in row-major order: \code{p->row}, \code{p->col}, \code{p->elem} (a \code{matel}), \code{p.notend()}.} +\end{lareference} +Other classes from \code{CSRMat}: \code{explicit NRMat(const CSRMat&)} (full matrix), \code{explicit NRSMat(const CSRMat&)} +(lower triangle, square only, as from \code{SparseSMat}), \code{explicit SparseMat(const CSRMat&)} (not symmetric), +\code{explicit SparseSMat(const CSRMat&)} and \code{explicit NRVec(const CSRMat&)} (for a $1\times m$ or $n\times 1$ matrix). + +\subsection{Arithmetic} +\begin{lareference} +\lafunc{CSRMat & operator*=(const T &a); const CSRMat operator*(const T &a) const}{Scaling of the values (BLAS); the index arrays stay shared, $a=0$ gives the zero matrix.} +\lafunc{CSRMat & operator=(const T &a); void identity()}{$a\cdot1$ resp.\ the unit matrix (square only).} +\lafunc{CSRMat & operator+=(const T &a); CSRMat & operator-=(const T &a); const CSRMat operator+(const T &a) const; const CSRMat operator-(const T &a) const}{$A\pm a\cdot1$ (square only): in place if all diagonal elements are stored, otherwise by a merge.} +\lafunc{void axpy(const T alpha, const CSRMat &x, const bool transp=false)}{$A\leftarrow A+\alpha x$ (or $\alpha x\Tr$): merge of the sorted rows in two passes; for the same structure (shared or equal index arrays) a BLAS \code{axpy} of the values. Elements which cancel stay stored.} +\lafunc{CSRMat & operator+=(const CSRMat &rhs); CSRMat & operator-=(const CSRMat &rhs); const CSRMat operator+(const CSRMat &rhs) const; const CSRMat operator-(const CSRMat &rhs) const}{Via \code{axpy}.} +\lafunc{const typename LA_traits::normtype norm(const T scalar=(T)0) const}{Frobenius norm of $A-\mathrm{scalar}\cdot1$.} +\lafunc{const T dot(const CSRMat &rhs) const; const T dot(const NRMat &rhs) const}{$\sum_{ij}\bar a_{ij}b_{ij}$, conjugating the left operand as \code{NRMat::dot} (unlike \code{SparseMat::dot}).} +\lafunc{const T trace() const}{Sum of the diagonal elements.} +\lafunc{void simplify(const typename LA_traits::normtype sparseepsilon=LA_precision::sparse_epsilon())}{Removes the elements with $|a_{ij}|\le$ \code{sparseepsilon}.} +\end{lareference} +The result of an addition is declared hermitian if both operands are (a matrix without stored elements counts as hermitian) and the +scalar factor is real; multiplying by or adding a non-real scalar clears the flag. In other classes: +\code{NRMat::operator+=(const CSRMat&)} and \code{operator-=}. + +\subsection{Products} +\begin{lareference} +\lafunc{void gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x) const}{$r\leftarrow\beta r+\alpha\,\mathrm{op}(A)x$, \code{trans} \code{'n'}, \code{'t'} or \code{'c'} (conjugate transposition, the same as \code{'t'} for real types); forwards to \code{NRVec::gemv}. The size of \code{x} and (in DEBUG) of the result are checked, $\beta=0$ clears the result.} +\lafunc{inline const NRVec operator*(const NRVec &rhs) const}{$Ax$.} +\lafunc{const T* diagonalof(NRVec &r, const bool divide=0, bool cache=false) const}{Diagonal (binary search in each row), or $r$ divided by it elementwise (zero diagonal elements skipped). For a non-square matrix the diagonal of $A\Tr A$ ($\sum_i a_{ij}^2$, no conjugation, as \code{SparseMat}), used as the preconditioner of \code{conjgrad}. \code{cache} is ignored.} +\lafunc{inline const NRMat operator*(const NRMat &rhs) const}{$AB$ with a dense result, via \code{NRMat::gemm} below.} +\lafunc{void gemm(const T beta, const CSRMat &a, const char transa, const CSRMat &b, const char transb, const T alpha)}{$C\leftarrow\beta C+\alpha\,\mathrm{op}(A)\,\mathrm{op}(B)$ with a sparse result (\code{trans} as for \code{gemv}), by the row-wise algorithm of Gustavson with a dense accumulator \cite{sparse:gustavson1978} in two passes; the rows of the result are sorted, elements which cancel stay stored, the result is not declared hermitian. With $\beta=0$ the dimensions of \code{*this} do not matter.} +\lafunc{inline const CSRMat operator*(const CSRMat &rhs) const}{$AB$.} +\end{lareference} +In other classes: \code{NRVec::gemv(const T beta, const CSRMat &a, const char trans, const T alpha, const NRVec &x)}, +\code{NRVec::operator*(const CSRMat&)} ($x\Tr A$), +\code{NRMat::gemm(const T &beta, const CSRMat &a, const char transa, const NRMat &b, const char transb, const T &alpha)} +(all combinations of \code{'n'}, \code{'t'}, \code{'c'}) and \code{NRMat::operator*(const CSRMat&)} (dense times sparse, dense result). +The sparse kernels are plain loops (there is no sparse BLAS in the libraries used). + +\subsection{Transposition, permutations, direct sums and products} +\begin{lareference} +\lafunc{const CSRMat transpose(bool conj=false) const; void transposeme(bool conj=false)}{$A\Tr$ or $A\Hc$ by a counting sort, $O(n_{\rm nz}+n+m)$; the flag is kept.} +\lafunc{void permuteme_rows(const NRPerm &p, const bool inverse=false); void permuteme_cols(const NRPerm &p, const bool inverse=false); void permuteme_both(const NRPerm &p, const NRPerm &q, const bool inverse=false)}{The convention of \code{SparseMat}: element $(i,j)$ moves to row $p_{i+1}-1$ resp.\ column $q_{j+1}-1$ (matrix indices from 0, permutations from 1, optionally inverted). Since the full matrix is stored, \code{permuteme_rows} permutes only the rows also of a hermitian matrix (unlike a symmetric \code{SparseMat}); the flag is cleared, except by \code{permuteme_both} with $p=q$.} +\lafunc{const CSRMat permuted_rows(...) const; const CSRMat permuted_cols(...) const; const CSRMat permuted_both(...) const}{Out-of-place versions.} +\lafunc{void permuterows(const NRVec &p); void permutecolumns(const NRVec &p); void permuteindices(const NRVec &p)}{As for \code{SparseMat}: $i\to p_i$ (indices from 0), \code{permuteindices} for rows and columns.} +\lafunc{CSRMat & oplusequal(const CSRMat &rhs); const CSRMat oplus(const CSRMat &rhs) const}{Direct sum $A\oplus B$; also with \code{NRMat} and \code{NRSMat} (converted as above, i.e.\ \code{NRSMat} as hermitian).} +\lafunc{const CSRMat otimes(const CSRMat &rhs) const}{Direct (Kronecker) product, $(A\otimes B)_{i n_B+k,\,j m_B+l}=a_{ij}b_{kl}$ as \code{NRMat::operator|}; also with \code{NRMat} and \code{NRSMat}.} +\end{lareference} +The results of \code{oplus} and \code{otimes} are declared hermitian if both operands are. + +\subsection{Input/output and free functions} +\begin{lareference} +\lafunc{void put(int fd, bool dimensions=1, bool transp=0, bool orcaformat=false) const; void get(int fd, bool dimensions=1, bool transp=0, bool orcaformat=false)}{Binary I/O: if \code{dimensions}, $n$ and $m$ (\code{SPMatindex}); the flag (\code{int}); the three arrays by \code{NRVec::put} with their lengths. \code{transp} writes or reads the transposed matrix; \code{orcaformat=true} raises \code{laerror}.} +\lafunc{std::ostream& operator<<(std::ostream &s, const CSRMat &x); std::istream& operator>>(std::istream &s, CSRMat &x)}{Text I/O in the format of \code{SparseMat} (read through a \code{SparseMat}, so duplicates are summed); the flag is not written.} +\lafunc{inline const CSRMat commutator(const CSRMat &x, const CSRMat &y, const bool trx=0, const bool tryy=0); inline const CSRMat anticommutator(...)}{$xy\mp yx$ via two \code{gemm} calls.} +\end{lareference} +In \ref{ch:nonclass}: \code{realpart()}, \code{imagpart()} of a complex \code{CSRMat} and \code{realmatrix()}, \code{imagmatrix()}, \code{complexmatrix()} +of real ones; only the values are converted (BLAS), the index arrays stay shared unless elements which became zero are removed. +The real part of a hermitian matrix and \code{realmatrix} of a declared symmetric one keep the flag. The templates of \code{matexp.h} +(\code{exp()}, \code{sincos()}, \code{exptimes()}) and the iterative solvers of \ref{ch:iterative} work with \code{CSRMat}. \section{Examples}\label{sec:sparse:examples} \begin{lstlisting} @@ -353,6 +445,15 @@ h.add(2,2,1.); SparseSMat h2 = h*h; //result stays symmetric NRVec w = h*x; std::cout << y << z << w << NRMat(h2) << sd; + +//compressed row format: assembled as SparseMat, then converted +SparseMat hb(4,4); +hb.add(0,0,1.); hb.add(1,0,0.2); hb.add(0,1,0.2); hb.add(3,3,2.); +CSRMat hc(hb); +hc.setsymmetric(); //declare it symmetric, e.g. for davidson +NRVec u = hc*x; +CSRMat > ev = exp(imagmatrix(hc)); //exp(iH), sparse +CSRMat hc2 = hc*2.; //shares rowptr and col with hc } \end{lstlisting} @@ -364,4 +465,7 @@ std::cout << y << z << w << NRMat(h2) << sd; \item In a symmetric \code{SparseMat} never store both $a_{ij}$ and $a_{ji}$; \code{setsymmetric()} does not check this. \item \code{SparseSMat::gemm} computes $A\Tr B$ (or $A\Hc B$); use \code{operator*} for the general product. \item Complex \code{SparseMat} matrices are complex symmetric, never hermitian, in all mirroring operations, whereas \code{SparseSMat} (\code{add} with \code{both=true}, conversion from \code{NRSMat}, \code{otimes_sparse}) is hermitian. +\item \code{CSRMat} cannot be filled element by element; assemble a \code{SparseMat} or \code{SparseSMat} and convert it. +\item The flag of \code{CSRMat} only declares hermiticity. Conversions from \code{NRMat}, \code{SparseSMat} and arrays (and from a complex + symmetric \code{SparseMat}) do not set it; call \code{setsymmetric()} for a hermitian matrix before \code{davidson} or \code{lanczos}. \end{itemize} diff --git a/doc/vec.tex b/doc/vec.tex index d020c76..f8fd42c 100644 --- a/doc/vec.tex +++ b/doc/vec.tex @@ -66,6 +66,7 @@ one: \code{rhs} becomes the real part (or the imaginary part if \code{imagpart}) $n(n+1)/2$ elements. \emph{Shares} the data with \code{S} (no copy).} \lafunc{explicit NRVec(const NRMat &rhs)}{View of the row-major storage of an $n\times m$ matrix as a vector of $nm$ elements. \emph{Shares} the data with \code{rhs}; if the library is compiled with \code{MATPTR}, the data are copied instead.} +\lafunc{explicit NRVec(const CSRMat &rhs)}{The same from a compressed row sparse matrix (section~\ref{sec:sparse:csr}).} \lafunc{explicit NRVec(const SparseMat &rhs)}{Dense vector from a sparse matrix having one row or one column (otherwise \code{laerror}); duplicate entries are summed. Defined in \texttt{sparsemat.cc} for \code{double} and \code{std::complex}.} \lafunc{~NRVec()}{Decrements the reference count, frees the data when it reaches zero.} @@ -162,6 +163,7 @@ with $y=$ \code{*this}; \code{trans} is \code{'n'} ($A$), \code{'t'} ($A\Tr$) or \code{*this} must already have the correct size (checked in DEBUG builds) and defined contents if $\beta\neq0$.} \lafunc{void gemv(const T beta, const NRSMat &a, const char trans, const T alpha, const NRVec &x)}{Same for a packed symmetric (real) or Hermitian (complex) matrix (BLAS spmv/hpmv); \code{trans}: for complex \code{'t'} gives $a\Tr x = a^* x$, \code{'n'} and \code{'c'} give $a x$; ignored for real matrices.} +\lafunc{void gemv(const T beta, const CSRMat &a, const char trans, const T alpha, const NRVec &x)}{The same for a compressed row sparse matrix, \code{trans} \code{'n'}, \code{'t'} or \code{'c'} (section~\ref{sec:sparse:csr}).} \lafunc{void gemv(const T beta, const SparseMat &a, const char trans, const T alpha, const NRVec &x, const bool treat_as_symmetric = false)}{Same for a sparse matrix (defined in \texttt{sparsemat.cc}, see Chapter~\ref{ch:sparse}); with \code{treat_as_symmetric} only one triangle is assumed to be stored.} @@ -172,7 +174,7 @@ real and imaginary parts are processed separately.} \lafunc{const NRVec operator*(const NRMat &mat) const}{Row vector times matrix, $x\Tr A$ (gemv with \code{'t'}; no conjugation). For $A x$ see \code{NRMat::operator*} in Chapter~\ref{ch:mat}.} \lafunc{const NRVec operator*(const NRSMat &mat) const}{Row vector times packed symmetric/hermitian matrix, $x\Tr S$ (for hermitian $S$ computed as $S\Tr x = S^* x$ by \code{gemv} with \code{'t'}).} -\lafunc{const NRVec operator*(const SparseMat &mat) const}{Row vector times sparse matrix; CPU only.} +\lafunc{const NRVec operator*(const SparseMat &mat) const; const NRVec operator*(const CSRMat &mat) const}{Row vector times sparse matrix; CPU only.} \lafunc{const NRMat otimes(const NRVec &rhs, const bool conjugate = false, const T &scale = 1) const}{Outer product $A_{ij}=\mathrm{scale}\cdot x_i\, y_j$, with $y_j^*$ if \code{conjugate} (complex only); BLAS ger/geru/gerc.} \lafunc{const NRMat operator|(const NRVec &rhs) const}{Same as \code{otimes(rhs,true)}, i.e.\ $x y\Hc$.} diff --git a/mat.h b/mat.h index f0e6828..d9020d8 100644 --- a/mat.h +++ b/mat.h @@ -424,9 +424,13 @@ public: NRMat & operator+=(const SparseMat &rhs); //! subtract given sparse matrix NRMat & operator-=(const SparseMat &rhs); + NRMat & operator+=(const CSRMat &rhs); + NRMat & operator-=(const CSRMat &rhs); //! perform the \b gemm operation void gemm(const T &beta, const SparseMat &a, const char transa, const NRMat &b, const char transb, const T &alpha); + void gemm(const T &beta, const CSRMat &a, const char transa, const NRMat &b, const char transb, const T &alpha); + const NRMat operator*(const CSRMat &rhs) const; //dense times compressed row sparse, dense result inline void simplify() {}; bool issymmetric() const { return 0; }; diff --git a/smat.h b/smat.h index 3de96e6..6d2c148 100644 --- a/smat.h +++ b/smat.h @@ -235,6 +235,7 @@ public: //members concerning sparse matrix explicit NRSMat(const SparseMat &rhs); // dense from sparse explicit NRSMat(const SparseSMat &rhs); // dense from sparse + explicit NRSMat(const CSRMat &rhs); // dense from sparse (lower triangle) inline void simplify() {}; //just for compatibility with sparse ones bool issymmetric() const {return 1;} void swap(NRSMat &rhs) //more efficient swap than via tmp and constructors and operator= diff --git a/sparsemat.h b/sparsemat.h index bbf3b0c..99750dc 100644 --- a/sparsemat.h +++ b/sparsemat.h @@ -80,6 +80,7 @@ public: inline int getcount() const {return count?*count:0;} explicit SparseMat(const NRMat &rhs); //construct from a dense one explicit SparseMat(const NRSMat &rhs); //construct from a dense symmetric one + explicit SparseMat(const CSRMat &rhs); //full storage, not symmetric SparseMat & operator=(const SparseMat &rhs); SparseMat & operator=(const T &a); //assign a to diagonal void identity() {*this = (T)1;}; diff --git a/t.cc b/t.cc index 65c42c4..e4be093 100644 --- a/t.cc +++ b/t.cc @@ -1880,7 +1880,6 @@ c.moveto(gpu1); cout << "Error = "<<(c-cgpu).norm()< h0; @@ -1903,7 +1902,6 @@ NRMat >r2 = exp(h2); cout <<"error = "<<(r2-NRMat >(r)).norm()< &a, const char trans, const T alpha, const NRVec &x); void gemv(const T beta, const NRSMat &a, const char trans /**< just for compatibility reasons */, const T alpha, const NRVec &x); void gemv(const T beta, const SparseMat &a, const char trans, const T alpha, const NRVec &x,const bool treat_as_symmetric = false); + void gemv(const T beta, const CSRMat &a, const char trans, const T alpha, const NRVec &x); void gemv( const typename LA_traits_complex::Component_type beta, const typename LA_traits_complex::NRMat_Noncomplex_type &a, @@ -375,6 +376,9 @@ public: result.gemv((T)0, mat, 't', (T)1, *this); return result; }; + + //! multiply given compressed row sparse matrix with this vector from left (defined in csrmat.h) + inline const NRVec operator*(const CSRMat &mat) const; //! compute the outer product of two vectors const NRMat otimes(const NRVec &rhs, const bool conjugate = false, const T &scale = 1) const; @@ -486,6 +490,7 @@ public: //! constructor creating vector from sparse matrix explicit NRVec(const SparseMat &rhs); + explicit NRVec(const CSRMat &rhs); //! routine for compatibility with sparse types inline void simplify() {};