\chapter{Packed symmetric and hermitian matrices: NRSMat}\label{ch:smat} \laheader{smat.h, smat.cc} \section{Purpose}\label{sec:smat:purpose} \code{NRSMat} stores a square symmetric (real \code{T}) or hermitian (complex \code{T}) matrix of order $n$ in packed form, i.e.\ only $n(n+1)/2$ elements of one triangle are kept. It halves the memory compared to \code{NRMat} and gives access to the packed BLAS/LAPACK routines (\code{dspmv}, \code{zhpmv}, \code{dspr2}, \code{zhpr2}, \code{dspev}, \ldots). Typical uses are Hamiltonians, overlap and density matrices, Hessians (see \ref{ch:optimize}) and as input for \code{diagonalize()} (see \ref{ch:nonclass}). Results of operations which are not symmetric in general (e.g.\ the product of two symmetric matrices) are returned as \code{NRMat}. \code{NRSMat_from1} (section~\ref{sec:smat:from1}) is the same class with the two-index \code{operator()} counting from 1. The file \code{smat.cc} contains explicit instantiations for the element types \code{double}, \code{float}, \code{std::complex}, \code{std::complex} and the integer types \code{char}, \code{short}, \code{int}, \code{long}, \code{long long} and their unsigned variants. Most numerical members have BLAS-based specializations only for the four floating point types. Some members are defined only for these types: \code{amax}, \code{amin}, \code{norm}, \code{dot}, \code{axpy}, \code{randomize}, \code{inverse} and the products with \code{NRMat} and \code{NRSMat}. \section{Storage and concepts}\label{sec:smat:storage} \subsection{Packed lower triangle} The elements $A_{ij}$ with $i\ge j$ (lower triangle) are stored row by row, i.e.\ the row-major packed lower triangle, which is the same memory layout as the column-major packed upper triangle (Fortran \code{UPLO='U'}): \begin{equation}\label{eq:smat:index} A_{ij} \;\to\; \mathtt{v}[k],\qquad k = \frac{i(i+1)}{2}+j \quad (0\le j\le i} can be viewed as an \code{NRVec} of length $n(n+1)/2$ and vice versa without copying (see section~\ref{sec:smat:construct}). \code{smat.h} defines the macro \code{NN2} (\code{(size_t)nn*(nn+1)/2}) which it uses internally and does not undefine; avoid using this name in your own code. \subsection{Index functions}\label{sec:smat:indexfunc} The following inline function templates (namespace \code{LA}, template parameter = integer type of the indices) compute the packed index and return \code{size_t}. They are used by the class itself and are useful for code working directly with packed data (e.g.\ two-electron integrals in \ref{ch:fourindex}). \begin{lareference} \lafunc{size_t SMat_index(T i, T j)}{Index (\ref{eq:smat:index}) for $0\le i,jj$, symmetric in $i,j$; returns \code{(size_t)-1} for $i=j$.} \lafunc{size_t ASMat_index_1(T i, T j)}{The same for indices counting from 1: $(i-2)(i-1)/2+j-1$ for $i>j$; \code{(size_t)-1} for $i=j$. Computed in the arithmetic of \code{T} (no cast to \code{size_t}), so it may overflow for large \code{int} indices.} \end{lareference} \subsection{Hermitian interpretation of complex matrices}\label{sec:smat:hermitian} For complex \code{T} an \code{NRSMat} is a \emph{hermitian} matrix: the lower triangle is stored and the upper triangle is its complex conjugate, $A_{ij}=A_{ji}^*$ for $i(const NRSMat&)} conjugates the upper triangle and \code{NRSMat(const NRMat&)} takes the hermitian part $(A+A\Hc)/2$; \item \code{fprintf()}, \code{sum()} and \code{NRMat::operator+=/-=} (also \code{+}, \code{-}) with an \code{NRSMat} operand use the full hermitian matrix; \code{operator>>} and \code{fscanf} read the full square matrix, the elements of the lower triangle (read later) are the ones stored; \item \code{operator<<}, \code{permuted()}, \code{submatrix(const NRVec&)}, \code{rsum()} and \code{csum()} are built on \code{operator()} and therefore use the stored element also for $i&, int)} and \code{NRVec(const NRSMat&)} also share the data (the reference count is common to the vector and the matrix). GPU support (\code{CUDALA}) is experimental, see section~\ref{sec:concepts:gpu}; many members are CPU only (\code{NOT_GPU}). \section{Construction, assignment, conversion}\label{sec:smat:construct} \begin{lareference} \lafunc{NRSMat()}{Empty matrix (no data, no reference count).} \lafunc{explicit NRSMat(const int n, const GPUID loc = undefined)}{Order $n$, elements uninitialized.} \lafunc{NRSMat(const T &a, const int n)}{Order $n$, all $n(n+1)/2$ stored elements (i.e.\ also the off-diagonal ones) set to \code{a}.} \lafunc{NRSMat(const T *a, const int n)}{Copy of $n(n+1)/2$ packed elements (lower triangle by rows) from \code{a}.} \lafunc{NRSMat(const NRSMat &rhs)}{Shallow copy.} \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); 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.} \lafunc{NRSMat & operator=(const T &a)}{Scalar matrix: zero all elements (by \code{memset}), set the diagonal to \code{a}. Dimension must be set before.} \lafunc{void identity()}{\code{*this = (T)1}.} \lafunc{void clear()}{Set all elements to zero (keeps the dimension).} \lafunc{void resize(const int n)}{Change the order; data are not preserved and new elements are uninitialized. A shared matrix is detached; an unshared one of the same order is left untouched; \code{n=0} deallocates.} \lafunc{void dealloc()}{\code{resize(0)}.} \lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{Make the data private, see section~\ref{sec:concepts:refcount}.} \lafunc{void swap(NRSMat &rhs)}{Exchange contents without copying.} \lafunc{void moveto(const GPUID dest); GPUID getlocation() const}{GPU location (experimental), see section~\ref{sec:concepts:gpu}.} \lafunc{int getcount() const}{Current reference count (0 for an empty matrix).} \end{lareference} Conversions in other classes: \code{explicit NRMat(const NRSMat&)} expands to a full square matrix (hermitian for complex types, see section~\ref{sec:smat:hermitian}); \code{explicit NRVec(const NRSMat&)} views the packed data as a vector (shared data). \code{NRMat::transposedtimes()} and \code{timestransposed()} return $A\Hc A$ and $A A\Hc$ as \code{NRSMat} (see \ref{ch:mat}). \section{Element access}\label{sec:smat:access} \begin{lareference} \lafunc{T& operator()(const int i, const int j); const T& operator()(const int i, const int j) const}{Element $(i,j)$, $0\le i,jj}; helper for code that uses packed storage for a matrix one triangle of which is redundant but not equal to the other (e.g.\ antisymmetric), to know whether the stored element must be transformed.} \lafunc{bool issymmetric() const}{Always true (interface compatibility with sparse matrices).} \lafunc{void simplify()}{Does nothing (interface compatibility with sparse matrices).} \end{lareference} \lawarning{The non-const \code{operator()} and \code{operator[]} do not call \code{copyonwrite()}. Call \code{x.copyonwrite()} before writing through them; in DEBUG builds they raise an error for a shared matrix even when only reading, so read elements of a possibly shared matrix through a const reference.} \section{Arithmetic and BLAS/LAPACK-backed operations}\label{sec:smat:arith} All in-place operators call \code{copyonwrite()}. The binary operators returning \code{NRSMat} are generated from the in-place ones. \begin{lareference} \lafunc{NRSMat & operator*=(const T &a)}{Multiply all elements by \code{a} (\code{dscal}/\code{zscal}).} \lafunc{NRSMat & operator+=(const T &a); NRSMat & operator-=(const T &a)}{Add/subtract $a\cdot 1$, i.e.\ \code{a} on the diagonal only.} \lafunc{NRSMat & operator+=(const NRSMat &rhs); NRSMat & operator-=(const NRSMat &rhs)}{Elementwise sum/difference (\code{daxpy}/\code{zaxpy}).} \lafunc{const NRSMat operator-() const}{Unary minus.} \lafunc{const NRSMat operator*(const T &a) const; const NRSMat operator+(const T &a) const; const NRSMat operator-(const T &a) const}{As the in-place versions (\code{+}/\code{-} act on the diagonal).} \lafunc{const NRSMat operator+(const NRSMat &rhs) const; const NRSMat operator-(const NRSMat &rhs) const}{Sum, difference.} \lafunc{const NRMat operator+(const NRMat &rhs) const; const NRMat operator-(const NRMat &rhs) const}{Sum/difference with a dense matrix, dense result.} \lafunc{const NRMat operator*(const NRSMat &rhs) const}{Matrix product (not symmetric in general). Real: dedicated BLAS-1/2 code; complex: \code{*this * NRMat(rhs)}, see section~\ref{sec:smat:hermitian}.} \lafunc{const NRMat operator*(const NRMat &rhs) const}{$S A$ by \code{dspmv}/\code{zhpmv} column by column.} \lafunc{const NRVec operator*(const NRVec &rhs) const}{$S x$ (\code{dspmv}/\code{zhpmv}).} \lafunc{const NRVec > operator*(const NRVec > &rhs) const}{Real symmetric matrix times complex vector (real and imaginary parts separately).} \lafunc{void gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x) const}{$r \leftarrow \beta r + \alpha S x$, forwards to \code{r.gemv(beta,*this,trans,alpha,x)}; \code{trans} is ignored.} \lafunc{void gemv(const T beta, NRVec > &r, const char trans, const T alpha, const NRVec > &x) const}{The same, real matrix and complex vectors.} \lafunc{void axpy(const T alpha, const NRSMat &x)}{$S \leftarrow S + \alpha x$. For hermitian complex matrices the result is hermitian only for real $\alpha$ (not checked).} \lafunc{void rank2update(const T alpha, const NRVec &x, const NRVec &y)}{Rank-2 update $S \leftarrow S + \alpha x y\Hc + \bar\alpha y x\Hc$ (real: $\alpha(xy\Tr+yx\Tr)$); \code{dspr2}/\code{zhpr2}/\code{sspr2}/\code{chpr2} for \code{double}/\code{complex}/\code{float}/\code{complex}, generic loop otherwise. A symmetric rank-1 update $S \leftarrow S + \beta x x\Hc$ (real $\beta$) is obtained as \code{rank2update(beta/2,x,x)}; there is no separate rank-1 member.} \lafunc{const T dot(const NRSMat &rhs) const}{$\sum_{i\ge j} \bar A_{ij} B_{ij}$, i.e.\ the dot product of the \emph{packed} vectors (off-diagonal elements counted once; \code{ddot}/\code{zdotc}).} \lafunc{const T dot(const NRVec &rhs) const}{Dot product of the packed data with a vector of length $n(n+1)/2$.} \lafunc{const typename LA_traits::normtype norm(const T scalar = (T)0) const}{Euclidean norm of the packed data of $S - \mathtt{scalar}\cdot 1$; off-diagonal elements are counted \emph{once}, so this is not the Frobenius norm of the full matrix.} \lafunc{const T trace() const}{Trace.} \lafunc{const T sum(bool onlytriangle=false) const}{Sum of all elements of the full matrix, or of the stored triangle only.} \lafunc{const NRVec rsum() const; const NRVec csum() const}{Row sums and column sums of the full matrix (equal for real, complex conjugate of each other for hermitian matrices).} \lafunc{inline const T amax() const; inline const T amin() const}{Stored element with the largest/smallest absolute value (complex: $|\Re|+|\Im|$); floating point types only.} \lafunc{const T* diagonalof(NRVec &r, const bool divide = 0, bool cache = false) const}{\code{divide=false}: store the diagonal into \code{r} and return \code{&r[0]}; \code{divide=true}: divide \code{r} elementwise by the nonzero diagonal elements and return \code{NULL}. \code{cache} is unused. Interface used by iterative solvers (\ref{ch:iterative}).} \lafunc{NRSMat inverse()}{Inverse via \code{NRMat(*this).inverse()}, symmetrized back. Floating point types only.} \lafunc{NRSMat& conjugateme(); const NRSMat conjugate() const}{Complex conjugation of all stored elements (no-op for real types).} \lafunc{NRSMat::normtype> abs() const}{Elementwise absolute values.} \lafunc{NRSMat::normtype> diffabs(const NRSMat &rhs) const}{Elementwise $|A_{ij}|-|B_{ij}|$.} \lafunc{void randomize(const typename LA_traits::normtype &x)}{Uniformly distributed elements in $(-x,x)$; complex: real and imaginary parts independent, imaginary part of the diagonal set to zero. Floating point types only.} \lafunc{const bool operator==(const NRSMat &rhs) const; const bool operator!=(const NRSMat &rhs) const}{Exact elementwise comparison.} \end{lareference} \section{Other member functions}\label{sec:smat:other} \subsection{Submatrices and permutations} \begin{lareference} \lafunc{const NRSMat submatrix(const int from, const int to) const}{Diagonal block with rows/columns \code{from}\ldots\code{to} (inclusive).} \lafunc{const NRSMat submatrix(const NRVec &selection) const}{Matrix $B_{ij} = A_{s_i s_j}$ for the index list $s$ = \code{selection}.} \lafunc{void storesubmatrix(const int from, const NRSMat &rhs)}{Store \code{rhs} as diagonal block starting at \code{from}.} \lafunc{void storesubmatrix(const NRVec &selection, const NRSMat &rhs)}{$A_{s_i s_j} = B_{ij}$.} \lafunc{const NRSMat permuted(const NRPerm &p, const bool inverse=false) const}{Symmetric permutation of rows and columns by \code{p} (indices from 1, see \ref{ch:permutation}): \code{inverse=false} gives $B_{p_i p_j}=A_{ij}$, \code{inverse=true} gives $B_{ij}=A_{p_i p_j}$.} \end{lareference} \subsection{Input/output} See section~\ref{sec:concepts:io} for the general conventions. \begin{lareference} \lafunc{void put(int fd, bool dimensions = 1, bool transp = 0, bool orcaformat=false) const}{Binary output: if \code{dimensions}, $n$ is written twice (as for a square \code{NRMat}), followed by \code{sizeof(T)} if \code{orcaformat}; then the $n(n+1)/2$ packed elements. \code{transp} is ignored.} \lafunc{void get(int fd, bool dimensions = 1, bool transp = 0, bool orcaformat=false)}{Binary input matching \code{put}; with \code{dimensions=false} the current order is used.} \lafunc{void fprintf(FILE *f, const char *format, const int modulo) const}{Formatted output of the full square matrix by \code{lawritemat} (as \code{NRMat::fprintf}, see \ref{ch:mat}).} \lafunc{void fscanf(FILE *f, const char *format)}{Reads the dimensions (must be equal) and then the \emph{whole square} matrix row by row; this is intentional, the data are assumed symmetric. Since $(i,j)$ and $(j,i)$ share the storage, the element read later, i.e.\ the lower triangle one, wins.} \lafunc{void printsorted(std::ostream &s, int direction=1, bool absvalue=false, bool stable=false, typename LA_traits::normtype thr=0, int topmax=0) const}{Print the stored elements sorted with their indices, see \code{NRVec::printsorted} in \ref{ch:vec}.} \end{lareference} \section{Related free functions}\label{sec:smat:free} \begin{lareference} \lafunc{std::ostream& operator<<(std::ostream &s, const NRSMat &x)}{Text output: \code{n n}, then the full square matrix row by row.} \lafunc{std::istream& operator>>(std::istream &s, NRSMat &x)}{Text input of the same format; the full square is read, the lower triangle element wins (as in \code{fscanf}). Error if the dimensions differ.} \lafunc{const NRSMat operator+(const T &a, const NRSMat &rhs); const NRSMat operator*(const T &a, const NRSMat &rhs)}{Scalar from the left: $a\cdot 1 + S$, $aS$.} \lafunc{const NRSMat operator-(const T &a, const NRSMat &rhs) = delete}{Deliberately not available (compile error); write \code{-S+a} for $a\cdot1-S$.} \lafunc{NRSMat > complexify(const NRSMat &rhs)}{Complex copy of a real matrix.} \lafunc{void NRSMat_convert(NRSMat &a, const NRSMat &b)}{Element type conversion (\code{a} is resized, elements converted by a cast).} \end{lareference} Functions in other headers taking \code{NRSMat} (described in \ref{ch:nonclass}): \code{diagonalize()} (\code{dspev}/\code{zhpev}, also generalized problem), \code{linear_solve()}, \code{trace2()}, \code{twoside_transform()} ($C\Hc S C$), \code{matrixfunction()}, \code{sqrt()}, \code{sqrtinv()}, \code{log()}, \code{realpart()}, \code{imagpart()}. \code{NRMat} has \code{operator+=}, \code{operator-=}, \code{operator+}, \code{operator-} and \code{operator*} with an \code{NRSMat} right operand, and \code{NRVec} has \code{gemv} with an \code{NRSMat} and \code{operator*(const NRSMat&)} (vector from the left); see \ref{ch:mat} and \ref{ch:vec}. \section{NRSMat\_from1}\label{sec:smat:from1} \code{NRSMat_from1} is derived from \code{NRSMat} and differs only in \code{operator()}, whose indices run from 1 to $n$ (\code{SMat_index_1}). \code{operator[]} and everything else are inherited and still count from 0; arithmetic operators return \code{NRSMat}, which converts back implicitly. \begin{lareference} \lafunc{NRSMat_from1(); explicit NRSMat_from1(const int n); NRSMat_from1(const T &a, const int n); NRSMat_from1(const T *a, const int n)}{As for \code{NRSMat} (the last one inherits the bug of the base constructor).} \lafunc{NRSMat_from1(const NRSMat &rhs)}{Implicit conversion from the base class (shallow).} \lafunc{explicit NRSMat_from1(const NRMat &rhs); explicit NRSMat_from1(const NRVec &rhs, const int n)}{As for \code{NRSMat}.} \lafunc{const T& operator()(const int i, const int j) const; T& operator()(const int i, const int j)}{Element $(i,j)$, $1\le i,j\le n$. Range checked in DEBUG; unlike the base class the non-const version does not check the reference count. Plain storage access as in the base class (no conjugation, section~\ref{sec:smat:hermitian}).} \end{lareference} \section{Examples}\label{sec:smat:examples} \begin{lstlisting} #include "la.h" using namespace LA; int main() { const int n=4; NRSMat s(n); for(int i=0; i x(1.,n); NRVec y = s*x; //dspmv NRMat full(s); //full square copy NRSMat t = s; //shares data with s t.copyonwrite(); t(0,3) = 5.; //same storage as t(3,0); s unchanged t.rank2update(0.5,x,y); //t += 0.5*(x y^T + y x^T) const NRSMat &sc = s; double sum=0; for(size_t k=0; k w(n); NRMat v(n,n); NRSMat work(s); //diagonalize() detaches work itself diagonalize(work,w,&v); //eigenvalues/vectors, work is overwritten std::cout << s << w; } \end{lstlisting} \section{Pitfalls}\label{sec:smat:pitfalls} \begin{itemize} \item \code{size()} is $n(n+1)/2$, use \code{nrows()} for the order. \item \code{dot()} and \code{norm()} work on the packed triangle; for the Frobenius quantities of the full matrix use e.g.\ \code{NRMat(s).norm()} or \code{trace2()}. \item For complex types read section~\ref{sec:smat:hermitian} carefully. \item \code{NRSMat(const NRVec&,int)} and \code{NRVec(const NRSMat&)} share data with their argument. \item \code{NRSMat(const NRMat&)} silently symmetrizes (takes the hermitian part for complex types), it does not check symmetry. \item Complex matrices: \code{operator()(i,j)} with $i}; converting back with \code{NRSMat(...)} symmetrizes. \item \code{operator=(const T&)} produces a scalar (diagonal) matrix, whereas the constructor \code{NRSMat(a,n)} fills all elements. \end{itemize}