Files
LA_library/doc/sparse.tex
T
2026-11-05 23:23:55 +01:00

472 lines
49 KiB
TeX

\chapter{Sparse matrices}\label{ch:sparse}
\laheader{sparsemat.h, sparsemat.cc, sparsesmat.h, sparsesmat.cc, csrmat.h, csrmat.cc}
\section{Overview}\label{sec:sparse:overview}
\LA{} offers three independent sparse matrix classes with different storage schemes, plus a small helper class:
\begin{itemize}
\item \code{SparseMat<T>} (section~\ref{sec:sparse:sparsemat}): an \emph{unordered linked list} of
(row, column, value) triplets. Appending elements is cheap, duplicates are allowed and summed,
sorting is done on demand. A flag marks symmetric matrices of which only one triangle is stored.
Products with dense vectors and matrices are its main purpose (e.g.\ CI-type Hamiltonians in
Davidson/Lanczos solvers, \ref{ch:iterative}, and the matrix exponential).
\item \code{SparseSMat<T>} (section~\ref{sec:sparse:sparsesmat}): an array of rows, each row a
\code{std::map} from column index to value. It allows efficient random access and accumulation and
sparse$\times$sparse products, and offers a pivoted sparse Cholesky decomposition. It was written for
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<T>} (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<T>} (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}
Common definitions in \code{sparsemat.h}:
\begin{lareference}
\lafunc{typedef unsigned int SPMatindex; typedef int SPMatindexdiff}{Index type and index difference type. All row and column indices count from 0.}
\lafunc{const double SPARSEEPSILON=1e-15}{Kept for user code. The library itself uses the precision-dependent threshold \code{LA_precision<normtype>::sparse_epsilon()} (section~\ref{sec:concepts:traits}; $10^{-15}$ for double, i.e.\ $\epsilon_{\rm sp}$, and $10^{-7}$ for float): elements with absolute value below (or equal to) it are dropped by \code{simplify()} and by most constructors and accumulating operations; it is denoted $\epsilon_{\rm sp}$ below.}
\lafunc{template<typename T> struct matel {T elem; SPMatindex row; SPMatindex col; matel *next;}}{List element of \code{SparseMat}; also the value type returned by the iterators of both classes.}
\end{lareference}
Both classes are explicitly instantiated for \code{double}, \code{float}, \code{std::complex<double>} and \code{std::complex<float>}.
Several operations are implemented for the real types \code{double} and \code{float} only. \code{SparseMat} nowhere conjugates when
it mirrors elements of a symmetric matrix (source comments: ``not OK for complex hermitean''), so
its complex symmetric matrices are complex symmetric, not hermitian. \code{SparseSMat} stores both triangles
and its mirroring operations follow the hermitian convention of \code{NRSMat} (section~\ref{sec:smat:hermitian}).
Neither class has GPU support.
The header \code{sparsemat_traits.h} is an obsolete leftover: it specializes a template \code{NRMat_traits}
which no longer exists, is not included by any file and is not installed; it is kept in the source tree.
\section{SparseMat}\label{sec:sparse:sparsemat}
\laheader{sparsemat.h, sparsemat.cc}
\subsection{Storage}\label{sec:sparse:storage}
The nonzero elements form a singly linked list of \code{matel<T>} (pointer \code{list}); new elements are
prepended. The list is in no particular order and may contain several entries with the same
$(i,j)$, which represent their sum; \code{simplify()} merges such duplicates, removes elements with
$|a_{ij}|\le$ $\epsilon_{\rm sp}$ and leaves the list sorted by rows. Operations needing an ordering
(\code{simplify}, sparse products, \code{dot}) build private arrays of pointers sorted by rows or by columns
(member \code{sort()}); these arrays and the cached element count \code{nonzero} are kept until the next
operation which calls the private \code{unsort()}: \code{add()} (and everything built on it), \code{simplify},
\code{resize}, \code{transposeme}, the permutation members, \code{get}, assignment, and \code{copyonwrite()} of a
shared matrix. Writing \emph{indices} through the iterator or \code{getlist()} after a sort is not detected.
\textbf{Symmetric flag.} If \code{issymmetric()} is true, the matrix is square and each stored off-diagonal
element $a_{ij}$ represents both $a_{ij}$ and $a_{ji}$ (no conjugation); diagonal elements are stored once.
Which triangle is used is arbitrary: the constructor from \code{NRSMat} stores $i\ge j$, \code{simplify()}
moves all elements to $i\le j$, and user code may mix them, but it must not store both $a_{ij}$ and $a_{ji}$
unless their sum is meant. \code{setsymmetric()} only sets the flag, \code{defineunsymmetric()} only clears it
(the data are then interpreted as a triangular matrix), and \code{setunsymmetric()} unfolds the matrix by
adding the mirror images of the off-diagonal elements. Operations not supporting symmetric storage
(sparse$\times$sparse products, \code{sort()}) unfold it themselves or raise an error.
\subsection{Reference counting}\label{sec:sparse:refcount}
The list is reference counted: the copy constructor, \code{operator=} and \code{transpose()} of a symmetric
matrix share it (sorted arrays are never shared). There is \emph{no} \code{operator|=}; a deep copy is
obtained by \code{SparseMat<T> b(a); b.copyonwrite();}. \code{copyonwrite()} copies the list (in reversed
order) when it is shared.
Most modifying members call \code{copyonwrite()}: \code{simplify}, \code{operator+=}/\code{-=} with a matrix,
\code{addtriangle}, \code{join}, \code{axpy}, \code{operator*=}, \code{transposeme}, \code{setunsymmetric},
\code{incsize}, \code{addsafe}, the permutation members, \code{gemm}, \code{get}; \code{operator=(const T&)},
\code{resize} and \code{gemm} with \code{beta=0} detach by reallocation. Exceptions:
\begin{itemize}
\item \code{add()} is a minimal inline function: no \code{copyonwrite()}, no range check (it only invalidates
the sorted arrays and the cached \code{nonzero} if they exist). By design, user code must call
\code{copyonwrite()} before \code{add()} on a possibly shared matrix (as done in the examples in \code{t.cc}).
Adding to a shared list does not corrupt the other copies (elements are prepended), but the added
elements are leaked or, after a later \code{copyonwrite()}, silently included only in this copy.
\item \code{operator+=(const T&)} and \code{operator-=(const T&)} call \code{add()} before the \code{copyonwrite()}
inside \code{simplify()} (result correct, memory leaked when shared).
\item The iterator and \code{getlist()} give writable access to the list elements without \code{copyonwrite()}.
\item \code{norm()} is const but calls \code{simplify()} (which detaches a shared list).
\end{itemize}
\subsection{Construction, assignment, conversion}
\begin{lareference}
\lafunc{SparseMat()}{Undefined empty matrix ($0\times0$, no reference count).}
\lafunc{SparseMat(const SPMatindex n, const SPMatindex m)}{Defined empty (all zero) $n\times m$ matrix, not symmetric.}
\lafunc{SparseMat(const SparseMat &rhs)}{Shallow copy; an empty matrix gets its own reference count.}
\lafunc{explicit SparseMat(const NRMat<T> &rhs)}{From a dense matrix, elements with $|a_{ij}|>$ $\epsilon_{\rm sp}$.}
\lafunc{explicit SparseMat(const NRSMat<T> &rhs)}{From a packed symmetric matrix; symmetric flag set, lower triangle stored.}
\lafunc{explicit SparseMat(const CSRMat<T> &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.}
\lafunc{void identity()}{\code{*this = (T)1}.}
\lafunc{void resize(const SPMatindex n, const SPMatindex m)}{New empty $n\times m$ matrix; data are discarded (detaches if shared), the symmetric flag is cleared.}
\lafunc{void dealloc()}{\code{resize(0,0)}.}
\lafunc{void incsize(const SPMatindex n, const SPMatindex m)}{Enlarge the dimensions by $n$ and $m$ keeping the elements (used for direct sums).}
\lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{Make the list private, see section~\ref{sec:concepts:refcount}.}
\lafunc{void clear()}{Zero matrix of the same dimensions: as \code{resize(nrows(),ncols())}, but the symmetric flag is kept (detaches if shared).}
\lafunc{void swap(SparseMat &rhs)}{Exchange contents without copying.}
\lafunc{int getcount() const}{Reference count (0 if undefined).}
\end{lareference}
Dense from sparse: \code{explicit NRMat<T>(const SparseMat<T>&)} (symmetric matrices are unfolded,
duplicates summed), \code{explicit NRSMat<T>(const SparseMat<T>&)} (requires the symmetric flag, duplicates summed),
\code{explicit NRVec<T>(const SparseMat<T>&)} (for a $1\times m$ or $n\times 1$ matrix).
\subsection{Element insertion and access}
\begin{lareference}
\lafunc{inline void add(const SPMatindex n, const SPMatindex m, const T elem)}{Prepend element $(n,m)$; it is summed with existing entries at the same position. No checks, no \code{copyonwrite()}, see section~\ref{sec:sparse:refcount}. The matrix must have been dimensioned (constructor with dimensions or \code{resize}).}
\lafunc{void addsafe(const SPMatindex n, const SPMatindex m, const T elem)}{As \code{add}, but ignores $|$\code{elem}$|\le\epsilon_{\rm sp}$, calls \code{copyonwrite()} and creates the reference count of an undefined matrix (range check only with the lowercase \code{debug} macro).}
\lafunc{void simplify(const typename LA_traits<T>::normtype sparseepsilon=LA_precision<normtype>::sparse_epsilon())}{Sum duplicates, remove elements with $|a_{ij}|\le$ \code{sparseepsilon}, sort by rows; symmetric matrices are first moved to the upper triangle ($i\le j$).}
\lafunc{unsigned int length() const}{Number of list elements (cached until the next modification, see section~\ref{sec:sparse:storage}).}
\lafunc{unsigned int sort(int type) const}{Build the internal array sorted by rows (\code{type=0}) or columns (\code{type=1}) and return the number of elements. Logically const, but unfolds a symmetric matrix (\code{setunsymmetric}). Normally called internally only.}
\lafunc{inline matel<T> *getlist() const; void setlist(matel<T> *l)}{Raw access to the list head (no reference count handling; \code{setlist} does not free the old list).}
\lafunc{iterator begin() const; iterator end() const}{Forward iterator over the raw list (unsorted, duplicates possible, one triangle for symmetric matrices). \code{*it} is \code{matel<T>&}, \code{it->row}, \code{it->col}, \code{it->elem} are writable; \code{++} (pre- and postfix), \code{==}, \code{!=}.}
\lafunc{inline SPMatindex nrows() const; inline SPMatindex ncols() const}{Dimensions.}
\lafunc{inline bool issymmetric() const}{Symmetric flag.}
\lafunc{inline void setsymmetric(); inline void defineunsymmetric(); void setunsymmetric()}{Set the flag (square only) / clear the flag / unfold the stored triangle, see section~\ref{sec:sparse:storage}.}
\end{lareference}
\subsection{Arithmetic}
Binary operators with a matrix operand must not combine a symmetric left operand with a general right one.
\begin{lareference}
\lafunc{SparseMat & operator*=(const T &a)}{Scale all elements (\code{a=0} calls \code{clear()}).}
\lafunc{SparseMat & operator+=(const T &a); SparseMat & operator-=(const T &a)}{Add/subtract $a\cdot 1$ (square matrices), then \code{simplify()}.}
\lafunc{SparseMat & operator+=(const SparseMat &rhs); SparseMat & operator-=(const SparseMat &rhs)}{Append \code{rhs} (unfolded if \code{rhs} is symmetric and \code{*this} is not; error for symmetric \code{*this} and general \code{rhs}), then \code{simplify()}.}
\lafunc{const SparseMat operator+(const T &rhs) const; const SparseMat operator-(const T &rhs) const; const SparseMat operator*(const T &rhs) const}{Out-of-place versions.}
\lafunc{const SparseMat operator+(const SparseMat &rhs) const; const SparseMat operator-(const SparseMat &rhs) const}{Out-of-place versions.}
\lafunc{SparseMat & addtriangle(const SparseMat &rhs, const bool lower, const char sign)}{Append the elements of \code{rhs} with $i\le j$ (\code{lower=true}) or $i\ge j$ (\code{lower=false}), all of them if \code{rhs} is symmetric; \code{sign} is \code{'+'} or \code{'-'}. No \code{simplify()}.}
\lafunc{SparseMat & join(SparseMat &rhs)}{Fast \code{+=}: moves the list of \code{rhs} (must be unshared, same dimensions and symmetry) to the end of this list; \code{rhs} is left empty. No \code{simplify()}.}
\lafunc{void axpy(const T alpha, const SparseMat &x, const bool transp=0)}{$A \leftarrow A + \alpha x$ or $A + \alpha x\Tr$; no \code{simplify()} (except for $\alpha=\pm1$, which uses \code{+=}/\code{-=}).}
\lafunc{const T trace() const}{Trace.}
\lafunc{const typename LA_traits<T>::normtype norm(const T scalar=(T)0) const}{Frobenius norm of $A-\mathtt{scalar}\cdot1$ of the full matrix (symmetric off-diagonal elements counted twice). Calls \code{simplify()}.}
\lafunc{const T dot(const SparseMat &rhs) const}{$\sum_{ij} a_{ij} b_{ij}$ without conjugation (also for complex types), general (not symmetric) matrices only; merge of the two lists sorted by rows.}
\lafunc{const T dot(const NRMat<T> &rhs) const}{$\sum_{ij} a_{ij} b_{ij}$ with a dense matrix; \code{double} and \code{float} only.}
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide=0, bool cache=false) const}{Square matrix: diagonal into \code{r} (\code{divide=false}, returns \code{&r[0]}) or divide \code{r} by the nonzero diagonal elements (\code{divide=true}, returns \code{NULL}). Rectangular matrix: the diagonal of $A\Tr A$ (assuming a simplified matrix), for preconditioning. \code{cache} is unused.}
\end{lareference}
\subsection{Products}
\begin{lareference}
\lafunc{inline const NRVec<T> operator*(const NRVec<T> &rhs) const}{$A x$.}
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x, bool treat_as_symmetric=false) const}{$r \leftarrow \beta r + \alpha\,\mathrm{op}(A)x$; forwards to \code{NRVec<T>::gemv}. Any \code{trans} other than \code{'n'} means transposition (no conjugation). With \code{treat_as_symmetric} a general matrix is used as if it had the symmetric flag. The sizes of \code{x} and (in DEBUG) of the result are checked.}
\lafunc{inline const NRMat<T> operator*(const NRMat<T> &rhs) const}{$A B$ via \code{NRMat<T>::gemm} with a sparse first operand; \code{double} and \code{float} only (the complex types raise ``not implemented yet'').}
\lafunc{const SparseMat operator*(const SparseMat &rhs) const}{Sparse product of two general matrices (error if one is symmetric); the result is simplified.}
\lafunc{void gemm(const T beta, const SparseMat &a, const char transa, const SparseMat &b, const char transb, const T alpha)}{$C \leftarrow \beta C + \alpha\,\mathrm{op}(A)\,\mathrm{op}(B)$ for general \code{a}, \code{b}; if \code{*this} is symmetric (possible only with $\beta\neq0$) only elements with $i\le j$ are added. Result simplified. \code{a} and \code{b} must not be \code{*this} (checked in DEBUG).}
\end{lareference}
Related functions in other classes:
\code{NRVec<T>::gemv(const T beta, const SparseMat<T> &a, const char trans, const T alpha, const NRVec &x, const bool treat_as_symmetric=false)},
\code{NRVec<T>::operator*(const SparseMat<T>&)} ($x\Tr A$),
\code{NRMat<T>::gemm(const T &beta, const SparseMat<T> &a, const char transa, const NRMat &b, const char transb, const T &alpha)}
(\code{double} and \code{float} only) and \code{NRMat<T>::operator+=}, \code{operator-=(const SparseMat<T>&)}.
\subsection{Transposition, permutations, direct sums}
\begin{lareference}
\lafunc{void transposeme(); const SparseMat transpose() const}{Transpose in place / out of place (no conjugation); trivial for symmetric matrices.}
\lafunc{void permuteindices(const NRVec<SPMatindex> &p)}{Square matrix: $(i,j)\to(p_i,p_j)$, indices from 0.}
\lafunc{void permuterows(const NRVec<SPMatindex> &p); void permutecolumns(const NRVec<SPMatindex> &p)}{$i\to p_i$ resp.\ $j\to p_j$ (for symmetric matrices both indices are permuted), indices from 0.}
\lafunc{void permuteme_rows(const NRPerm<int> &p, const bool inverse=false); void permuteme_cols(const NRPerm<int> &p, const bool inverse=false)}{The same with a permutation object (\ref{ch:permutation}), optionally inverted. The matrix indices count from 0, the permutation (as always for \code{NRPerm}) from 1: element $(i,j)$ moves to row $p_{i+1}-1$ resp.\ column $p_{j+1}-1$ (for symmetric matrices both indices are permuted), the same direction as \code{NRMat::permuted_rows}/\code{permuted_cols}.}
\lafunc{void permuteme_both(const NRPerm<int> &p, const NRPerm<int> &q, const bool inverse=false)}{Rows by \code{p}, columns by \code{q} (same index convention).}
\lafunc{const SparseMat permuted_rows(const NRPerm<int> &p, const bool inverse=false) const; const SparseMat permuted_cols(const NRPerm<int> &p, const bool inverse=false) const; const SparseMat permuted_both(const NRPerm<int> &p, const NRPerm<int> &q, const bool inverse=false) const}{Out-of-place versions.}
\lafunc{SparseMat & oplusequal(const SparseMat &rhs); const SparseMat oplus(const SparseMat &rhs) const}{Direct sum $A\oplus B$ (block diagonal); both must have the same symmetry.}
\lafunc{SparseMat & oplusequal(const NRMat<T> &rhs); const SparseMat oplus(const NRMat<T> &rhs) const}{Direct sum with a dense matrix (general \code{*this} only).}
\lafunc{SparseMat & oplusequal(const NRSMat<T> &rhs); const SparseMat oplus(const NRSMat<T> &rhs) const}{Direct sum with a packed symmetric matrix (symmetric \code{*this} only; the lower triangle of \code{rhs} is added).}
\lafunc{const SparseMat otimes(const SparseMat &rhs) const; const SparseMat otimes(const NRMat<T> &rhs) const; const SparseMat otimes(const NRSMat<T> &rhs) const}{Direct (Kronecker) product: declared, but not implemented anywhere (link error).}
\end{lareference}
\subsection{Input/output}
\begin{lareference}
\lafunc{void put(int fd, bool dimensions=1, bool transposed=false, bool orcaformat=false) const}{Binary output: if \code{dimensions}, $n$, $m$ (\code{SPMatindex}), the symmetric flag and the cached element count (\code{int}); then for each list element row, column and the value (written by \code{LA_traits<T>::put}, so nested types work); terminated by the pair $(-1,-1)$. \code{transposed} swaps rows and columns.}
\lafunc{void get(int fd, bool dimensions=1, bool transposed=false, bool orcaformat=false)}{Binary input matching \code{put}. With \code{dimensions=false} the read list replaces the current one (which is freed, or detached if shared); the dimensions are kept.}
\end{lareference}
The argument \code{orcaformat} exists for the uniform \code{LA_traits} interface (section~\ref{sec:concepts:io}), so that
e.g.\ \code{NRVec<SparseMat<T>>} can be written and read; the ORCA format is defined for dense data only and
\code{orcaformat=true} raises \code{laerror}. The same holds for \code{SparseSMat}.
\subsection{Related free functions}
\begin{lareference}
\lafunc{std::ostream& operator<<(std::ostream &s, const SparseMat<T> &x)}{Text output: \code{n m}, then lines \code{row col value} in list order, terminated by \code{-1 -1}. The symmetric flag is not written.}
\lafunc{std::istream& operator>>(std::istream &s, SparseMat<T> &x)}{Text input of the same format; the result is a general (not symmetric) matrix.}
\lafunc{inline const SparseMat<T> commutator(const SparseMat<T> &x, const SparseMat<T> &y, const bool trx=0, const bool tryy=0)}{$[\mathrm{op}(x),\mathrm{op}(y)] = xy-yx$, via two \code{gemm} calls (general matrices only).}
\lafunc{inline const SparseMat<T> anticommutator(const SparseMat<T> &x, const SparseMat<T> &y, const bool trx=0, const bool tryy=0)}{$xy+yx$.}
\end{lareference}
In \ref{ch:nonclass}: \code{realpart()} and \code{imagpart()} of a \code{SparseMat<complex<double>>} or \code{SparseMat<complex<float>>}. The generic
matrix functions in \code{matexp.h} (e.g.\ \code{exp()}) work with \code{SparseMat}, and the iterative solvers
of \ref{ch:iterative} use its \code{gemv}, \code{diagonalof}, \code{issymmetric}, \code{nrows} interface.
\section{SemiSparseMat}\label{sec:sparse:semisparse}
\laheader{sparsemat.h}
A rudimentary class (``implemented just enough to call davidson etc.\ on it'') holding a dense diagonal and
a sparse off-diagonal part, both public members. If \code{diagonal} is nonempty, the diagonal elements
are assumed to be stored \emph{only} there. It is not reference counted by itself; its members are.
\begin{lareference}
\lafunc{NRVec<T> diagonal; SparseMat<T> offdiagonal}{The two parts (public data members).}
\lafunc{SemiSparseMat(); SemiSparseMat(const SPMatindex n, const SPMatindex m)}{Empty object / $n\times m$; for $n=m$ the diagonal vector is allocated and zeroed (zero matrix).}
\lafunc{SPMatindex nrows() const; SPMatindex ncols() const; SPMatindex issymmetric() const; void setsymmetric()}{Forwarded to \code{offdiagonal}.}
\lafunc{void add(const SPMatindex n, const SPMatindex m, const T elem)}{Diagonal elements go to \code{diagonal} (if allocated), others to \code{offdiagonal.add()}; no \code{copyonwrite()}.}
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x, bool treat_as_symmetric=false) const; NRVec<T> operator*(const NRVec<T> &rhs) const}{Matrix-vector product.}
\lafunc{const T* diagonalof(NRVec<T> &x, const bool divide=0, bool cache=false) const}{As for \code{SparseMat}, using \code{diagonal} if allocated.}
\lafunc{void clear(); void copyonwrite(); void resize(const SPMatindex n, const SPMatindex m)}{Applied to both parts (\code{resize} gives a zero matrix and allocates the diagonal only for $n=m$).}
\lafunc{std::ostream& operator<<(std::ostream &s, const SemiSparseMat<T> &x)}{Prints both parts.}
\lafunc{explicit NRMat<T>(const SemiSparseMat<T> &rhs)}{Dense conversion.}
\end{lareference}
\section{SparseSMat}\label{sec:sparse:sparsesmat}
\laheader{sparsesmat.h, sparsesmat.cc}
\subsection{Storage and concepts}
The data member \code{v} is an array of \code{nrows()} pointers to \code{std::map<SPMatindex,T>}; row $i$ maps
column indices to values and is \code{NULL} if empty. Each position occurs at most once, rows are
sorted by column automatically.
Despite its name, \code{SparseSMat} is a \emph{general} sparse matrix: it need not be square ($n\times m$),
and the elements $(i,j)$ and $(j,i)$ are stored independently, so nonsymmetric matrices are represented
exactly. \code{operator*} (the general product $AB$), \code{gemv}, \code{axpy}, the iterator, \code{transpose()},
\code{submatrix}/\code{storesubmatrix}, \code{put}/\code{get}, the text I/O and the conversions to and from \code{NRMat}
work for general matrices. The symmetry (hermiticity for complex types) survives only as a convention of some operations:
\begin{itemize}
\item \code{add(i,j,x)} with the default \code{both=true} inserts $x$ at $(i,j)$ and $x^*$ at $(j,i)$; use
\code{add(i,j,x,false)} for a single element;
\item the constructor from \code{NRSMat} and \code{otimes_sparse} produce hermitian (real: symmetric) matrices;
\item \code{gemm} computes $A\Tr B$ (or $A\Hc B$), i.e.\ $AB$ only for $A=A\Tr$ ($A=A\Hc$), and is restricted to square matrices;
\item \code{NRSMat(const SparseSMat&)} takes the lower triangle and assumes a hermitian matrix;
\item \code{issymmetric()} always returns \code{true} (needed by the Davidson interface, chapter~\ref{ch:iterative}).
\end{itemize}
In particular complex symmetric matrices such as $\exp(\mathrm{i}H)$ with real symmetric $H$ (the original purpose of
the class, built e.g.\ by \code{imagmatrix} and \code{exp}), hermitian and general matrices are all handled correctly
by \code{operator*} and \code{exp}.
\textbf{Planned extension.} In the future \code{SparseSMat} may be handled as a general matrix class with a member
\code{symmetry_type} (0 for no symmetry, 1 symmetric, 2 hermitian); the conversion from \code{NRSMat}, \code{otimes_sparse},
\code{gemm} (and \code{issymmetric()}) would then be implemented according to it, in a backward compatible way.
\textbf{Reference counting.} The row array is shared by the copy constructor and \code{operator=}; there is no
\code{operator|=}. \code{copyonwrite()} (no arguments) copies all maps (element type copied by its copy
constructor, i.e.\ no nested deep copy). It is called by \code{operator*=}, \code{axpy} (hence \code{+=}, \code{-=}),
\code{operator+=(const T&)}, \code{operator-=(const T&)}, \code{gemm} and \code{storesubmatrix};
\code{resize}, \code{clear}, \code{get(...,false,...)} and \code{operator=(const T&)} detach by reallocation. Not calling it:
\code{add()} (by design: element-level access, like \code{operator[]} of the dense classes and \code{SparseMat::add()};
call \code{copyonwrite()} before adding to a possibly shared matrix), \code{operator[]} (raw row access, no reference count check
even in DEBUG) and \code{simplify()} (removes only near-zero elements, which then disappear from all sharing
copies).
\subsection{Construction, assignment, conversion}
\begin{lareference}
\lafunc{SparseSMat()}{Undefined empty matrix.}
\lafunc{explicit SparseSMat(const SPMatindex n); explicit SparseSMat(const SPMatindex n, const SPMatindex m)}{Empty (zero) $n\times n$ or $n\times m$ matrix.}
\lafunc{SparseSMat(const SparseSMat &rhs)}{Shallow copy.}
\lafunc{explicit SparseSMat(const NRSMat<T> &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<T> &rhs)}{From a dense $n\times m$ matrix, nonzero elements copied as they are (no symmetrization).}
\lafunc{explicit SparseSMat(const SparseMat<T> &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<T> &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$).}
\lafunc{void copyonwrite()}{Make the data private.}
\lafunc{void resize(const SPMatindex nn, const SPMatindex mm)}{Empty matrix of the new size (detaches if shared); \code{resize(0,0)} frees everything and leaves an undefined (empty) matrix.}
\lafunc{void dealloc(); void clear()}{\code{resize(0,0)}; \code{resize(nrows(),ncols())}.}
\lafunc{inline void setcoldim(int i)}{Set the number of columns without touching the data.}
\lafunc{inline int getcount() const}{Reference count.}
\end{lareference}
Dense from sparse: \code{explicit NRMat<T>(const SparseSMat<T>&)} copies all stored elements;
\code{explicit NRSMat<T>(const SparseSMat<T>&)} (square only) takes the lower triangle $i\ge j$ (as stored in \code{NRSMat}; the matrix is assumed hermitian).
\subsection{Element insertion and access}
\begin{lareference}
\lafunc{inline void add(const SPMatindex n, const SPMatindex m, const T elem, const bool both=true)}{Add \code{elem} to element $(n,m)$ and, if \code{both} and $n\ne m$, its complex conjugate to $(m,n)$. Creates the row map if needed; no \code{copyonwrite()}; range check in DEBUG only. Resulting zeros remain until \code{simplify()}.}
\lafunc{inline const std::map<SPMatindex,T> *line(const SPMatindex n) const}{Read-only access to row $n$ (\code{NULL} if empty).}
\lafunc{inline ROWTYPE & operator[](const SPMatindex i)}{Row $i$ as a reference to the pointer \code{std::map<SPMatindex,T>*} (\code{typedef ROWTYPE}), possibly \code{NULL}. Non-const only.}
\lafunc{unsigned long long simplify()}{Remove elements with $|a_{ij}|<$ $\epsilon_{\rm sp}$ and empty rows; returns the number of remaining elements.}
\lafunc{inline unsigned long long length()}{\code{simplify()} (non-const).}
\lafunc{int nrows() const; int ncols() const; inline bool issymmetric() const}{Dimensions; \code{issymmetric()} is always true.}
\lafunc{iterator begin() const; iterator end() const}{Iterator over all stored elements, row by row in increasing column order. \code{*it} is a \code{matel<T>} \emph{copy} held in the iterator (writing to it does not change the matrix). It is meant for comparison with \code{end()} or via \code{it.notend()}; \code{iterator(const SparseSMat&)} starts at the first element.}
\end{lareference}
\subsection{Arithmetic and products}
\begin{lareference}
\lafunc{SparseSMat & operator*=(const T &a)}{Scale (\code{a=0} clears the matrix).}
\lafunc{SparseSMat & operator+=(const T &a); SparseSMat & operator-=(const T &a)}{Add/subtract $a\cdot1$.}
\lafunc{void axpy(const T alpha, const SparseSMat &x, const bool transp=0)}{$A\leftarrow A+\alpha x$, then \code{simplify()}; \code{transp} is ignored.}
\lafunc{inline SparseSMat & operator+=(const SparseSMat &rhs); inline SparseSMat & operator-=(const SparseSMat &rhs)}{\code{axpy(}$\pm1$\code{,rhs)}.}
\lafunc{inline const SparseSMat operator*(const T &rhs) const; inline const SparseSMat operator+(const T &rhs) const; inline const SparseSMat operator-(const T &rhs) const}{Out-of-place scalar operations.}
\lafunc{inline const SparseSMat operator+(const SparseSMat &rhs) const; inline const SparseSMat operator-(const SparseSMat &rhs) const}{Sum, difference.}
\lafunc{void gemm(const T beta, const SparseSMat &a, const char transa, const SparseSMat &b, const char transb, const T alpha)}{$C\leftarrow\beta C+\alpha\,\tilde A\Tr\tilde B$ where $\tilde A=\bar A$ if \code{transa=='c'} and $\tilde B=\bar B$ if \code{transb=='c'} (other values: no conjugation); all matrices square of equal order. For symmetric $A$ this is $\mathrm{op}(A)\,\mathrm{op}(B)$; it is \emph{not} a general product. Rows $k$ of $A$ and $B$ are multiplied as an outer product by BLAS and scattered into the maps; the result is simplified.}
\lafunc{const SparseSMat operator*(const SparseSMat &rhs) const}{General product $AB$ of the stored matrices (hermitian, complex symmetric, nonsymmetric or rectangular): row $i$ of the result is accumulated as $\sum_k A_{ik}\,(\text{row }k\text{ of }B)$; simplified.}
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x) const}{$r\leftarrow\beta r+\alpha Ax$ (general $A$); only \code{trans='n'} is implemented.}
\lafunc{inline const NRVec<T> operator*(const NRVec<T> &rhs) const}{$Ax$.}
\lafunc{typename LA_traits<T>::normtype norm(const T scalar=(T)0) const}{Frobenius norm of $A-\mathtt{scalar}\cdot1$ over all stored elements.}
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide=0, bool cache=false) const}{As for \code{SparseMat} (square matrices only).}
\lafunc{SparseSMat transpose(bool conj=false) const}{Transposed (optionally conjugated) copy.}
\lafunc{void transposeme() const}{Raises an error (not implemented).}
\lafunc{SparseSMat<T> cholesky(void) const}{Pivoted Cholesky decomposition of a positive semidefinite symmetric/hermitian matrix (not checked): returns $R$ with $R\Hc R = A$ (\code{result.transpose(true)*result} reproduces the input). The pivoting is done a priori by sorting the diagonal; pivots with real part below \code{LA_precision<normtype>::cholesky_epsilon()} ($10^{-16}$ for double, the old \code{CHOLESKYEPSILON}, $10^{-8}$ for float) are skipped (rank-deficient case), elements below $\epsilon_{\rm sp}$ are dropped. $R$ is upper triangular only in the permuted basis.}
\lafunc{SparseSMat submatrix(const int fromrow, const int torow, const int fromcol, const int tocol) const}{Block (inclusive bounds).}
\lafunc{void storesubmatrix(const int fromrow, const int fromcol, const SparseSMat &rhs)}{Store \code{rhs} into the block starting at $(\mathtt{fromrow},\mathtt{fromcol})$: the old elements of the block are removed first (as \code{NRMat::storesubmatrix}); calls \code{copyonwrite()}.}
\end{lareference}
\subsection{Input/output and free functions}
\begin{lareference}
\lafunc{void put(int fd, bool dimen=1, bool transp=0, bool orcaformat=false) const}{Binary output: optionally $n$, $m$; then row, column, value of each element; terminated by $(-1,-1)$. With \code{transp} the transposed matrix is written (dimensions and indices swapped), as for \code{SparseMat}.}
\lafunc{void get(int fd, bool dimen=1, bool transp=0, bool orcaformat=false)}{Binary input matching \code{put}; with \code{transp} the transposed matrix is stored. Elements are accumulated by \code{add(...,false)}; with \code{dimen=false} the read matrix replaces the old contents (\code{clear()}), the dimensions are kept.}
\lafunc{std::ostream & operator<<(std::ostream &s, const SparseSMat<T> &x)}{Text output: \code{n m}, lines \code{row col value} (all stored elements), terminator \code{-1 -1}.}
\lafunc{std::istream& operator>>(std::istream &s, SparseSMat<T> &x)}{Text input of the same format (square only); every element is inserted only at its own position (\code{add(i,j,x,false)}), so both triangles must be present in the input.}
\lafunc{SparseSMat<T> otimes_sparse(const NRVec<T> &lhs, const NRVec<T> &rhs, const bool conjugate=false, const T &scale=1)}{\emph{Symmetrized} outer product, symmetric (complex: hermitian) regardless of the input vectors (unlike \code{SparseMat}):
with $M=\mathtt{scale}\cdot l\,r\Tr$ (or $l\,r\Hc$ if \code{conjugate}) the result is $S=M+M\Hc$ ($=l\,r\Tr+r\,l\Tr$ for real types),
including the doubled diagonal $S_{ii}=M_{ii}+M_{ii}^*$. Note that \code{otimes_sparse(x,x,true)} is $2\,x x\Hc$; use \code{scale=0.5} for $x x\Hc$.
Elements with $|S_{ij}|\le$ $\epsilon_{\rm sp}$ are omitted. Both vectors must have the same length (\code{laerror} otherwise).}
\lafunc{SparseSMat<T> NRMat<T>::operator*(const SparseSMat<T> &rhs) const}{Declared in mat.h: dense times sparse, general product $AS$ with a sparse result (row $i$ accumulated as $\sum_k A_{ik}\,(\text{row }k\text{ of }S)$, zero $A_{ik}$ skipped; simplified). Instantiated for \code{double} and \code{std::complex<double>}.}
\end{lareference}
In \ref{ch:nonclass}: \code{complexmatrix()}, \code{realmatrix()} and \code{imagmatrix()} build a
\code{SparseSMat<complex<double>>} (\code{SparseSMat<complex<float>>}) from real ones. The generic \code{exp()} of \code{matexp.h} works with
\code{SparseSMat}.
\section{CSRMat}\label{sec:sparse:csr}
\laheader{csrmat.h, csrmat.cc}
\code{CSRMat<T>} 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<double>} and
\code{std::complex<float>}; 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<SPMatindex> &rowptr, const NRVec<SPMatindex> &col, const NRVec<T> &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<T> &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<T> &rhs); explicit CSRMat(const NRMat<T> &rhs)}{Elements with $|a_{ij}|>\epsilon_{\rm sp}$; the flag is not set (call \code{setsymmetric()} if appropriate).}
\lafunc{explicit CSRMat(const NRSMat<T> &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<SPMatindex> & getrowptr() const; const NRVec<SPMatindex> & getcol() const; const NRVec<T> & 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<T>}), \code{p.notend()}.}
\end{lareference}
Other classes from \code{CSRMat}: \code{explicit NRMat<T>(const CSRMat<T>&)} (full matrix), \code{explicit NRSMat<T>(const CSRMat<T>&)}
(lower triangle, square only, as from \code{SparseSMat}), \code{explicit SparseMat<T>(const CSRMat<T>&)} (not symmetric),
\code{explicit SparseSMat<T>(const CSRMat<T>&)} and \code{explicit NRVec<T>(const CSRMat<T>&)} (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<T>::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<T> &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<T>::normtype sparseepsilon=LA_precision<normtype>::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<T>::operator+=(const CSRMat<T>&)} and \code{operator-=}.
\subsection{Products}
\begin{lareference}
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &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<T>::gemv}. The size of \code{x} and (in DEBUG) of the result are checked, $\beta=0$ clears the result.}
\lafunc{inline const NRVec<T> operator*(const NRVec<T> &rhs) const}{$Ax$.}
\lafunc{const T* diagonalof(NRVec<T> &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<T> operator*(const NRMat<T> &rhs) const}{$AB$ with a dense result, via \code{NRMat<T>::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<T>::gemv(const T beta, const CSRMat<T> &a, const char trans, const T alpha, const NRVec &x)},
\code{NRVec<T>::operator*(const CSRMat<T>&)} ($x\Tr A$),
\code{NRMat<T>::gemm(const T &beta, const CSRMat<T> &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<T>::operator*(const CSRMat<T>&)} (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<int> &p, const bool inverse=false); void permuteme_cols(const NRPerm<int> &p, const bool inverse=false); void permuteme_both(const NRPerm<int> &p, const NRPerm<int> &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<SPMatindex> &p); void permutecolumns(const NRVec<SPMatindex> &p); void permuteindices(const NRVec<SPMatindex> &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<T> &x); std::istream& operator>>(std::istream &s, CSRMat<T> &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<T> commutator(const CSRMat<T> &x, const CSRMat<T> &y, const bool trx=0, const bool tryy=0); inline const CSRMat<T> 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}
#include "la.h"
using namespace LA;
int main()
{
//general sparse matrix as a list of triplets
SparseMat<double> a(4,4);
a.add(0,0,2.); a.add(1,2,1.); a.add(3,0,-1.);
a.add(1,2,0.5); //duplicate position: values are summed
a.simplify(); //merge duplicates, drop zeros
NRVec<double> x(1.,4);
NRVec<double> y = a*x; //sparse * dense vector
NRMat<double> d(a); //dense copy
for(SparseMat<double>::iterator p=a.begin(); p!=a.end(); ++p)
std::cout << p->row <<" "<< p->col <<" "<< p->elem <<"\n";
//symmetric storage: only one triangle is stored
SparseMat<double> s(4,4);
s.setsymmetric();
s.add(2,0,1.5); s.add(3,3,1.);
NRVec<double> z = s*x; //uses s(2,0) and s(0,2)
NRSMat<double> sd(s); //dense packed copy
//shared matrices: detach before add()
SparseMat<double> b(a); //shares the list with a
b.copyonwrite();
b.add(2,2,3.);
//map-based sparse matrix
SparseSMat<double> h(4);
h.add(0,1,0.5); //adds (0,1) and (1,0)
h.add(2,2,1.);
SparseSMat<double> h2 = h*h; //result stays symmetric
NRVec<double> w = h*x;
std::cout << y << z << w << NRMat<double>(h2) << sd;
//compressed row format: assembled as SparseMat, then converted
SparseMat<double> 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<double> hc(hb);
hc.setsymmetric(); //declare it symmetric, e.g. for davidson
NRVec<double> u = hc*x;
CSRMat<std::complex<double> > ev = exp(imagmatrix(hc)); //exp(iH), sparse
CSRMat<double> hc2 = hc*2.; //shares rowptr and col with hc
}
\end{lstlisting}
\section{Pitfalls}\label{sec:sparse:pitfalls}
\begin{itemize}
\item \code{add()} of both classes does not call \code{copyonwrite()}; call it yourself on a matrix that may be shared.
\item \code{SparseMat} may contain duplicates; call \code{simplify()} before relying on the list contents
(iteration, \code{NRSMat} conversion, \code{diagonalof} for rectangular matrices).
\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}