LaTeX manual

This commit is contained in:
jiri committed 2026-10-30 22:15:26 +01:00
1 parent 176562f088
commit 648c8d1061
30 files changed
+6090

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
%bibliography items of the chapter Iterative methods (iterative.tex), cited in the comments of davidson.h and diis.h
\bibitem{iterative:davidson1975} E. R. Davidson, J. Comput. Phys. \textbf{17}, 87 (1975).
\bibitem{iterative:pulay1980} P. Pulay, Chem. Phys. Lett. \textbf{73}, 393 (1980).
\bibitem{iterative:pulay1982} P. Pulay, J. Comput. Chem. \textbf{3}, 556 (1982).
+33
View File
@@ -0,0 +1,33 @@
%bibliography items of the chapter Optimization (optimize.tex), cited in the comments of optimize.h and optimize.cc
\bibitem{optimize:nocedal2006} J. Nocedal, S. J. Wright, \emph{Numerical Optimization}, 2nd ed., Springer, New York 2006.
\bibitem{optimize:conn2000} A. R. Conn, N. I. M. Gould, Ph. L. Toint, \emph{Trust-Region Methods}, SIAM, Philadelphia 2000.
\bibitem{optimize:brent1973} R. P. Brent, \emph{Algorithms for Minimization without Derivatives}, Prentice-Hall, Englewood Cliffs 1973.
\bibitem{optimize:csaszar1984} P. Cs\'asz\'ar, P. Pulay, J. Mol. Struct. \textbf{114}, 31 (1984).
\bibitem{optimize:farkas2002} \"O. Farkas, H. B. Schlegel, Phys. Chem. Chem. Phys. \textbf{4}, 11 (2002).
\bibitem{optimize:hager2005} W. W. Hager, H. Zhang, SIAM J. Optim. \textbf{16}, 170 (2005).
\bibitem{optimize:bofill1994} J. M. Bofill, J. Comput. Chem. \textbf{15}, 1 (1994).
\bibitem{optimize:more1983} J. J. Mor\'e, D. C. Sorensen, SIAM J. Sci. Stat. Comput. \textbf{4}, 553 (1983).
\bibitem{optimize:steihaug1983} T. Steihaug, SIAM J. Numer. Anal. \textbf{20}, 626 (1983).
\bibitem{optimize:banerjee1985} A. Banerjee, N. Adams, J. Simons, R. Shepard, J. Phys. Chem. \textbf{89}, 52 (1985).
\bibitem{optimize:baker1986} J. Baker, J. Comput. Chem. \textbf{7}, 385 (1986).
\bibitem{optimize:madsen2004} K. Madsen, H. B. Nielsen, O. Tingleff, \emph{Methods for Non-Linear Least Squares Problems}, 2nd ed., IMM, Technical University of Denmark 2004.
\bibitem{optimize:lagarias1998} J. C. Lagarias, J. A. Reeds, M. H. Wright, P. E. Wright, SIAM J. Optim. \textbf{9}, 112 (1998).
\bibitem{optimize:gao2012} F. Gao, L. Han, Comput. Optim. Appl. \textbf{51}, 259 (2012).
\bibitem{optimize:powell1964} M. J. D. Powell, Comput. J. \textbf{7}, 155 (1964).
\bibitem{optimize:powell1994} M. J. D. Powell, in \emph{Advances in Optimization and Numerical Analysis}, eds. S. Gomez, J.-P. Hennart, Kluwer, Dordrecht 1994, p.~51.
\bibitem{optimize:powell1998} M. J. D. Powell, Acta Numerica \textbf{7}, 287 (1998).
\bibitem{optimize:powell2004} M. J. D. Powell, \emph{The NEWUOA software for unconstrained optimization without derivatives}, Report DAMTP 2004/NA08, University of Cambridge 2004.
\bibitem{optimize:ostaszewski2021} M. Ostaszewski, E. Grant, M. Benedetti, Quantum \textbf{5}, 391 (2021).
\bibitem{optimize:nakanishi2020} K. M. Nakanishi, K. Fujii, S. Todo, Phys. Rev. Research \textbf{2}, 043158 (2020).
\bibitem{optimize:wierichs2022} D. Wierichs, J. Izaac, C. Wang, C. Y.-Y. Lin, Quantum \textbf{6}, 677 (2022).
\bibitem{optimize:mitarai2018} K. Mitarai, M. Negoro, M. Kitagawa, K. Fujii, Phys. Rev. A \textbf{98}, 032309 (2018).
\bibitem{optimize:schuld2019} M. Schuld, V. Bergholm, C. Gogolin, J. Izaac, N. Killoran, Phys. Rev. A \textbf{99}, 032331 (2019).
\bibitem{optimize:stokes2020} J. Stokes, J. Izaac, N. Killoran, G. Carleo, Quantum \textbf{4}, 269 (2020).
\bibitem{optimize:amari1998} S. Amari, Neural Comput. \textbf{10}, 251 (1998).
\bibitem{optimize:sutskever2013} I. Sutskever, J. Martens, G. Dahl, G. Hinton, in \emph{Proceedings of the 30th International Conference on Machine Learning (ICML)}, 2013.
\bibitem{optimize:kingma2015} D. P. Kingma, J. Ba, in \emph{International Conference on Learning Representations (ICLR)}, 2015.
\bibitem{optimize:reddi2018} S. J. Reddi, S. Kale, S. Kumar, in \emph{International Conference on Learning Representations (ICLR)}, 2018.
\bibitem{optimize:loshchilov2019} I. Loshchilov, F. Hutter, in \emph{International Conference on Learning Representations (ICLR)}, 2019.
\bibitem{optimize:spall1992} J. C. Spall, IEEE Trans. Autom. Control \textbf{37}, 332 (1992).
\bibitem{optimize:spall1998} J. C. Spall, IEEE Trans. Aerosp. Electron. Syst. \textbf{34}, 817 (1998).
\bibitem{optimize:mueller1979} K. M\"uller, L. D. Brown, Theor. Chim. Acta \textbf{53}, 75 (1979).
+3
View File
@@ -0,0 +1,3 @@
\bibitem{permutation:knuth4a} D.~E.~Knuth, \emph{The Art of Computer Programming, Vol.~4A: Combinatorial Algorithms, Part~1}, Addison-Wesley, 2011 (section 7.2.1.2, Generating all permutations).
\bibitem{permutation:hamermesh} M.~Hamermesh, \emph{Group Theory and Its Application to Physical Problems}, Addison-Wesley, 1962.
\bibitem{permutation:aigner} M.~Aigner, \emph{Combinatorial Theory}, Springer, 1979.
+1
View File
@@ -0,0 +1 @@
\bibitem{polynomial:hakmem} M.~Beeler, R.~W.~Gosper, R.~Schroeppel, \emph{HAKMEM}, MIT AI Memo 239, 1972 (item 101, continued fraction arithmetic).
+4
View File
@@ -0,0 +1,4 @@
\bibitem{quaternion:marsaglia1972}
G.~Marsaglia,
\newblock Choosing a point from the surface of a sphere,
\newblock Ann. Math. Stat. 43, 645 (1972).
+4
View File
@@ -0,0 +1,4 @@
\bibitem{utilities:boppana1992}
R.~Boppana, M.~M.~Halld\'orsson,
\newblock Approximating maximum independent sets by excluding subgraphs,
\newblock BIT 32, 180 (1992).
+4
View File
@@ -0,0 +1,4 @@
\bibitem{vecmat3:mcadams2011}
A.~McAdams, A.~Selle, R.~Tamstorf, J.~Teran, E.~Sifakis,
\newblock Computing the singular value decomposition of $3\times3$ matrices with minimal branching and elementary floating point operations,
\newblock Technical Report 1690, University of Wisconsin--Madison, 2011.
+21
View File
@@ -0,0 +1,21 @@
%bibliography; the items of each chapter are kept in bib/<chapter>.tex
\begin{thebibliography}{99}
\addcontentsline{toc}{chapter}{Bibliography}
\InputIfFileExists{bib/concepts}{}{}
\InputIfFileExists{bib/vec}{}{}
\InputIfFileExists{bib/mat}{}{}
\InputIfFileExists{bib/smat}{}{}
\InputIfFileExists{bib/sparse}{}{}
\InputIfFileExists{bib/nonclass}{}{}
\InputIfFileExists{bib/iterative}{}{}
\InputIfFileExists{bib/optimize}{}{}
\InputIfFileExists{bib/tensor}{}{}
\InputIfFileExists{bib/fourindex}{}{}
\InputIfFileExists{bib/permutation}{}{}
\InputIfFileExists{bib/polynomial}{}{}
\InputIfFileExists{bib/vecmat3}{}{}
\InputIfFileExists{bib/quaternion}{}{}
\InputIfFileExists{bib/bitvector}{}{}
\InputIfFileExists{bib/regression}{}{}
\InputIfFileExists{bib/utilities}{}{}
\end{thebibliography}
+123
View File
@@ -0,0 +1,123 @@
\chapter{Bit vectors and polynomials over GF(2)}\label{ch:bitvector}
\laheader{bitvector.h, bitvector.cc}
Compressed bit vectors, which also represent polynomials over GF(2) and elements of GF($2^n$). \code{bitvector} is a reference-counted class derived from \code{NRVec} (see section~\ref{sec:concepts:refcount}).
\section{Purpose and storage}
\code{bitvector} is a compressed vector of bits stored in 64-bit blocks
(\code{typedef uint64_t bitvector_block}, macro \code{blockbits}=64). It is publicly derived from
\code{NRVec<bitvector_block>}, so it shares its reference counting and copy-on-demand
behaviour; the number of blocks is \code{NRVec::size()} of the base, the number of bits is
the (hiding) \code{bitvector::size()}. Bit $i$ is bit $i \bmod 64$ of block $\lfloor i/64\rfloor$
(least significant first). The private member \code{modulo} $=n \bmod 64$ remembers the
bit length; the unused high bits of the last block (``padding'') may contain garbage and are
cleared by \code{zero_padding()} where needed. \code{bitvector_rounded(n)} rounds $n$ up to a
multiple of 64.
Besides a plain bit set, a \code{bitvector} is interpreted as a polynomial over GF(2): bit $i$ is
the coefficient of $x^i$. Addition and subtraction are XOR, multiplication and division are
polynomial operations, and with an irreducible polynomial the field GF($2^n$) is available.
A comment in the header plans a \code{monic_bitvector} class (not storing the leading 1, so that
field operations need no resizing); it is \emph{not implemented}.
\section{Copy on demand}
Copy constructor and \code{operator=} share the data (\code{operator=} is compiler-generated,
calling \code{NRVec::operator=}), \code{|=} of the base class makes a deep copy (but beware it is
overloaded as bitwise OR here, see Pitfalls). The following members call \code{copyonwrite()}:
\code{clear}, \code{fill}, \code{randomize}, \code{operator++}, \code{operator--}, \code{&=}, \code{|=}, \code{^=}
(with a bitvector argument), \code{>>=}, \code{leftshift}/\code{<<=}; \code{resize} detaches.
The single-bit and block setters do \emph{not}: \code{set}, \code{reset}, \code{flip}, \code{assign},
\code{setblock}, and \code{&=}, \code{|=}, \code{^=} with a \code{bitvector_block} argument (also the
\code{bitmatrix} and \code{bitvector_from1} wrappers of \code{set}/\code{reset}/\code{flip}/\code{assign}).
They are element-level access by design (used in inner loops) and behave like writes through \code{operator[]}:
call \code{x.copyonwrite()} once before using them on a possibly shared vector. In DEBUG builds they raise \code{laerror}
when the data are shared (count check as in \code{NRVec::operator[]}).
\section{Reference: construction, access}
\begin{lareference}
\lafunc{bitvector(); explicit bitvector(const unsigned int n)}{Empty; $n$ bits, cleared.}
\lafunc{bitvector(const bitvector_block a, const unsigned int n)}{$n$ bits, every \emph{block} set to \code{a}.}
\lafunc{bitvector(const bitvector &rhs)}{Shallow copy.}
\lafunc{explicit bitvector(const uint8_t *data, const unsigned int n); void getdata(uint8_t *data)}{Import/export $n$ bits from/to a byte array (\code{(n+7)/8} bytes, bit $i$ in byte \code{i/8}); little-endian machines only (laerror otherwise).}
\lafunc{void resize(const unsigned int n, bool preserve=false)}{New bit length; with \code{preserve} keep the old bits and zero the new ones, otherwise clear everything.}
\lafunc{unsigned int size() const}{Number of bits.}
\lafunc{const bool operator[](const unsigned int i) const; const bool get(const unsigned int i) const}{Read bit $i$ (no assignable non-const \code{[]} exists).}
\lafunc{bitvector_block getblock(const unsigned int i) const; void setblock(const unsigned int i, const bitvector_block b); int getblocksize() const}{Raw block access, block size in bits (64).}
\lafunc{void set(const unsigned int i); void reset(const unsigned int i); void flip(const unsigned int i); const bool assign(const unsigned int i, const bool r)}{Set to 1, to 0, negate, assign bit $i$ (range checked in DEBUG; no copyonwrite, see above).}
\lafunc{void clear(); void fill()}{All bits 0 / 1.}
\lafunc{void zero_padding() const}{Clear the unused bits of the last block (logically const).}
\lafunc{bool is_zero() const; bool iszero() const; bool is_one() const}{Test for the zero vector / the polynomial 1.}
\lafunc{void randomize()}{Random bits via \code{RANDINT64} (chapter~\ref{ch:utilities}).}
\lafunc{void read(int fd, bool dimensions=1, bool transp=0); void write(int fd, bool dimensions=1, bool transp=0)}{Binary I/O on a file descriptor: \code{modulo} followed by \code{NRVec::get}/\code{put} (section~\ref{sec:concepts:io}).}
\end{lareference}
\section{Reference: bitwise and integer operations}
\begin{lareference}
\lafunc{bitvector &operator++(); bitvector &operator--()}{Increment/decrement as a multi-word binary integer (bit 0 least significant), wrapping around.}
\lafunc{bool operator==(const bitvector &) const; bool operator!=(const bitvector &) const}{Equality; vectors of different length are equal iff the extra bits of the longer one are zero.}
\lafunc{bool operator<(const bitvector &) const; operator>, operator<=, operator>=}{Comparison as unsigned integers (bit $i$ has the weight $2^i$, the same reading as \code{getblock} and \code{++}), only for equal lengths (laerror otherwise).}
\lafunc{bitvector operator~() const}{Bitwise negation.}
\lafunc{bitvector &operator&=(const bitvector &); operator|=, operator^=; operator&, operator|, operator^}{Bitwise operations; \code{*this} is enlarged (zero-filled) if \code{rhs} is longer.}
\lafunc{bitvector &operator&=(const bitvector_block rhs); operator|=, operator^=}{Bitwise operation with the \emph{first block} only.}
\lafunc{bitvector &operator>>=(unsigned int i); bitvector operator>>(unsigned int i) const}{Logical right shift (towards bit 0), length kept.}
\lafunc{bitvector &leftshift(unsigned int i, bool autoresize=false); bitvector &operator<<=(unsigned int i); bitvector operator<<(unsigned int i) const}{Logical left shift; \code{<<} and \code{<<=} enlarge the vector by $i$ bits (autoresize), \code{leftshift} by default keeps the length and drops the top bits.}
\lafunc{unsigned int population(const unsigned int before=0) const}{Hamming weight (number of ones); \code{before}$\neq0$ is not implemented (laerror).}
\lafunc{unsigned int bitdiff(const bitvector &y) const}{Hamming distance (same number of blocks required).}
\lafunc{unsigned int nlz() const; unsigned int ntz() const}{Number of leading (from the top bit) and trailing zeros.}
\end{lareference}
\section{Reference: polynomials over GF(2) and GF($2^n$)}
\begin{lareference}
\lafunc{bitvector operator+(const bitvector &) const; operator-, operator+=, operator-=}{Addition = subtraction = XOR.}
\lafunc{bitvector multiply(const bitvector &rhs, bool autoresize=true) const; bitvector operator*(const bitvector &rhs) const; bitvector &operator*=(const bitvector &rhs)}{Polynomial product (naive shift-and-add, no carry-less multiply instructions; for short vectors). With \code{autoresize} the result has \code{size()+rhs.size()} bits, otherwise \code{max} of the sizes (the caller guarantees no overflow).}
\lafunc{bitvector pow(unsigned int n) const}{$p^n$ by repeated squaring.}
\lafunc{bitvector division(const bitvector &rhs, bitvector &remainder) const; bitvector operator/(const bitvector &) const}{Polynomial division with remainder (\texttt{operator\%} returns the remainder); the remainder is resized to \code{rhs.size()}. Division by zero: laerror.}
\lafunc{bitvector gcd(const bitvector &rhs) const; bitvector lcm(const bitvector &rhs) const}{Greatest common divisor (Euclid) and least common multiple.}
\lafunc{bitvector composition(const bitvector &rhs) const}{$p(r(x))$ by the Horner scheme.}
\lafunc{unsigned int degree() const}{Polynomial degree (0 for the zero polynomial).}
\lafunc{void truncate(int t=0)}{Resize to \code{max(degree()+1, t)} bits, preserving data.}
\lafunc{bool is_irreducible() const}{Irreducibility test over GF(2) (Rabin-type test: $x^{2^d}\equiv x$ and $\gcd(x^{2^{d/p}}-x, f)=1$ for prime divisors $p$ of the degree $d$, cf.\ Brent--Zimmermann; uses \code{factorization} of section~\ref{sec:utilities:numbers}).}
\lafunc{bitvector field_mult(const bitvector &rhs, const bitvector &irpolynom) const}{Product in GF($2^n$)$\,=\,$GF(2)[x]/(irpolynom), $n=\deg(\mathrm{irpolynom})$; the operands must have at least $n$ bits.}
\lafunc{bitvector field_inv(const bitvector &irpolynom) const; bitvector field_div(const bitvector &rhs, const bitvector &irpolynom) const}{Multiplicative inverse (extended Euclid; laerror if the polynomial is reducible or the input is 0 modulo it) and division in GF($2^n$).}
\lafunc{bitvector field_pow(unsigned int n, const bitvector &irpolynom) const; bitvector field_sqrt(const bitvector &irpolynom) const}{Power and square root ($\sqrt a=a^{2^{n-1}}$) in GF($2^n$).}
\lafunc{bitvector field_composition(const bitvector &rhs, const bitvector &irpolynom) const}{$p(r)$ evaluated in GF($2^n$).}
\lafunc{bitvector find_irreducible(int deg, int pop=-1, int nth=1)}{Free function: an irreducible polynomial of degree \code{deg}. With \code{pop}$=-1$ random trials; otherwise the \code{nth} one (in the order of a recursive search) with Hamming weight \code{pop} (odd, $\ge3$); returns a zero vector if none was found.}
\end{lareference}
\section{bitmatrix, bitvector\_from1 and helper functions}
\code{bitmatrix} (derived from \code{bitvector}) stores an $n\times m$ bit matrix row-wise
(bit \code{i*m+j}). \code{bitvector_from1} is a \code{bitvector} indexed from 1.
\begin{lareference}
\lafunc{bitmatrix(); bitmatrix(unsigned int nn0, unsigned int mm0); void resize(const unsigned int n, const unsigned int m)}{Construct/resize (data cleared).}
\lafunc{unsigned int nrows() const; unsigned int ncols() const}{Dimensions.}
\lafunc{const bool operator()(const unsigned int i, const unsigned int j) const}{Read element.}
\lafunc{void set(i,j); void reset(i,j); void flip(i,j); const bool assign(i,j,const bool r)}{Write element (no copyonwrite).}
\lafunc{bitvector_from1(); bitvector_from1(const bitvector &rhs); explicit bitvector_from1(const unsigned int n)}{Constructors.}
\lafunc{operator[](i); get(i); set(i); reset(i); assign(i,r); population(before)}{As in \code{bitvector}, with indices shifted by one. (Note \code{operator[]} and \code{get} are non-const here.)}
\lafunc{void bitvector_expand(const bitvector &v, NRVec<T> &r); void bitvector_compress(bitvector &r, const NRVec<T> &v)}{Convert to/from a vector of 0/1 values of type \code{T}.}
\lafunc{bitvector mantissa(T x, int nbits, int shift=0)}{Binary expansion of $x\,2^{\mathrm{shift}}\in[0,1)$ (laerror otherwise): bit $i$ ($1\le i<\mathrm{nbits}$) is the coefficient of $2^{-i}$.}
\lafunc{void bitvector_decimal(T &x, const bitvector &b, int shift=0)}{Inverse of \code{mantissa}: $x=2^{\mathrm{shift}}\sum_i b_i 2^{-i}$.}
\lafunc{std::ostream &operator<<(std::ostream &, const bitvector &); std::istream &operator>>(std::istream &, bitvector &)}{Text I/O as a string of 0/1 characters, bit 0 first, no length (input: one whitespace-delimited token, any character other than \code{'0'} is 1).}
\lafunc{std::ostream &operator<<(std::ostream &, const bitmatrix &); std::istream &operator>>(std::istream &, bitmatrix &)}{Dimensions, then rows of 0/1 (input: whitespace-separated integers).}
\end{lareference}
\code{LA_traits<bitvector>} declares \code{elementtype} and \code{normtype} \code{bool}.
\section{Example}
\begin{lstlisting}
int d=13;
bitvector ir=find_irreducible(d,3); //trinomial, if any
if(ir.is_zero()) ir=find_irreducible(d,5);
bitvector a(d);
do a.randomize(); while(a.is_zero());
bitvector ai = a.field_inv(ir);
if(!a.field_mult(ai,ir).is_one()) laerror("GF(2^n) inversion failed");
bitvector s = a.field_sqrt(ir); //s*s == a in GF(2^13)
bitvector u(100),v(100);
u.randomize(); v.randomize();
bitvector w=u*v; //polynomial product, 200 bits
if(!(w%u).is_zero() || !(w/u-v).is_zero()) laerror("division");
\end{lstlisting}
\section{Pitfalls}
\lawarning{\code{|=} is bitwise OR here, not the deep copy of the base class \code{NRVec}; to get an independent copy use \code{bitvector b(a); b.copyonwrite();}.}
\lawarning{Bits beyond \code{size()} in the last block may be nonzero after \code{fill()}, \code{setblock()} or the block constructor; most predicates call \code{zero_padding()}, but use it explicitly before raw block access.}
+121
View File
@@ -0,0 +1,121 @@
\chapter{Building and using the library}\label{ch:building}
\section{Requirements}
\begin{itemize}
\item a C++11 capable compiler (the library is developed with GNU g++),
\item BLAS and LAPACK (Fortran interface); optionally the C interfaces CBLAS and CLAPACK,
or Intel MKL, or ATLAS,
\item GNU autotools (autoconf, automake, libtool) when building from the git repository,
\item optionally NVIDIA CUBLAS (experimental, section~\ref{sec:concepts:gpu}),
\item optionally doxygen for the generated source code reference, which is independent of this manual.
\end{itemize}
\section{Configuration and installation}
\begin{lstlisting}[language=bash]
autoreconf --install # only if ./configure does not exist yet
./configure [options] CXXFLAGS=-I<path> LDFLAGS=-L<path>
make # library, and this manual and the doxygen pages if pdflatex/doxygen were found
make check # builds the test programs, runs testoptimize
make install
\end{lstlisting}
\code{CXXFLAGS} and \code{LDFLAGS} should point to the preferred BLAS/LAPACK installation; after
running \code{configure}, check in the generated \header{Makefile} that the intended library was found.
\code{./configure --disable-optimize CXXFLAGS='' LDFLAGS=''} gives a fast compilation for development.
\code{configure} looks for the libraries in the following order and sets the preprocessor macros
accordingly:
\begin{enumerate}
\item Intel MKL (\code{mkl_intel_lp64}, \code{mkl_sequential}, \code{mkl_core} and the header
\header{mkl_cblas.h}); if found, \code{-DHAS_MKL -DNONCLAPACK} is used;
\item otherwise ATLAS (optional) and the reference BLAS (mandatory), CBLAS (if \code{cblas_ddot} and \header{cblas.h}
are not found, \code{-DNONCBLAS} is set) and LAPACK (mandatory; if the C interface \code{clapack_dgesv}
with \header{clapack.h} is not found, \code{-DNONCLAPACK} is set);
\item CUBLAS: if \code{cublasInit} and \header{cublas.h} are found, the GPU support is switched on
(\code{-DCUDALA}) and the C interfaces of BLAS and LAPACK are not used.
\end{enumerate}
\subsection{Configure options}
\begin{longtable}{@{}p{0.36\linewidth}p{0.6\linewidth}@{}}
\toprule
\textbf{Option} & \textbf{Effect} \\
\midrule
\endhead
\code{--disable-debug} & do not define \code{DEBUG}; by default range checks and other consistency checks are compiled in (section~\ref{sec:concepts:errors}) \\
\code{--enable-optimize=<flags>} & compiler optimization flags, default \code{-O3 -finline-limit=1000}; \code{--disable-optimize} for none \\
\code{--enable-profile} & compile with \code{-pg} \\
\code{--enable-matptr} & define \code{MATPTR}: \code{NRMat} stores an array of row pointers (\code{T**}), see chapter~\ref{ch:mat}; incompatible with CUDA \\
\code{--enable-fortran64int} & define \code{LONG_FORTRAN_INT} to link with BLAS and LAPACK using 64-bit integers; implies \code{NONCBLAS} and \code{NONCLAPACK} (the old misspelling \code{--enable-fotran64int} is accepted as an alias) \\
\bottomrule
\end{longtable}
\subsection{Preprocessor macros}
The macros below are set by \code{configure} in the \header{Makefile}. Since most of the library consists of
templates compiled in the user's code, \emph{a program using the library must be compiled with the same
macros} (at least \code{DEBUG}, \code{MATPTR}, \code{CUDALA}, \code{NONCBLAS}, \code{NONCLAPACK},
\code{HAS_MKL}, \code{LONG_FORTRAN_INT}, \code{FORTRAN_}), otherwise the object layouts or the called
functions do not match.
\begin{longtable}{@{}p{0.25\linewidth}p{0.71\linewidth}@{}}
\toprule
\textbf{Macro} & \textbf{Meaning} \\
\midrule
\endhead
\code{DEBUG} & range checks of indices, dimension checks, the check of the reference count in non-const element access \\
\code{NONCBLAS} & no CBLAS library: the \code{cblas_*} functions used by the library are provided by the wrappers in \header{noncblas.cc}, which call the Fortran BLAS \\
\code{NONCLAPACK} & the same for the C interface of LAPACK (the library calls the Fortran LAPACK routines) \\
\code{HAS_MKL} & use the headers of Intel MKL \\
\code{FORTRAN_} & Fortran routine names have a trailing underscore (always set in \header{Makefile.am}) \\
\code{LONG_FORTRAN_INT} & Fortran integers are 64-bit (\code{long}) \\
\code{MATPTR} & row pointer representation of \code{NRMat} \\
\code{CUDALA} & GPU support via CUBLAS (experimental) \\
\code{NO_STRASSEN} & do not compile the experimental Strassen matrix multiplication interface (\header{strassen.cc}, needs an external library); always set in \header{Makefile.am} \\
\bottomrule
\end{longtable}
The headers \header{fortran.h}, \header{noncblas.h}, \header{cuda_la.h}, \header{config.h} and
\header{LA_version.h} are internal to the library and are not documented in this manual.
\section{Compiling and linking user programs}
Include \header{la.h} (or the individual headers) and link with \code{-lla}, followed by the
libraries found by \code{configure}, e.g.
\begin{lstlisting}[language=bash]
g++ -O3 -DDEBUG -DFORTRAN_ -DNONCLAPACK -I$PREFIX/include prog.cc \
-L$PREFIX/lib -lla -llapack -lcblas -lblas
\end{lstlisting}
The variables \code{AM_CXXFLAGS} and \code{AM_LDFLAGS} in the generated \header{Makefile} show the exact
options used for the library itself.
The string \code{LA::version} (declared in \header{LA_version.h}) contains the git version, the commit date
and the time and place of compilation of the library; it is generated by the script \header{get_git_version}
at every \code{make}.
\section{Test programs}
\code{make check} builds the programs
\begin{description}
\item[\code{t}] a large collection of tests and usage examples of nearly all parts of the library
(\header{t.cc}, \header{t2.cc}); the individual tests are switched on and off by \code{if(0)} / \code{if(1)}
blocks in the source, many of them read their input from the standard input;
\item[\code{test}] further small tests (\header{test.cc});
\item[\code{tX}] a test with X11 graphics (\header{tX.cc});
\item[\code{test_reg}, \code{test_regsurf}] tests of the regression module;
\item[\code{testoptimize}] the regression test of the optimization module (chapter~\ref{ch:optimize}); it
prints PASS/FAIL lines and its exit code is the number of failed checks.
\end{description}
\code{make check} builds all these programs and runs \code{testoptimize} (automake \code{TESTS}); its result is in
\header{test-suite.log}.
\section{Documentation}
This manual is in the subdirectory \header{doc}. \code{make manual} (alias \code{make doc}) compiles it by running
\code{pdflatex} three times in the scratch directory \header{doc/.build} and copies the result to \header{doc/manual.pdf}; this
is done only when a \code{.tex} file is newer than the PDF, and a \LaTeX{} error stops the build with the end of the log.
A source code reference is generated by doxygen with the configuration file \header{doxygen.cfg} by \code{make doxygen}
(output in \header{doxygen-doc}, rebuilt when a source file changes); it is independent of this manual.
\code{make} (i.e.\ \code{make all}) builds both in addition to the library, each only if \code{configure} found
\code{pdflatex} resp.\ \code{doxygen}; \code{make clean} removes \header{doc/.build} and \header{doxygen-doc} (not the PDF).
+278
View File
@@ -0,0 +1,278 @@
\chapter{Common concepts}\label{ch:concepts}
This chapter describes the design common to all classes of the library. The class chapters refer to
it and describe only what is specific to the class.
\section{Reference counting and copy on demand}\label{sec:concepts:refcount}
\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
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
same data:
\begin{lstlisting}
NRVec<double> a(1.,1000); //1000 elements equal to 1
NRVec<double> b(a); //no copy of the data, a.getcount()==2
NRVec<double> c;
c = a; //no copy, count is 3 now
\end{lstlisting}
The data are deallocated when the last object referring to them is destroyed or assigned a different
value. The current value of the counter is returned by \code{getcount()}.
Consequently, objects can be passed to and returned from functions by value at low cost, and expressions
like \code{x = A*x} are safe, since the result is computed into a new temporary object which is then
assigned to \code{x}.
\subsection{Copy on demand}
Before shared data are modified, the object must obtain its own copy of them. This is done by the member
function
\begin{lareference}
\lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{If the reference count is larger than one,
allocate a private copy of the data (copy them unless \code{detachonly}, which only allocates
uninitialized memory) and decrement the count of the shared block. For nested element types (see
section~\ref{sec:concepts:traits}) it is applied recursively to the elements when \code{deep} is true, also when the
outer object is not shared. For an object with count one it does nothing (apart from the recursion).}
\end{lareference}
The rules are:
\begin{enumerate}
\item \emph{Every member function and operator of the library which modifies the data of an object as a whole calls
\code{copyonwrite()} itself} (\code{axpy}, \code{gemv}, \code{gemm}, \code{+=}, \code{*=}, assignment of a
scalar, \code{randomize}, \code{storesubmatrix}, \code{resize}, \dots). Explicit calls before these
functions are not needed.
\item \emph{Before writing to individual elements through \code{operator[]}, \code{operator()}, a raw pointer}
(\code{operator T*()}, \code{&x[0]}, \dots) \emph{or a member function accessing individual elements} (such as
\code{swap(i,j)} of \code{NRVec}, used in sorting loops) the user has to call \code{copyonwrite()} explicitly.
In the first cases the library cannot distinguish a write access from a read access; element-level members do not call it
by design, since a \code{copyonwrite()} inside inner loops over individual elements would be an unacceptable overhead:
\begin{lstlisting}
NRVec<double> b(a); //b shares data with a
b.copyonwrite(); //b gets its own copy
b[0] = 5.; //a is not changed
\end{lstlisting}
Without the \code{copyonwrite()}, the assignment would silently change \code{a} as well.
The rule applies to every object which might share its data: objects obtained by copying or assignment,
objects received as function arguments by reference or pointer (they may share data with other copies in
the caller), objects stored in other containers. A freshly constructed object (by a constructor taking
dimensions) has count one, but calling \code{copyonwrite()} for it costs nothing.
\item \code{resize()} detaches an object from shared data: a shared object always gets newly allocated
memory (with the old contents only if their preservation is requested), even when the size does not change.
An object which has just been resized can be written to. \code{resize(0)} deallocates.
\end{enumerate}
\subsection{The DEBUG check of the reference count}
In programs compiled with \code{DEBUG}, the non-const versions of \code{operator[]} and \code{operator()}
of the basic classes call \code{laerror} (section~\ref{sec:concepts:errors}) when the reference count is larger
than one, since this is a possible write to shared data:
\begin{lstlisting}
NRVec<double> c = a;
c[1] = 7.; //DEBUG: LA:ERROR - possible use of NRVec[] with count>1 as l-value
\end{lstlisting}
The check cannot distinguish reading from writing. Reading elements of a possibly shared object should
therefore be done through a const reference, which selects the const version of the operator:
\begin{lstlisting}
void f(NRVec<double> &x) //non-const, x may share data with another vector
{
const NRVec<double> &xc = x;
double s = xc[0] + xc[1]; //no false alarm
x.copyonwrite();
x[0] = s; //write
}
\end{lstlisting}
The check can be switched off at run time by setting the global variable \code{LA::_LA_count_check=false}.
It is a safety net only: writes through raw pointers and writes in programs compiled without \code{DEBUG}
are not checked.
\lawarning{A missing \code{copyonwrite()} does not cause a crash, it silently modifies other objects which share
the data. It is the most frequent error when using the library. A library member function which modifies
data without calling \code{copyonwrite()} is a bug of the library.}
\subsection{Objects referring to external memory}
The constructor \code{NRVec(T *a, int n, bool skeleton)} with \code{skeleton=true} (and analogous
constructors of other classes, see the class chapters) creates an object which refers to memory owned by
somebody else, without copying. Its reference count is set to two, so that the memory is never deallocated by
the library and any modification by a library function first makes a private copy (writes through
\code{operator[]} go to the external memory, but are reported by the DEBUG check). It is useful for applying
library functions to data stored elsewhere. Such an object is intended as a \emph{read-only view} of the external data.
\section{Shallow and deep copy}\label{sec:concepts:assign}
\begin{lareference}
\lafunc{X(const X &rhs); X &operator=(const X &rhs)}{shallow copy: share the data with \code{rhs}
(section~\ref{sec:concepts:refcount}).}
\lafunc{X &operator|=(const X &rhs)}{deep copy: \code{*this = rhs} followed by \code{copyonwrite()}; this object gets its own copy
of the data, recursively for nested types.}
\lafunc{X &operator=(const T &a)}{assignment of a scalar. For vectors every element is set to \code{a}; for square matrices
(\code{NRMat}, \code{NRSMat}) the matrix is set to $a\,\mathbf{1}$, i.e.\ the diagonal is set to \code{a} and the
off-diagonal elements to zero. In the same spirit \code{+=} and \code{-=} with a scalar add to the diagonal of a matrix.}
\end{lareference}
The deep copy is needed when the copy will be modified by element access, or when a function
destroys its argument (e.g.\ \code{diagonalize}):
\begin{lstlisting}
NRMat<double> a(n,n);
...
NRMat<double> asave;
asave |= a; //a will be overwritten
\end{lstlisting}
\code{x.swap(y)} exchanges the contents of two objects without any copying.
Conversions between different classes (e.g.\ \code{NRSMat} $\to$ \code{NRMat}, \code{NRMat} $\to$ \code{NRVec},
real $\to$ complex) are done by explicit constructors and always create new data, unless stated otherwise
in the class chapters.
\section{Traits and element types}\label{sec:concepts:traits}
\laheader{la_traits.h}
The algorithms of the library are written generically with the help of the traits class template
\code{LA_traits<T>}, which is defined for all scalar types and for all container classes of the library.
Its main members are:
\begin{lareference}
\lafunc{typedef ... elementtype}{type of the elements (for a scalar the type itself)}
\lafunc{typedef ... normtype}{type of the norm, e.g.\ \code{double} for \code{std::complex<double>} and for \code{NRMat<std::complex<double> >}}
\lafunc{typedef ... realtype; typedef ... complextype}{the corresponding real and complex type, e.g.\ \code{NRVec<double>} and \code{NRVec<std::complex<double> >}}
\lafunc{typedef ... producttype}{type of the product of two objects (for \code{NRSMat} it is \code{NRMat})}
\lafunc{static bool is_plaindata()}{true for scalar types, which can be copied by \code{memcpy}, false for the classes}
\lafunc{static bool is_complex()}{whether the (innermost) element type is complex}
\lafunc{static normtype norm(const T &x); static void axpy(T &s, const T &x, const elementtype c)}{norm and $s \leftarrow s + c\,x$, used by generic algorithms such as \code{matexp.h}}
\lafunc{static void copyonwrite(T &x); static void clearme(T &x); static void deallocate(T &x)}{generic versions of the member functions (no operation for scalars)}
\lafunc{static void put(int fd, const T &x, ...); static void get(int fd, T &x, ...); multiput; multiget}{binary I/O, recursive for nested types}
\lafunc{static T conjugate(const T &x); realpart(x); imagpart(x); setrealpart(x,y); setimagpart(x,y)}{complex helpers, trivial for real types}
\lafunc{static bool bigger(const T &x, const T &y); smaller(x,y)}{comparison used for sorting; for complex types it is an error}
\end{lareference}
The auxiliary template \code{isscalar<T>} distinguishes scalars from classes, \code{LA_traits_complex<T>}
gives for a complex type the component type and the corresponding real vector and matrix types (used by
the mixed real--complex functions such as \code{gemv} of a complex vector with a real matrix), and
\code{LA_traits_io<T>} makes \code{char} types be read and written as numbers by the stream operators.
The functions \code{LA::real(c)} and \code{LA::imag(c)} return references to the real and imaginary part of a
\code{std::complex}, which can be used as l-values.
\subsection{Element types and instantiation}
The class templates are explicitly instantiated in the library for \code{double}, \code{std::complex<double>}
and the integer types \code{char}, \code{short}, \code{int}, \code{long}, \code{long long} and their unsigned
variants. The BLAS and LAPACK backed functions are implemented for \code{double} and \code{std::complex<double>}
(some also for mixed real--complex arguments); 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
for any type with the necessary arithmetic operations.
\subsection{Nested types}
The element type can be a class of the library, e.g.\ \code{NRVec<NRMat<double> >} (a list of matrices) or
\code{NRMat<NRVec<double> >}. Copy on demand is then applied on both levels: \code{copyonwrite()} of the outer
object copies the inner objects shallowly and calls their \code{copyonwrite()}, so that \code{|=} makes a
complete deep copy; \code{put} and \code{get} write and read the inner objects including their dimensions.
Arithmetic operations of the outer class (e.g.\ \code{axpy}) work elementwise via the traits.
\lawarning{Writing into an inner object obtained through \code{operator[]} of the outer object
(\code{v[i](j,k)=x}) requires both objects to be unshared: call \code{v.copyonwrite()} (which recursively
detaches the inner objects as well) before.}
\section{Storage conventions}\label{sec:concepts:storage}
\begin{itemize}
\item Indices run from 0. The classes \code{NRVec_from1}, \code{NRMat_from1} and \code{NRSMat_from1} are
derived from the basic classes and differ only in their element access operators, which take indices from 1.
They convert to and from their base classes without copying.
\item \code{NRVec}: contiguous array of \code{size()} elements.
\item \code{NRMat}: row major (C) order, element $(i,j)$ of an $n\times m$ matrix is at position $i\,m+j$;
\code{a[i]} is a pointer to the $i$-th row. Note that BLAS and LAPACK use column major order, so the
library internally works with the transposed matrices; the user does not need to care, except when calling
Fortran routines directly.
\item \code{NRSMat}: lower triangle of a symmetric or hermitian matrix, packed by rows, element $(i,j)$ with
$i \ge j$ is at position $i(i+1)/2+j$ (which is also the upper triangle packed by columns, the format of the
BLAS/LAPACK packed routines). The macros/functions \code{SMat_index} compute these positions.
\item \code{Tensor}: the leftmost index changes fastest (chapter~\ref{ch:tensor}).
\end{itemize}
All classes can return a raw pointer to their data (e.g.\ \code{operator T*()} of \code{NRVec} and \code{NRMat}),
for passing them to external routines; the copy on demand rule for writes through pointers applies.
\section{GPU support (experimental)}\label{sec:concepts:gpu}
When compiled with \code{CUDALA} (configure finds CUBLAS), the data of \code{NRVec}, \code{NRMat} and
\code{NRSMat} can reside either in the main memory or in the memory of a GPU. The location is described by
the enumeration
\begin{lstlisting}
typedef enum {undefined=-1, cpu=0, gpu1=1, gpu2=2, gpu3=3, gpu4=4} GPUID;
\end{lstlisting}
Constructors take an optional location argument (default \code{undefined}, which means the global default
location \code{DEFAULT_LOC}, settable by \code{set_default_loc()}); \code{getlocation()} returns the location
and \code{moveto(loc)} moves the data. BLAS-like operations of objects on the GPU are performed by CUBLAS;
operands of a binary operation must reside in the same location. Operations which are not implemented for the
GPU call \code{laerror} (``Operation not implemented on GPU (yet). Use .moveto(0) first.''); element access
by \code{operator[]} and \code{operator()} is possible only in the main memory. Without \code{CUDALA},
\code{getlocation()} always returns \code{cpu} and \code{moveto()} does nothing, so that code written for
the GPU compiles also without it.
\lawarning{The GPU support is experimental and has not been thoroughly tested. It is not documented in
detail in this manual; the class chapters at most mention operations restricted to the CPU.}
The GPU code is currently not maintained: it uses the legacy CUBLAS API (\code{cublasInit}) and a compute capability
(\code{-arch sm_20}) not supported by current CUDA versions, and the \code{CUDALA} build has known compile errors. It will be revisited
with current NVIDIA hardware.
\section{Error handling and DEBUG checks}\label{sec:concepts:errors}
\laheader{laerror.h, laerror.cc}
Errors are reported by the macro \code{laerror(message)}, which calls \code{LA::laerror2(message, function)}
with the name of the calling function. \code{laerror2} prints the message and the function name to both
\code{std::cout} and \code{std::cerr}, the system error message if \code{errno} is set, the command line, the
user name, host name and working directory (and the list of files in the working directory if
\code{LA_error_list_files} is true), and then throws an exception of the class
\begin{lstlisting}
class LAerror : public std::exception {public: std::string msg; ... const char *what() const noexcept;};
\end{lstlisting}
which can be caught and printed by \code{operator<<}:
\begin{lstlisting}
try { ... }
catch(LAerror &e) {std::cerr << "error: " << e << std::endl;}
\end{lstlisting}
Errors detected by BLAS, CBLAS and ATLAS (via \code{xerbla} and its variants) are converted to \code{laerror}
as well. Users may call \code{laerror} in their own code. The function \code{sigtraceback(signal,flags)}
installs a handler printing a stack trace (by running \code{gdb} on the process) when the given signal arrives,
e.g.\ \code{sigtraceback(SIGSEGV,1)}; see chapter~\ref{ch:utilities}.
With \code{DEBUG} defined, the library checks index ranges, dimensions of operands, allocation of objects
and the reference count in element access (section~\ref{sec:concepts:refcount}). Without \code{DEBUG}, these
errors lead to undefined behaviour. Programs should be developed and tested with \code{DEBUG}.
\section{Input and output}\label{sec:concepts:io}
All classes support three kinds of I/O:
\begin{description}
\item[Stream operators] \code{operator<<} and \code{operator>>} write and read a human readable text format:
the dimensions followed by the elements (a matrix by rows, one row per line). The input operator resizes the
object. \code{char} elements are written as numbers.
\item[\code{fprintf}, \code{fscanf}] formatted output with a \code{printf} format for the elements and input
from a C \code{FILE*} (see the class chapters).
\item[\code{put}, \code{get}] unformatted binary I/O to a POSIX file descriptor:
\begin{lstlisting}
void put(int fd, bool dimensions=1, bool transp=0, bool orca_format=false) const;
void get(int fd, bool dimensions=1, bool transp=0, bool orca_format=false);
\end{lstlisting}
With \code{dimensions} true, a header with the dimensions is written before the data and \code{get} resizes the
object accordingly; otherwise nothing but the elements is written and the object must already have the right size.
The format is the binary representation of the machine (no conversion of endianity). Per class:
\begin{itemize}
\item \code{NRVec}: header $n$ and a padding \code{int} 1 (8-byte alignment), then the $n$ elements; \code{transp} is ignored.
\item \code{NRMat}: header \code{nrows}, \code{ncols}, then the elements by rows. With \code{transp} the transposed matrix is
written (dimensions swapped, elements column by column); \code{get} with \code{transp} reads such data back into the
original orientation, i.e.\ it reads a file stored by columns.
\item \code{NRSMat}: header $n$, $n$, then the packed lower triangle; \code{transp} is ignored.
\item \code{SparseMat}: header \code{nrows}, \code{ncols} (\code{SPMatindex}) and the \code{int} flags \code{symmetric} and
\code{nonzero}, then triples (row, column, element) and a terminating pair of indices $-1$; \code{SparseSMat}: header
\code{nrows}, \code{ncols}, triples and the terminating pair. With \code{transp} rows and columns are swapped.
\item \code{Tensor} and \code{fourindex} have their own \code{put}/\code{get} (see their chapters).
\end{itemize}
\code{orca_format} (dense classes only; \code{laerror} for the sparse ones) writes the header of the ORCA program: the
dimensions followed by \code{sizeof(T)} instead of the padding; ORCA matrices are stored by columns, so they are read by
\code{get(fd,true,true,true)}. Elements of class type (e.g.\ a vector of matrices) are written recursively by their own
\code{put}, with the same \code{dimensions} flag; \code{transp} is passed on to them only by the transposed
\code{NRMat} and the sparse matrices.
\end{description}
For storing many objects of the same size in a scratch file, see \code{AuxStorage} (chapter~\ref{ch:utilities}).
+322
View File
@@ -0,0 +1,322 @@
\chapter{Four-index quantities}\label{ch:fourindex}
\laheader{fourindex.h, fourindex.cc}
\section{Purpose and main idea}\label{sec:fourindex:purpose}
The header provides containers for four-index quantities with permutational symmetry, typically
two-electron integrals, coupled-cluster $T_2$ amplitudes and two-particle (transition) density matrices.
Only permutationally non-equivalent elements are stored. There are three kinds of classes:
\begin{itemize}
\item \code{fourindex<I,T>}: in-core \emph{sparse} storage, a reference-counted linked list of
elements \code{(i,j,k,l,value)} with indices of type \code{I} (e.g.\ \code{unsigned char} or \code{int});
\item \code{fourindex_ext<I,T>}: the same list stored in a binary file, read and written in large
page-aligned blocks;
\item \code{fourindex_dense<S,T,I>}: \emph{dense} in-core storage for a given symmetry type \code{S},
derived from \code{NRMat<T>} or \code{NRSMat<T>} (``supermatrix'' of index pairs).
\end{itemize}
Iterators run over the stored elements or over all their symmetry-equivalent permutations, so that
algorithms like Fock matrix builds can be written without caring about the symmetry.
Conversions between \code{fourindex_dense} and the general \code{Tensor} class are described in
chapter~\ref{ch:tensor}.
Indices are opaque to the sparse classes (they are never compared with the dimension), so both 0- and
1-based indexing are possible there; the dense classes use indices from 1.
Only \code{fourindex<unsigned char,double>}, \code{fourindex_ext<unsigned char,double>} and
\code{fourindex_dense<nosymmetry,double,unsigned char>} are explicitly instantiated in fourindex.cc;
everything else is implemented in the header and instantiated on use, except \code{fscanf()} and
\code{fprintf()} which exist only for \code{fourindex<unsigned char,double>}.
\section{Symmetry types}\label{sec:fourindex:symmetry}
\begin{lstlisting}
typedef enum {undefined_symmetry=-1, nosymmetry=0, ...} fourindexsymtype;
\end{lstlisting}
Each symmetry type is defined by the list of index permutations (with a sign) generating the equivalent
elements, in the static table \code{fourindex_permutations}\allowbreak\code{[fourindex_n_symmetrytypes]}\allowbreak\code{[max_fourindex_permnumbers+1][5]};
an entry $(p_0,p_1,p_2,p_3,s)$ means that the element with indices $(I_{p_0},I_{p_1},I_{p_2},I_{p_3})$ equals $s$ times
the stored element $(I_0,I_1,I_2,I_3)=(i,j,k,l)$. \code{fourindex_permnumbers[]} gives the number of
permutations. Several enumerators are aliases (same value).
\begin{longtable}{@{}>{\raggedright\arraybackslash}p{0.43\linewidth}r>{\raggedright\arraybackslash}p{0.45\linewidth}@{}}
\toprule
enumerator(s) & perms & equivalent elements\\
\midrule
\endhead
\code{nosymmetry} & 1 & none\\
\code{twoelectronrealmullikan}, \code{twoelectronrealmullikanAA} & 8 & $(ij|kl)=(ji|kl)=(ij|lk)=(kl|ij)=\dots$ (real integrals, Mulliken notation)\\
\code{twoelectronrealdirac} & 8 & $\langle ij|kl\rangle=\langle kj|il\rangle=\langle il|kj\rangle=\langle kl|ij\rangle=\langle ji|lk\rangle=\dots$ (Dirac notation)\\
\code{T2ijab_aces}, \code{trdm2AA} & 4 & antisymmetric in $i,j$ and in $k,l$\\
\code{antisymtwoelectronrealdirac}, \code{rdm2AA} & 8 & $\langle ij\|kl\rangle$: antisymmetric in $i,j$ and in $k,l$, symmetric in the exchange of the pairs\\
\code{T2IjAb_aces}, \code{trdm2AB} & 1 & none (different index ranges)\\
\code{twoelectronrealmullikanAB} & 4 & symmetric in $i,j$ and in $k,l$, no pair exchange (integrals over two different spin orbital sets)\\
\code{T2ijab_unitary} & 8 & antisymmetric in $i,j$ and in $k,l$, antisymmetric in the pair exchange (antihermitean exponent)\\
\code{T2IjAb_unitary} & 2 & $(kl,ij)=-(ij,kl)$\\
\code{antisymtwoelectronrealdiracAB}, \code{rdm2AB}, \code{twoelectroncomplexmullikan_}\allowbreak\code{without_conjugation} & 2 & symmetric in the pair exchange $(ij,kl)=(kl,ij)$\\
\code{twoelectronrealmullikan}\allowbreak\code{reducedsymAA}, \code{rdm2SS} & 4 & $(ij|kl)=(kl|ij)=(ji|lk)=(lk|ji)$ (e.g.\ unitary downfolded CC effective integrals)\\
\code{twoelectronrealmullikan}\allowbreak\code{reducedsymAB} & 2 & $(ij|kl)=(ji|lk)$ (e.g.\ spin-summed 2-RDM)\\
\code{twoelectroncomplexmullikan} & 4 & $(ij|kl)=(kl|ij)$, $(ji|lk)=(lk|ji)=(ij|kl)^*$\\
\bottomrule
\end{longtable}
The last entry of each permutation is applied by \code{fourindex_permfactor()}: $\pm1$ is a sign, $\pm2$ means complex
conjugation (with the sign), used for \code{twoelectroncomplexmullikan}: $(ij|kl)=(kl|ij)=(ji|lk)^*=(lk|ji)^*$.
\begin{lareference}
\lafunc{template<class I, class T> void symmetry_faktor(const fourindexsymtype symmetry, const union packed_index<I> &index, T &elem)}{multiply \code{elem} by the factor $1/2$ for each coincidence of indices which makes two of the generated permutations identical, so that summing over all permutations generated by a \code{piterator} counts every distinct element exactly once. Implemented for all symmetry types: the element is scaled by $1/n$, $n$ the number of the type's permutations leaving the index tuple unchanged (e.g.\ $1/(1+m)$, $m\in\{0,1,3\}$, for \code{twoelectroncomplexmullikan} and \code{twoelectronrealmullikanreducedsymAA}); for coincidences making an element vanish by antisymmetry no scaling is applied (the generated contributions cancel).}
\end{lareference}
\section{Element records}\label{sec:fourindex:records}
\begin{lareference}
\lafunc{template<class I> struct indiv_index {I i; I j; I k; I l;}}{the four indices (packed with GNU C).}
\lafunc{template<class I> union packed_index {I packed[4]; struct indiv_index<I> indiv;}}{the indices accessible by name or as an array.}
\lafunc{template<class I, class T> struct matel4 {T elem; matel4 *next; union packed_index<I> index;}}{list element of \code{fourindex}; also the type returned by the permutation iterators.}
\lafunc{template<class I, class T> struct matel4stored {T elem; union packed_index<I> index;}}{packed record of the binary file format (used by \code{fourindex::put/get}, \code{fourindex_ext} and \code{fourindex_dense::putext}).}
\end{lareference}
The binary format is thus a raw sequence of \code{matel4stored<I,T>} records (machine dependent, no header
unless written by \code{fourindex::put(fd,true)}).
\section{In-core sparse storage: \texttt{fourindex<I,T>}}\label{sec:fourindex:fourindex}
\subsection{Reference counting}
\code{fourindex} is reference counted: the copy constructor and \code{operator=} share the list,
\code{copyonwrite()} copies it if shared. There is no \code{operator|=}.
\code{permuteme()}, \code{operator+=} and \code{join()} call \code{copyonwrite()}, \code{resize()} detaches.
The \code{add()} functions (and therefore \code{get()} and \code{operator>>}) are element-level access by design:
they prepend new elements to this object's list without \code{copyonwrite()}, so call \code{copyonwrite()} once
before adding to a possibly shared object (otherwise the copies diverge while sharing one reference count).
Writing through the non-const \code{iterator} modifies the shared list; call \code{copyonwrite()} first.
A default-constructed \code{fourindex} is empty but defined and can be filled by \code{add()} or \code{get()}.
\subsection{Construction and basic operations}
\begin{lareference}
\lafunc{fourindex()}{undefined (no list, symmetry \code{undefined_symmetry}, dimension 0).}
\lafunc{fourindex(const I n, const fourindexsymtype symmetry0=undefined_symmetry, const I terminator0= -1, const bool doscaling0=true)}{empty list for dimension (number of orbitals) \code{n}, with symmetry, terminator for text I/O and scaling flag for \code{piterator}.}
\lafunc{fourindex(const fourindex &rhs); fourindex & operator=(const fourindex &rhs)}{shallow copy (list shared, all attributes including \code{symmetry} copied).}
\lafunc{~fourindex()}{deletes the list when the last reference disappears.}
\lafunc{void resize(const I n)}{discard all elements (detach if shared) and set the dimension; \code{n} must be positive.}
\lafunc{void dealloc()}{intended to release the list; calls \code{resize(0)}, which raises \code{laerror}.}
\lafunc{void copyonwrite()}{copy the list if shared (the order of elements is reversed by copying).}
\lafunc{int getcount() const}{reference count (0 if undefined).}
\lafunc{I size() const}{the dimension \code{n}, \emph{not} the number of elements.}
\lafunc{unsigned long length() const}{number of stored elements.}
\lafunc{matel4<I,T> *getlist() const}{the head of the list.}
\lafunc{void setsymmetry(fourindexsymtype s); fourindexsymtype getsymmetry() const}{symmetry type.}
\lafunc{void setterminator(const I terminator0); I getterminator() const}{index value marking the end of a text list.}
\lafunc{void setscaling(const bool doscaling0)}{whether \code{piterator} applies \code{symmetry_faktor}.}
\lafunc{void add(const I i, const I j, const I k, const I l, const T elem); void add(const union packed_index<I> &index, const T elem); void add(const I (&index)[4], const T elem); void add(const matel4<I,T> &rhs); void add(const matel4stored<I,T> &rhs)}{prepend one element (no check for duplicates or canonical order). The iteration order is thus the reverse of the insertion order.}
\lafunc{fourindex & operator+=(const fourindex &rhs)}{append (copy) all elements of \code{rhs}; dimensions must agree, symmetries are not checked.}
\lafunc{fourindex & join(fourindex &rhs)}{move the list of an unshared \code{rhs} to the end of this list; \code{rhs} is emptied. Cheaper than \code{+=}.}
\lafunc{void permuteme(const NRPerm<int> &p, const bool inverse=false)}{relabel all indices $i\to p_i$ (orbital permutation); \code{p.size()} must equal the dimension and the indices must be 1-based.}
\lafunc{void swap(fourindex &rhs)}{exchange contents without copying.}
\end{lareference}
\code{+=} and \code{join()} require equal symmetries of the operands (\code{laerror} otherwise); an empty object with undefined symmetry adopts the symmetry of the right-hand side.
\subsection{Iterators}
\begin{lareference}
\lafunc{class iterator; iterator begin() const; iterator end() const}{forward iterator over the stored elements; \code{*it} and \code{it->} give \code{matel4<I,T>} (modifiable through a non-const iterator), pre- and postincrement, \code{==}, \code{!=}.}
\lafunc{class piterator; piterator pbegin() const; piterator pend() const}{iterates over all permutations of all stored elements as defined by the symmetry type; \code{*p}/\code{p->} give a const \code{matel4<I,T>} with permuted indices and the element multiplied by the sign and, if scaling is on, by \code{symmetry_faktor} for repeated indices. Contributions of all generated elements can therefore simply be accumulated. Only preincrement; test the end by \code{p.notend()} or \code{p.end()} (comparing with \code{pend()} is less efficient).}
\lafunc{class qiterator; qiterator qbegin(); qiterator qend() const}{iterates over the \emph{distinct} permutations of each stored element (duplicates arising from equal indices are skipped, no scaling); sign applied. Only preincrement; use \code{notend()}/\code{end()}.}
\end{lareference}
The symmetry must be set (not \code{undefined_symmetry}) for \code{piterator} and \code{qiterator}.
\subsection{Input and output}
\begin{lareference}
\lafunc{unsigned long put(int fd, bool withattr=true) const}{write all elements as \code{matel4stored} records, preceded (if \code{withattr}) by the symmetry and the dimension, each padded to the size of a union of \code{fourindexsymtype}, \code{I} and \code{T}; returns the number of elements.}
\lafunc{unsigned long get(int fd, bool withattr=true)}{read the same format until end of file, \emph{adding} the elements to the list; returns their number.}
\lafunc{template <class I, class T> std::ostream& operator<<(std::ostream &s, const fourindex<I,T> &x)}{text output: the dimension, lines \code{i j k l value}, and a final line with four terminators and 0.}
\lafunc{template <class I, class T> std::istream& operator>>(std::istream &s, fourindex<I,T> &x)}{read the text format; \code{x} is resized to the read dimension (the symmetry is not set) and reading stops at the first line containing the terminator of \code{x} as an index.}
\lafunc{void fscanf(FILE *f); void fprintf(FILE *f, char *format) const}{C-style text I/O, only for \code{fourindex<unsigned char,double>}; line format \code{value i j k l}. \code{fscanf} adds elements until a line in which any index equals the terminator (compared after conversion to the index type, so $-1$ in the file matches the default terminator 255); \code{fprintf} applies \code{format} (which must consume a \code{double} and four \code{int}s) to each element and ends with a line \code{0 t t t t} with the terminator $t$. With terminator 0 this is the FCIDUMP-like convention (blocks separated by zero lines, read by consecutive \code{fscanf} calls; a one-electron line $i\,j\,0\,0$ also ends a block).}
\end{lareference}
\section{Disc storage: \texttt{fourindex\_ext<I,T>}}\label{sec:fourindex:ext}
\code{fourindex_ext} accesses a file of \code{matel4stored<I,T>} records through an already opened file
descriptor. It is not copyable (private default constructor, copy constructor and assignment) and is
not reference counted. Reading and writing go through one internal page-aligned buffer whose size is
a common multiple of the record size, the page size and the file system block size; the class tries to
\code{mlock} the buffer (failure ignored). The buffer is shared by all iterators of an object, therefore
only one iteration may be active at a time.
The file may be opened with \code{O_DIRECT} to avoid filling the page cache when a large file is read once
sequentially; this is supported: the buffer is page-aligned and its size is a multiple of the page and filesystem
block size, as \code{O_DIRECT} requires (tested on Linux 6.6 with ext4, also for a file size that is not a multiple of
the block size; Linux 2.4 is not supported).
\begin{lareference}
\lafunc{fourindex_ext(const int file, const fourindexsymtype s=undefined_symmetry, const I n=0, const unsigned int b=1024, const I terminator0= -1, const bool doscaling0= true)}{attach to the open descriptor \code{file}; \code{b} is the approximate buffer size in records.}
\lafunc{~fourindex_ext()}{frees the buffer; it does \emph{not} flush pending output and does not close the file.}
\lafunc{void setsymmetry(fourindexsymtype s); fourindexsymtype getsymmetry() const; void setterminator(const I terminator0); I getterminator() const; void setscaling(const bool doscaling0)}{as for \code{fourindex}.}
\lafunc{void resize(I n); I size() const}{set/get the dimension (no effect on the file).}
\lafunc{void rewind() const}{seek to the beginning of the file.}
\lafunc{void put(const matel4stored<I,T> x); void put(I i, I j, I k, I l, const T &elem)}{buffered write of one record.}
\lafunc{void flush()}{write the buffered records; must be called after the last \code{put()}.}
\lafunc{class iterator; iterator begin() const; iterator end() const}{rewind and iterate over all records of the file; \code{*it}/\code{it->} give \code{const matel4stored<I,T>}; preincrement only.}
\lafunc{class piterator; piterator pbegin(); piterator pend() const}{all permutations, as for \code{fourindex}.}
\lafunc{class qiterator; qiterator qbegin(); qiterator qend() const}{distinct permutations, as for \code{fourindex}.}
\lafunc{template <class I, class T> std::ostream& operator<<(std::ostream &s, const fourindex_ext<I,T> &x)}{text output of the whole file in the format of \code{fourindex}.}
\lafunc{template <class I, class T> std::istream& operator>>(std::istream &s, fourindex_ext<I,T> &x)}{read the text format and append the elements to the file (with \code{flush()}).}
\end{lareference}
\section{Dense storage: \texttt{fourindex\_dense<S,T,I>}}\label{sec:fourindex:dense}
\begin{lstlisting}
template<fourindexsymtype S, class T, class I> class fourindex_dense; // only specializations exist
\end{lstlisting}
The dense classes are derived publicly from \code{NRMat<T>} or \code{NRSMat<T>}, so the whole interface of
the base class (arithmetic, \code{norm()}, \code{put()/get()}, \code{randomize()}, \code{|=},
\code{copyonwrite()}, \dots, chapters~\ref{ch:mat} and~\ref{ch:smat}) is available, and they follow its
reference counting and copy-on-demand scheme. The third template parameter \code{I} is the index type of
the sparse classes and matters only for the constructors from \code{fourindex<I,T>} and
\code{fourindex_ext<I,T>} (named \code{DUMMY} in some places). \code{LA_traits<fourindex_dense<S,T,I> >} defines
\code{elementtype} and \code{normtype}.
The four-index \code{operator()} returning a reference is an element access in the sense of
section~\ref{sec:concepts:refcount}: call \code{copyonwrite()} before writing into a possibly shared object
(a DEBUG build raises \code{laerror} for a shared object). The same holds by design for the element-level members
\code{set()}, \code{add()} and \code{add_unique()}: they do not call \code{copyonwrite()}, the DEBUG count check
reports a write into shared data.
All specializations use indices from 1.
\subsection{twoelectronrealmullikanAB}
Base \code{NRMat<T>} of dimension $n(n+1)/2 \times n(n+1)/2$; row = symmetric pair $(ij)$, column = pair $(kl)$.
\begin{lareference}
\lafunc{fourindex_dense(); explicit fourindex_dense(const int n); fourindex_dense(const T &a, const int n); fourindex_dense(const T *a, const int n)}{empty; uninitialized; filled with \code{a}; copied from an array.}
\lafunc{fourindex_dense(const NRMat<T> &rhs)}{reinterpret a matrix (shared data); also an implicit conversion from the base class.}
\lafunc{fourindex_dense(const fourindex<I,T> &rhs); fourindex_dense(const fourindex_ext<I,T> &rhs)}{expand a sparse list of the same symmetry type (\code{laerror} otherwise); missing elements are zero.}
\lafunc{T& operator()(unsigned int i, unsigned int j, unsigned int k, unsigned int l); const T& operator()(unsigned int i, unsigned int j, unsigned int k, unsigned int l) const}{element $(ij|kl)$.}
\lafunc{void resize(const int n); int nbas() const}{resize for $n$ orbitals; the number of orbitals.}
\lafunc{void putext(int f, T thr=1e-15)}{write the non-equivalent elements with $|x|>\mathtt{thr}$ as \code{matel4stored<I,T>} records to the file descriptor \code{f} (readable by \code{fourindex_ext} or \code{fourindex::get(fd,false)}).}
\end{lareference}
\subsection{twoelectronrealmullikan}
Base \code{NRSMat<T>} of dimension $n(n+1)/2$ (symmetric matrix of symmetric pairs), i.e.\ full 8-fold
symmetry. The interface is identical to \code{twoelectronrealmullikanAB} with \code{NRSMat<T>} in place of
\code{NRMat<T>}.
\subsection{nosymmetry}
Base \code{NRMat<T>} of dimension $n^2\times n^2$; element $(i,j,a,b)$ is the matrix element
$((j-1)n+i-1,\,(b-1)n+a-1)$.
\begin{lareference}
\lafunc{fourindex_dense(); explicit fourindex_dense(const int nnn); explicit fourindex_dense(const int nnn, const NRMat<T> &mat)}{empty; uninitialized; reinterpret \code{mat} (shared).}
\lafunc{T& operator()(unsigned int i, unsigned int j, unsigned int a, unsigned int b); const T& operator()(...) const}{element access.}
\lafunc{void resize(const int nnn); unsigned int nbas() const}{dimension.}
\lafunc{void print(std::ostream &out) const}{print all elements as lines \code{i j a b value}.}
\end{lareference}
\subsection{T2IjAb\_aces}
Spin block $t_{Ij}^{Ab}$ of CC amplitudes in the ACES storage order; base \code{NRMat<T>} of dimension
$(\mathtt{noca}\cdot\mathtt{nocb})\times(\mathtt{nvra}\cdot\mathtt{nvrb})$, element $(i,j,a,b)$ at
$((j-1)\,\mathtt{noca}+i-1,\,(b-1)\,\mathtt{nvra}+a-1)$; $i,a$ are $\alpha$ and $j,b$ are $\beta$ indices.
\begin{lareference}
\lafunc{unsigned int noca, nocb, nvra, nvrb}{public dimensions (occupied/virtual, $\alpha$/$\beta$).}
\lafunc{fourindex_dense(); explicit fourindex_dense(const int nocca, const int noccb, const int nvrta, const int nvrtb); explicit fourindex_dense(const int nocca, const int noccb, const int nvrta, const int nvrtb, const NRMat<T> &data)}{empty; uninitialized; reinterpret \code{data} (shared, dimensions checked).}
\lafunc{void resize(const int nocca, const int noccb, const int nvrta, const int nvrtb)}{resize.}
\lafunc{T& operator()(unsigned int i, unsigned int j, unsigned int a, unsigned int b); const T& operator()(...) const}{element access.}
\lafunc{void print(std::ostream &out) const}{print all elements.}
\end{lareference}
\subsection{T2ijab\_aces}
Same-spin amplitudes $t_{ij}^{ab}$, antisymmetric in $i,j$ and in $a,b$; base \code{NRMat<T>} of dimension
$\binom{\mathtt{nocc}}{2}\times\binom{\mathtt{nvrt}}{2}$ storing the elements with $i>j$, $a>b$.
\begin{lareference}
\lafunc{unsigned int nocc, nvrt}{public dimensions.}
\lafunc{fourindex_dense(); explicit fourindex_dense(const int noc, const int nvr); explicit fourindex_dense(const int noc, const int nvr, const NRMat<T> &data)}{empty; uninitialized; reinterpret \code{data} (shared, dimensions checked).}
\lafunc{void resize(const int noc, const int nvr)}{resize.}
\lafunc{T operator()(unsigned int i, unsigned int j, unsigned int a, unsigned int b) const}{element value with the sign from the index order; 0 for $i=j$ or $a=b$. No l-value access because of the sign.}
\lafunc{void set(unsigned int i, unsigned int j, unsigned int a, unsigned int b, T elem); void add(unsigned int i, unsigned int j, unsigned int a, unsigned int b, T elem)}{set or increment an element given in any index order (the sign of the permutation is applied); elements with $i=j$ or $a=b$ are identically zero and ignored (a nonzero value raises \code{laerror} in DEBUG builds).}
\lafunc{void print(std::ostream &out) const}{print the stored elements.}
\end{lareference}
\subsection{antisymtwoelectronrealdiracAB}
Base \code{NRSMat<T>} of dimension $n^2$: symmetric matrix of ordered pairs; element $(i,j,a,b)$ is the
matrix element $((j-1)n+i-1,\,(b-1)n+a-1)$, i.e.\ symmetric only under exchange of the pairs. Here $i,a$ are
$\alpha$ and $j,b$ $\beta$ indices.
\begin{lareference}
\lafunc{fourindex_dense(); explicit fourindex_dense(const int n); explicit fourindex_dense(const int n, const NRSMat<T> &data)}{empty; uninitialized; reinterpret \code{data} (shared, dimension checked).}
\lafunc{T& operator()(unsigned int i, unsigned int j, unsigned int a, unsigned int b); const T& operator()(...) const; void set(unsigned int i, unsigned int j, unsigned int k, unsigned int l, T elem)}{element access.}
\lafunc{void resize(const int n); unsigned int nbas() const}{dimension.}
\lafunc{void print(std::ostream &out) const}{print the non-equivalent elements.}
\end{lareference}
\subsection{antisymtwoelectronrealdirac}
Antisymmetrized integrals $\langle ij\|kl\rangle$ with the full 8-fold symmetry; base \code{NRSMat<T>} of
dimension $\binom{n}{2}$ (symmetric matrix of antisymmetric pairs, \code{ASMat_index_1} of smat.h).
\begin{lareference}
\lafunc{fourindex_dense(); explicit fourindex_dense(const int n); explicit fourindex_dense(const int n, const NRSMat<T> &data); fourindex_dense(const T &a, const int n); fourindex_dense(const T *a, const int n)}{empty; uninitialized; reinterpret \code{data} (shared); filled with \code{a}; copied from an array.}
\lafunc{fourindex_dense(const fourindex<I,T> &rhs); fourindex_dense(const fourindex_ext<I,T> &rhs)}{build $\langle pq\|rs\rangle=(pr|qs)-(ps|qr)$ from Mulliken integrals: \code{rhs} must have symmetry \code{twoelectronrealmullikan}.}
\lafunc{const T operator()(unsigned int i, unsigned int j, unsigned int k, unsigned int l) const}{element value with sign, 0 for $i=j$ or $k=l$. No l-value access.}
\lafunc{void set(unsigned int i, unsigned int j, unsigned int k, unsigned int l, T elem); void add(unsigned int i, unsigned int j, unsigned int k, unsigned int l, T elem)}{set/increment with the sign from the index order; elements with $i=j$ or $k=l$ are ignored, or raise \code{laerror} if the macro \code{FORBID_NONEXISTENT_ASSIGNMENTS} is defined.}
\lafunc{void add_unique(unsigned int i, unsigned int j, unsigned int k, unsigned int l, T elem)}{increment only if $(i,j,k,l)$ is the canonical representative ($i>j$, $k>l$, pair $(ij)\ge(kl)$), otherwise do nothing.}
\lafunc{void resize(const int n); int nbas() const}{dimension.}
\lafunc{void print(std::ostream &out) const}{print the non-equivalent elements.}
\end{lareference}
\subsection{Other symmetry types}
No dense specializations exist for \code{twoelectronrealdirac}, the \code{..._unitary} types, the
\code{...reducedsym...} types and the complex types (source TODO: ``implement fourindex\_dense for unitary t2
symmetry types'').
\section{Examples}\label{sec:fourindex:examples}
A sparse list of real Mulliken integrals, a contraction over all equivalent elements, and conversion to
dense storage via a disc file:
\begin{lstlisting}
int n=3;
fourindex<int,double> f(n,twoelectronrealmullikan);
f.add(2,1,1,1,0.5); // (21|11)
f.add(2,2,1,1,0.3); // (22|11)
f.add(2,1,2,1,0.1); // (21|21)
// Coulomb-like matrix J_ij = sum_kl (ij|kl) D_kl over ALL elements
NRSMat<double> D(1.,n);
NRMat<double> J(0.,n,n);
for(fourindex<int,double>::piterator p=f.pbegin(); p.notend(); ++p)
J(p->index.indiv.i-1,p->index.indiv.j-1) +=
p->elem * D(p->index.indiv.k-1,p->index.indiv.l-1);
// the distinct elements (here 10), unscaled
int cnt=0;
for(fourindex<int,double>::qiterator q=f.qbegin(); q.notend(); ++q) ++cnt;
// via a binary file to dense storage
int fd=open("ints.bin",O_CREAT|O_TRUNC|O_RDWR,0644);
f.put(fd,false); // raw records only
fourindex_ext<int,double> fe(fd,twoelectronrealmullikan,n);
fourindex_dense<twoelectronrealmullikan,double,int> fdense(fe);
const fourindex_dense<twoelectronrealmullikan,double,int> &fc=fdense;
cout << fc(1,2,2,1) << endl; // 0.1 = (21|21)
close(fd);
\end{lstlisting}
Writing a file through \code{fourindex_ext}:
\begin{lstlisting}
int fd2=open("ints2.bin",O_CREAT|O_TRUNC|O_RDWR,0644);
{
fourindex_ext<int,double> fo(fd2,twoelectronrealmullikan,n);
fo.put(2,1,1,1,0.5);
fo.put(2,2,1,1,0.3);
fo.flush(); // not done by the destructor
for(fourindex_ext<int,double>::iterator it=fo.begin(); it!=fo.end(); ++it)
cout << it->elem << endl;
}
close(fd2);
\end{lstlisting}
See also the conversion examples \code{fourindex2tensor}/\code{tensor2fourindex} in chapter~\ref{ch:tensor}.
\section{Pitfalls}\label{sec:fourindex:pitfalls}
\begin{itemize}
\item \code{fourindex::size()} is the number of orbitals; the number of elements is \code{length()}.
\item The sparse classes do not enforce canonical index order or uniqueness; adding two equivalent elements
counts them twice in the iterators and in the dense constructors the later one wins.
\item \code{piterator} scaling is not implemented for all symmetry types; use \code{setscaling(false)} if
\code{laerror} reports it.
\item \code{fourindex_ext} must be flushed explicitly after writing; only one iterator per object may be
active.
\item Constructors of the dense classes from \code{NRMat}/\code{NRSMat} share the data.
\end{itemize}
+124
View File
@@ -0,0 +1,124 @@
\chapter{Introduction}\label{ch:introduction}
\section{Purpose}
\LA{} is a C++ class library for numerical linear algebra and related tasks.
Its core are the class templates
\code{NRVec<T>} (vector), \code{NRMat<T>} (dense matrix) and \code{NRSMat<T>} (symmetric or hermitian
matrix in packed storage), which provide a convenient and efficient interface to the BLAS and LAPACK
libraries. Around them a number of further classes and algorithms has grown, mostly motivated by the
needs of quantum chemistry: sparse matrices, tensors with arbitrary index symmetry, four-index quantities
(two-electron integrals), iterative eigensolvers and linear solvers working with matrices that are never
stored explicitly, convergence acceleration, numerical optimization, permutations and the symmetric group,
polynomials, quaternions and others.
The design follows a few principles, which are explained in detail in chapter~\ref{ch:concepts}:
\begin{itemize}
\item \emph{Reference counting with copy on demand.} Copying and assigning objects is cheap, data are shared
until they are modified. Objects can therefore be passed and returned by value. The price is that writing to
individual elements requires an explicit \code{copyonwrite()} call (section~\ref{sec:concepts:refcount}).
\item \emph{No expression templates.} The library does not optimize matrix expressions. Operators like
\code{A*x+y} are available for convenience, but performance critical code should call the BLAS-like member
functions (\code{gemv}, \code{gemm}, \code{axpy}, \dots) directly, which avoid temporaries.
\item \emph{Generic algorithms.} Iterative methods (Davidson, Lanczos, conjugate gradients, GMRES, the matrix
exponential, \dots) are templates which only need a few operations of the matrix class (typically
\code{nrows()}, \code{ncols()}, \code{diagonalof()} and \code{gemv()}), so that they work with user classes
which compute the matrix--vector product on the fly (``direct'' methods).
\item \emph{Nesting.} The element type of the containers can itself be a container, e.g.\
\code{NRVec<NRMat<double> >}; copy on demand and I/O work recursively (section~\ref{sec:concepts:traits}).
\end{itemize}
The manual is written for users who know both C++ and linear algebra. It explains the concepts and
gives a compact but complete reference of the public interface. Many further usage examples are in the
test programs \header{t.cc}, \header{t2.cc} and \header{test.cc} of the distribution; the regression test of the
optimization module is \header{testoptimize.cc}.
The class names \code{NRVec}, \code{NRMat} etc.\ are historical: the classes were first used together with
the Numerical Recipes library. They can be used with it, but are completely independent of it.
\section{Overview}
\begin{longtable}{@{}p{0.33\linewidth}p{0.62\linewidth}@{}}
\toprule
\textbf{Header} & \textbf{Content} \\
\midrule
\endhead
\header{la.h} & the single include file for the user \\
\header{vec.h} & \code{NRVec<T>}, \code{NRVec_from1<T>} (chapter~\ref{ch:vec}) \\
\header{mat.h} & \code{NRMat<T>}, \code{NRMat_from1<T>} (chapter~\ref{ch:mat}) \\
\header{smat.h} & \code{NRSMat<T>}, \code{NRSMat_from1<T>} (chapter~\ref{ch:smat}) \\
\header{sparsemat.h}, \header{sparsesmat.h} & sparse matrices \code{SparseMat<T>}, \code{SparseSMat<T>} (chapter~\ref{ch:sparse}) \\
\header{csrmat.h} & compressed row storage sparse matrix (unfinished) \\
\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}) \\
\header{tensor.h} & tensors with index symmetries (chapter~\ref{ch:tensor}) \\
\header{fourindex.h} & four-index quantities, two-electron integrals (chapter~\ref{ch:fourindex}) \\
\header{permutation.h}, \header{qsort.h} & permutations, partitions, Young tableaux, sorting (chapter~\ref{ch:permutation}) \\
\header{polynomial.h}, \header{contfrac.h} & polynomials, rationals and continued fractions (chapter~\ref{ch:polynomial}) \\
\header{vecmat3.h} & 3D vectors and matrices (chapter~\ref{ch:vecmat3}) \\
\header{quaternion.h} & quaternions (chapter~\ref{ch:quaternion}) \\
\header{bitvector.h} & bit vectors, polynomials over GF(2) (chapter~\ref{ch:bitvector}) \\
\header{numbers.h}, \header{graph.h} & number theory, graph algorithms (chapter~\ref{ch:utilities}) \\
\header{reg.h}, \header{regsurf.h} & regression (chapter~\ref{ch:regression}) \\
\header{auxstorage.h}, \header{la_random.h}, \header{miscfunc.h}, \header{simple.h}, \header{laerror.h} & utilities (chapter~\ref{ch:utilities}) \\
\header{la_traits.h} & traits classes, the basis of the generic programming (section~\ref{sec:concepts:traits}) \\
\bottomrule
\end{longtable}
Everything is declared in the namespace \code{LA}, with the exception of a few headers designed to be usable
also without the rest of the library (\header{vecmat3.h}: namespace \code{LA_Vecmat3}, \header{quaternion.h}:
namespace \code{LA_Quaternion}, \header{simple.h}). \header{la.h} includes all headers and contains
\code{using namespace LA;} together with a few typedefs (\code{NRDVec}, \code{NRCVec}, \code{NRIVec},
\code{NRDMat}, \code{NRCMat}, \code{NRIMat} for \code{double}, \code{std::complex<double>} and \code{int} elements).
\section{A minimal example}
\begin{lstlisting}
#include "la.h"
using namespace LA;
using namespace std;
int main()
{
const int n=4;
NRSMat<double> h(n);
h.randomize(1.); //symmetric matrix with random elements
NRSMat<double> hsave;
hsave |= h; //deep copy, diagonalize() destroys its argument
NRVec<double> e(n);
NRMat<double> v(n,n);
diagonalize(h,e,&v); //eigenvalues e (ascending), eigenvectors in columns of v
NRMat<double> r = hsave*v; //residual H V - V diag(e)
r.copyonwrite(); //always before writing via operator() or []
for(int i=0; i<n; ++i) for(int j=0; j<n; ++j) r(i,j) -= v(i,j)*e[j];
cout << e << "residual norm = " << r.norm() << endl;
}
\end{lstlisting}
It is compiled e.g.\ by
\begin{lstlisting}[language=bash]
g++ -O3 -I$PREFIX/include example.cc -L$PREFIX/lib -lla -llapack -lcblas -lblas
\end{lstlisting}
with the same preprocessor options and libraries the library was configured with
(chapter~\ref{ch:building}).
\section{Conventions used in this manual}
\begin{itemize}
\item \code{T} denotes the element type of a class template. Unless said otherwise, BLAS and LAPACK backed
operations are available for \code{double} and \code{std::complex<double>}; other element types (integer
types, nested classes) support the generic operations.
\item Indices run from 0, unless a \code{_from1} variant of a class is used.
\item In the reference lists, ``this'' denotes the object the member function is called for.
Functions that modify the object call \code{copyonwrite()} themselves unless stated otherwise.
\item Boxes marked \textbf{Caution} point to common pitfalls; yellow boxes are open questions for the
author, collected in the list at the beginning.
\end{itemize}
\section{Authors and licence}
\LA{} is copyright \copyright{} 2008-- Ji\v{r}\'{\i} Pittner. The complex versions of the basic classes were
written by Roman Curik, the CUDA interface was contributed by Miroslav Sulc; the regression module
has its own author (chapter~\ref{ch:regression}). The library is free software, distributed under the
GNU General Public License, version~3 or later.
Since 2026, the author started to employ an AI assistant for code review, documentation, and some new implementations.
+432
View File
@@ -0,0 +1,432 @@
\chapter{Iterative methods}\label{ch:iterative}
\laheader{davidson.h, lanczos.h, conjgrad.h, gmres.h, diis.h, bisection.h}
\section{Purpose and main idea}\label{sec:iterative:purpose}
This chapter describes the header-only iterative algorithms of \LA{}:
\begin{itemize}
\item \code{davidson()} (\header{davidson.h}): a few lowest (or closest to a target) eigenpairs of a large real symmetric or
complex hermitian matrix, or right eigenpairs with real eigenvalues of a real non-symmetric matrix;
\item \code{lanczos()} (\header{lanczos.h}): a few lowest (or closest to a target) eigenpairs of a large hermitian matrix;
\item \code{conjgrad()} (\header{conjgrad.h}) and \code{gmres()} (\header{gmres.h}): solution of large linear systems $Ax=b$,
or of the normal equations $A\Tr Ax=A\Tr b$ for rectangular $A$ (least squares);
\item the class \code{DIIS} (\header{diis.h}): Pulay's DIIS convergence acceleration of a general fixed-point iteration;
\item \code{bisection_find()} and \code{interpolation_find()} (\header{bisection.h}): search in a sorted array.
\end{itemize}
Dense (LAPACK based) diagonalization, linear solvers and matrix functions are in chapter~\ref{ch:nonclass}; the small
subspace problems of \code{davidson} and \code{lanczos} are solved by the routines described there
(\code{diagonalize}, \code{gdiagonalize}, \code{diagonalize3}, \code{linear_solve}). The quasi-Newton GDIIS optimizer built on
the \code{DIIS} class is in chapter~\ref{ch:optimize}.
All functions are templates defined in the headers (all except \header{bisection.h} are included by \header{la.h}) and are
instantiated by the user's code. The matrix is a template parameter \code{Matrix} which is accessed only through a few member
functions, so that the matrix need not be stored at all: the matrix-vector product may be computed ``directly'' from other
data (direct CI in quantum chemistry, operators given by a formula, a product of several matrices, \dots). Any of the \LA{}
matrix classes providing these functions (\code{NRMat}, \code{NRSMat}, \code{SparseMat}, \code{SparseSMat}, \code{CSRMat},
\code{SemiSparseMat}, chapters~\ref{ch:mat}, \ref{ch:smat}, \ref{ch:sparse}) can also be used directly.
\section{Requirements on the matrix class}\label{sec:iterative:matrix}
The member functions used are listed in table~\ref{tab:iterative:matrix}; they must be \code{const}. Their semantics follow
the \LA{} matrix classes:
\begin{lareference}
\lafunc{int nrows() const; int ncols() const}{Dimensions (any integer type). The eigensolvers require a square matrix
(\code{laerror} otherwise).}
\lafunc{bool issymmetric() const}{\code{true} if the matrix is real symmetric or complex hermitian. \code{davidson} chooses the
symmetric or non-symmetric algorithm by it; \code{lanczos} calls \code{laerror} if it is \code{false}. Note that
\code{NRMat::issymmetric()} always returns \code{false}, \code{NRSMat} and \code{SparseSMat} return \code{true}, \code{SparseMat}
returns its symmetric flag.}
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide=0, bool cache=false) const}{With \code{divide==false}: store the
diagonal $A_{ii}$ into \code{r} (allocated by the caller with \code{nrows()} elements) and return a pointer to the $n$ diagonal
elements, normally \code{&r[0]}. The eigensolvers call it with \code{cache=true}; a direct matrix may then compute the diagonal
once, keep it, and return a pointer to its own copy (the comment in davidson.h recommends this ``for efficiency in a direct
CI case''); the library classes ignore \code{cache}. With \code{divide==true}: divide \code{r} element-wise by the nonzero
diagonal elements (Jacobi preconditioning) and return \code{NULL}. For a rectangular matrix ($m\times n$, $m\ne n$) the
library classes use the diagonal of $A\Tr A$ (squared column norms, \code{r} with $n=$\code{ncols()} elements) instead, which is
the right (Jacobi) preconditioner for the normal equations used by \code{conjgrad} and \code{gmres} in that case; user matrix
classes should follow the same convention.}
\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$ with $\mathrm{op}(A)=A$ for \code{trans=='n'} and $A\Tr$ for \code{'t'}; \code{r} is allocated by
the caller with the right size. With $\beta=0$ the previous contents of \code{r} must be ignored (it can be uninitialized).}
\end{lareference}
\begin{table}[htb]
\centering
\begin{tabular}{@{}l>{\raggedright\arraybackslash}p{2.3cm}>{\raggedright\arraybackslash}p{3.1cm}>{\raggedright\arraybackslash}p{2.9cm}@{}}
\toprule
& \code{issymmetric} & \code{diagonalof} & \code{gemv} \\
\midrule
\code{davidson} & yes & \code{divide=false} & \code{'n'}, $\beta=0$ \\
\code{lanczos} & yes (must be true) & \code{divide=false}, only without \code{initguess} & \code{'n'}, $\beta=0$ and $\beta=1$ \\
\code{conjgrad} & -- & \code{divide=true}, if \code{precondition} & \code{'n'}; \code{'t'} if \code{doguess} or non-square \\
\code{gmres} & -- & \code{divide=true}, if \code{precondition} & \code{'n'}; \code{'t'} if \code{doguess} or non-square \\
\code{explicit_matrix} & -- & -- & \code{'n'}, $\beta=0$ \\
\bottomrule
\end{tabular}
\caption{Member functions of \code{Matrix} used by the iterative solvers; all of them also use \code{nrows()} and \code{ncols()}.}\label{tab:iterative:matrix}
\end{table}
\paragraph{Copy on demand.} The solvers keep the Krylov vectors as shallow copies of their work vectors
(sec.~\ref{sec:concepts:refcount}), so the vectors \code{r} passed to \code{gemv} and \code{diagonalof} frequently share their
data with stored vectors. A user implementation must therefore not write elements of \code{r} through \code{operator[]} or a raw
pointer before calling \code{r.copyonwrite()}; whole-object operations (\code{r=0.}, \code{r*=beta}, \code{r|=d}, \code{axpy},
and the \code{gemv} of the library classes) call it themselves. Forgetting it silently corrupts the Krylov space (in DEBUG
builds the non-const \code{operator[]} of a shared vector raises \code{laerror}). The input vector \code{x} is a const reference
and can be read freely. The library classes satisfy this.
\paragraph{Out-of-core storage.} \code{davidson}, \code{lanczos}, \code{gmres} and \code{DIIS} can keep their vectors in
scratch files instead of memory (parameter \code{incore}). They use \code{AuxStorage<T>} (\header{auxstorage.h},
chapter~\ref{ch:utilities}): a binary file with fixed-length records of raw elements, created by \code{mkostemp} as
\texttt{AUX\_XXXXXX} in \code{\$TMPDIR} (if set, otherwise in the current working directory) and unlinked at once, so no file
remains, even after a crash or an exception. Only one $n$-vector (two in
\code{davidson}) is then kept in memory per stored set besides the work vectors, at the price of reading all stored vectors in
every iteration. If a solver is left by an exception, the storage objects themselves are not freed (raw pointers), but the
disk space is released when the process ends.
\paragraph{Template argument deduction.} The element type \code{T} of \code{davidson} and \code{lanczos} is deduced from the
vector arguments. A literal \code{NULL} passed for one of the pointer parameters depending on \code{T} (\code{eivecs},
\code{initguess}, \code{target}) makes the deduction fail; either cast it (\code{(NRVec<double> *)NULL}, as in t.cc) or give
\code{T} explicitly: \code{davidson<double>(a,e,NULL,NULL,2)} (verified). Arguments left at their default values are not a
problem.
%-----------------------------------------------------------------------------------------------------------------------------
\section{Davidson diagonalization}\label{sec:iterative:davidson}
\laheader{davidson.h}
\begin{lareference}
\lafunc{template <typename T, typename Matrix> void davidson(const Matrix &bigmat, NRVec<T> &eivals, NRVec<T> *eivecs, const char *eivecsfile, int nroots=1, const bool verbose=0, const double eps=1e-6, const bool incore=1, int maxit=100, const int maxkrylov = 500, void (*initguess)(NRVec<T> &)=NULL, const typename LA_traits<T>::normtype *target=NULL)}{
Davidson's method \cite{iterative:davidson1975} for the \code{nroots} lowest eigenvalues of \code{bigmat}.
\begin{itemize}
\item \code{eivals}: on return the eigenvalues in its first \code{nroots} elements (ascending, or by increasing distance from
\code{*target}); it must have at least \code{nroots} elements (\code{laerror}), it is not resized.
\item \code{eivecs}: \code{NULL} or an array of at least \code{nroots} vectors receiving the normalized eigenvectors (by deep
copy, \code{|=}, so their previous size does not matter).
\item \code{eivecsfile}: \code{NULL} or a file name; the eigenvectors are then also written into this file as binary records
of $n$ elements of type \code{T}, record $i$ = root $i$ (an existing file is removed first), also if
\code{eivecs==NULL}.
\item \code{nroots}: number of roots; reduced silently to \code{maxkrylov-1} if larger.
\item \code{verbose}: print the eigenvalue estimates in each iteration and the convergence of each root to \code{std::cout}.
\item \code{eps}: convergence threshold (see below).
\item \code{incore}: keep the Krylov vectors $v_k$ and $Av_k$ in memory (\code{true}) or in two \code{AuxStorage} scratch files.
\item \code{maxit}: maximum number of iterations, i.e.\ of matrix-vector products after the initial one (restarts included).
\item \code{maxkrylov}: maximum dimension of the subspace; the subspace matrices are allocated with this dimension. When it
is reached, the method restarts (see below).
\item \code{initguess}: function filling its argument (allocated with $n$ elements, uninitialized) with the start vector,
which need not be normalized. By default the unit vector $e_j$ with $j$ the index of the lowest (real part of the) diagonal
element is used.
\item \code{target}: if not \code{NULL}, the subspace eigenpairs are sorted by $|\lambda-\mathtt{*target}|$ instead of
ascending order, i.e.\ the roots closest to the target value are sought.
\end{itemize}
Throws \code{laerror} if not converged within \code{maxit} iterations.}
\end{lareference}
\paragraph{Algorithm.} The method keeps the subspace vectors $v_k$ (not orthonormalized exactly, therefore the overlap matrix
$S_{jk}=v_j\cdot v_k$ is kept too), the products $Av_k$, and the subspace matrix $H_{jk}=v_j\cdot Av_k$. In each iteration the
generalized subspace eigenproblem $Hc=\lambda Sc$ is solved by \code{diagonalize} (symmetric case) or by \code{gdiagonalize}
with \code{sorttype=2} (real eigenvalues first, in ascending order; non-symmetric case, right eigenvectors; the imaginary parts
of complex eigenvalues are discarded). The roots are converged one after another: for the current root $\lambda$ with the
Ritz vector $y=\sum_kc_kv_k$ the residual $\rho=\sum_kc_k(Av_k-\lambda v_k)$ is formed and the correction vector is
$\delta_i=\rho_i/\mathrm{d}_i$ with $\mathrm{d}_i=A_{ii}-\lambda$, where $|\mathrm{d}_i|$ is limited from below by $0.1$
(keeping its sign). The correction is normalized and orthogonalized by Gram-Schmidt to all previous subspace vectors (the
projection on each $v_j$ is repeated while it removes more than 1\,\% of the norm) and appended to the subspace. Each iteration
costs one \code{gemv} with \code{'n'} and one \code{diagonalof}.
\paragraph{Convergence.} A root is considered converged when the coefficient of the most recently added subspace vector in its
subspace eigenvector, $|c_{\rm last}|$, is below \code{eps}; this is \emph{not} the residual norm, and the error of the
eigenvalue is typically much smaller than \code{eps}. After each iteration the method continues with the lowest root not yet
converged, and returns to a lower root if its $|c_{\rm last}|$ exceeds \code{eps} again. The method finishes successfully when
all \code{nroots} roots are converged, or (with a warning on \code{std::cout}) when the new correction vector vanishes before or
during the orthogonalization; in this case the current Ritz values are returned even if not all roots satisfy the criterion
(intended for tiny matrices). If $|c_{\rm last}|>100$ for the current root, ``Divergence in Davidson'' is printed and
\code{laerror} is called.
\paragraph{Restart.} When the subspace dimension reaches \code{maxkrylov}, the method restarts from the current approximation
of the lowest root alone, but only if no root has converged yet; otherwise it fails with \code{laerror}. For several roots
\code{maxkrylov} has therefore to be large enough to converge all of them without restart.
\paragraph{Element types.} \code{T=double} is the main case. For complex hermitian matrices (\code{T=std::complex<double>})
the method works, but according to the source comment ``does not converge so well for more than 1 root''; non-hermitian complex
matrices are not supported. The non-symmetric real case yields right eigenvectors belonging to real eigenvalues only.
The file has no header; it is read back with the \code{get} methods without dimensions, e.g.\ all roots as the rows of a matrix,
\code{NRMat<T> V(nroots,n); V.get(fd,false);}, or root by root with \code{NRVec<T>::get(fd,false)} (\code{fd} from
\code{open(eivecsfile,O_RDONLY)}). \code{AuxStorage(name)} cannot be used for reading, it recreates the file.
\begin{lareference}
\lafunc{template <typename T, typename Matrix> NRMat<T> explicit_matrix(const Matrix &bigmat)}{The explicit dense matrix of an
implicitly given one, built column by column by \code{gemv} on unit vectors (for debugging). \code{T} must be given:
\code{explicit_matrix<double>(a)}. Same as \code{reconstructmatrix} in chapter~\ref{ch:nonclass}.}
\end{lareference}
%-----------------------------------------------------------------------------------------------------------------------------
\section{Lanczos diagonalization}\label{sec:iterative:lanczos}
\laheader{lanczos.h}
\begin{lareference}
\lafunc{template <typename T, typename Matrix> void lanczos(const Matrix &bigmat, NRVec<T> &eivals, NRVec<T> *eivecs, const char *eivecsfile, int nroots=1, const bool verbose=0, const double eps=1e-6, const bool incore=1, int maxit=100, const int maxkrylov = 1000, void (*initguess)(NRVec<T> &)=NULL, const typename LA_traits<T>::normtype *target=NULL)}{
Lanczos method for the \code{nroots} lowest (or closest to \code{*target}) eigenvalues of a hermitian matrix. The parameters
have the same meaning as for \code{davidson}, with these differences: \code{bigmat.issymmetric()} must be \code{true}
(\code{laerror} otherwise); the start vector is normalized by the function; \code{incore=false} keeps only the Lanczos vectors
$v_k$ in one scratch file; the default \code{maxkrylov} is 1000.}
\end{lareference}
\paragraph{Algorithm.} Plain three-term Lanczos recurrence $w=Av_j-\beta_{j-1}v_{j-1}$, $\alpha_j=v_j\cdot w$,
$w\leftarrow w-\alpha_jv_j$, $\beta_j=|w|$, $v_{j+1}=w/\beta_j$, one \code{gemv} per iteration (with $\beta=1$ in the
\code{gemv} sense, accumulating into $-\beta_{j-1}v_{j-1}$). The vectors are not reorthogonalized, so that in long runs
copies of converged eigenvalues may appear (a known property of the method in finite precision). If $\beta_j=0$ (invariant
subspace), a random vector orthonormalized to all previous $v_k$ is used to continue. The tridiagonal matrix is diagonalized
in each iteration by \code{diagonalize3}. All $v_k$ are stored (in core or in the scratch file) to build the eigenvectors at the
end. If the diagonal elements $v_j\cdot Av_j$ have an imaginary part larger than $10^{-6}$, \code{laerror} reports a
non-hermitian matrix.
\paragraph{Convergence.} Converged when the changes of the \code{nroots} lowest (or closest to the target) eigenvalues
between two iterations are all $\le$ \code{eps}; this is checked once the dimension exceeds \code{nroots}. There is no restart:
the method fails with \code{laerror} when more than \code{maxit} iterations are needed or when the dimension reaches
\code{maxkrylov} (the effective limit is $\min(\mathtt{maxit},\mathtt{maxkrylov}-1)$ iterations).
%-----------------------------------------------------------------------------------------------------------------------------
\section{Conjugate gradients}\label{sec:iterative:conjgrad}
\laheader{conjgrad.h}
\begin{lareference}
\lafunc{template<typename T, typename Matrix> bool conjgrad(const Matrix &bigmat, const NRVec<T> &b, NRVec<T> &x, const bool doguess=true, const double tol=1e-8, const int itmax=1000, const bool verbose=true, bool issquare=1, const bool precondition=1)}{
Solves $Ax=b$ by the (Jacobi preconditioned) conjugate gradient method (the source refers to the book of Stoer and Bulirsch).
Returns \code{true} if converged, \code{false} after \code{itmax}+1 iterations without convergence; \code{x} then holds the
last iterate.
\begin{itemize}
\item \code{b}: right-hand side with \code{nrows()} elements; \code{x}: initial guess and solution, must have \code{ncols()}
elements (\code{laerror} otherwise).
\item \code{doguess}: overwrite \code{x} by the start vector $-A\Tr b$ (divided by the diagonal if \code{precondition}),
normalized to 1. With \code{false} the contents of \code{x} are the start vector.
\item \code{tol}: convergence threshold, see below.
\item \code{itmax}: maximum number of iterations; \code{verbose}: print the error in each iteration (default \code{true}).
\item \code{issquare}: \code{true} solves $Ax=b$ directly, which requires a symmetric positive definite matrix; \code{false}
applies CG to the normal equations $A\Tr Ax=A\Tr b$ (two \code{gemv} per iteration), usable for non-symmetric matrices.
It is set to \code{false} automatically for a rectangular matrix.
\item \code{precondition}: divide the residual by the diagonal (\code{diagonalof(r,true)}) in each iteration. The diagonal must
then be positive. For a square matrix with \code{issquare=false} the diagonal of $A$ (not of $A\Tr A$) is used, since
\code{diagonalof} cannot know that the normal equations are solved; this is not the proper preconditioner for $A\Tr A$ (the
Jacobi preconditioner would be the squared column norms) and may slow down or break the convergence if $A$ has non-positive
diagonal elements; use \code{precondition=false} then.
\end{itemize}}
\end{lareference}
\paragraph{Convergence.} The criterion is $|p|\le$ \code{tol} for the current search direction $p$ (an absolute criterion;
in the first iteration $p$ is the preconditioned residual). The function calls \code{laerror} (``singular matrix'') if
$p\cdot Ap$ (or $|Ap|^2$) or the residual product $r\cdot z$ vanishes. Only real \code{T} is supported (the scalars are
\code{double}).
%-----------------------------------------------------------------------------------------------------------------------------
\section{GMRES}\label{sec:iterative:gmres}
\laheader{gmres.h}
\begin{lareference}
\lafunc{template<typename T, typename Matrix> bool gmres(const Matrix &bigmat, const NRVec<T> &b, NRVec<T> &x, const bool doguess=1, const double eps=1e-7, const int MAXIT=50, const bool verbose=1, bool square=1, const bool precondition=1, int neustart=0, const int incore=1)}{
Solves $Ax=b$ by restarted GMRES with Givens rotations (after H.~R.~Schwarz, original implementation by M.~Warken). Returns
\code{true} if converged; otherwise \code{false}, with \code{x} holding the last iterate.
\begin{itemize}
\item \code{b}, \code{x}, \code{doguess}, \code{precondition}: as for \code{conjgrad}; the preconditioner $D^{-1}$ (Jacobi) is
applied from the left, i.e.\ GMRES minimizes $|D^{-1}(Ax-b)|$.
\item \code{eps}: relative threshold; converged when the estimated (preconditioned) residual norm is below
\code{eps}$\cdot\beta_0$, $\beta_0$ being the preconditioned residual norm of the start vector.
\item \code{MAXIT}: dimension of the Krylov space in one cycle; \code{MAXIT+1} vectors are stored.
\item \code{verbose}: print the residual estimate in each step (default \code{true}).
\item \code{square}: \code{false} solves the normal equations $A\Tr Ax=A\Tr b$; set automatically for a rectangular matrix.
\item \code{neustart}: maximum number of cycles (restarts); 0 means \code{nrows()/10}, and at least 10 cycles are always allowed.
\item \code{incore}: 0 stores the Krylov vectors in an \code{AuxStorage} scratch file.
\end{itemize}
On convergence the minimizer over the whole current Krylov space is returned. A breakdown ($|h_{k+1,k}|<0.01\,$\code{eps},
the Krylov space is invariant) ends the current cycle with the solution in that space; if it is not converged (singular
matrix), the next cycle starts from the new residual.
Calls \code{laerror} for a $1\times1$ system (\code{ncols()==1}), for incompatible sizes and for an exactly singular triangular
factor. The scalars are \code{double}, so only real \code{T} is supported.}
\lafunc{template<class T> void gmres_backsubstitute(const NRMat<T> &R, NRVec<T> &c, const NRVec<T> &d, const int k)}{Auxiliary:
solves the upper triangular system $R_{0..k,0..k}\,c=d$ (calls \code{c.copyonwrite()}; \code{c} must be allocated).}
\lafunc{template<typename T> void gmres_update(NRMat<T> &H, const NRVec<T> &cci, const NRVec<T> &ssi, NRVec<T> &d, NRVec<T> &c, const int k, NRVec<T> *v, AuxStorage<T> *st, const int incore, NRVec<T> &x)}{Auxiliary:
applies the Givens rotations $0..k$ to the Hessenberg matrix \code{H} (in place), solves $Rc=-d$ and adds $\sum_{i=0}^{k}c_iv_i$ to \code{x}.}
\end{lareference}
%-----------------------------------------------------------------------------------------------------------------------------
\section{DIIS convergence acceleration}\label{sec:iterative:diis}
\laheader{diis.h}
Direct inversion in the iterative subspace \cite{iterative:pulay1980,iterative:pulay1982}: given the last $m$ iterates $x_i$ of
some iterative procedure and their error vectors $e_i$, the extrapolated vector is $\sum_ic_ix_i$ with the coefficients
minimizing $|\sum_ic_ie_i|$ subject to $\sum_ic_i=1$, obtained from the linear system
\begin{equation}\label{eq:iterative:diis}
\begin{pmatrix}0&-1&\cdots&-1\\-1&B_{11}&\cdots&B_{1m}\\\vdots&\vdots&&\vdots\\-1&B_{m1}&\cdots&B_{mm}\end{pmatrix}
\begin{pmatrix}\lambda\\c_1\\\vdots\\c_m\end{pmatrix}=\begin{pmatrix}-1\\0\\\vdots\\0\end{pmatrix},\qquad B_{ij}=e_i\cdot e_j .
\end{equation}
\begin{lareference}
\lafunc{template<typename T, typename U> class DIIS}{\code{T} is the type of the iterated quantity, \code{U} the type of the
error vector, typically \code{NRVec}, \code{NRMat} or \code{NRSMat} (e.g.\ \code{T=NRSMat<double>} for a Fock or density
matrix and \code{U=NRMat<double>} for the commutator error). Both template arguments must be given (there is no default).
Requirements: \code{T} and \code{U} need \code{operator|=}, \code{clear()}, \code{axpy()}, \code{copyonwrite()} and the traits
\code{LA_traits<>::elementtype} and \code{normtype}; \code{U} also \code{dot()} and \code{norm()}. The element types of
\code{T} and \code{U} must be the same (the matrix of eq.~\eqref{eq:iterative:diis} is built over the element type of \code{U}
and converted to that of \code{T}). For out-of-core storage \code{T} and \code{U} must be \code{NRVec}, \code{NRMat} or
\code{NRSMat} of plain data (\code{AuxStorage}) and keep their size.}
\lafunc{DIIS()}{Uninitialized object (for arrays of \code{DIIS}); \code{setup()} must be called before use, otherwise
\code{extrapolate} calls \code{laerror}.}
\lafunc{DIIS(const int n, const bool core=1)}{Subspace of at most \code{n} stored iterates, kept in memory (\code{core=true}) or
in two \code{AuxStorage} scratch files.}
\lafunc{void setup(const int n, const bool core=1)}{(Re)initialize as by the constructor; the previous storage is released and
all stored vectors are forgotten. Can be called repeatedly.}
\lafunc{void reset()}{Forget all stored vectors (the next \code{extrapolate} starts a new subspace) but keep the allocated storage
and the dimension. Used to restart DIIS, e.g.\ after a step judged unreasonable (\code{GDIIS_Optimizer}, chapter~\ref{ch:optimize}).
After \code{reset()} the object behaves exactly as a freshly constructed one (tested in testoptimize.cc).}
\lafunc{typename LA_traits<U>::normtype extrapolate(T &vec, U &errvec, bool verbose=false, const Un diiseps=0, bool errvecout=false)}{
Stores deep copies of \code{vec} and \code{errvec} as the newest pair (when \code{n} pairs are already stored, the oldest is
dropped: circular buffer), updates $B$ with the products \code{errvec.dot(}$e_i$\code{)} and solves
eq.~\eqref{eq:iterative:diis} for the current number $m$ of stored pairs (\code{linear_solve} for \code{NRSMat},
chapter~\ref{ch:nonclass}). \code{vec} is overwritten by $\sum_ic_ix_i$, and with \code{errvecout=true} also \code{errvec} by
$\sum_ic_ie_i$ (the estimated error of the extrapolated vector). \code{diiseps} (type \code{LA_traits<U>::normtype}) is added
to the diagonal element $B_{mm}$ of the new vector as a regularization. \code{verbose} prints the solution vector
$(\lambda,c_1,\dots,c_m)$. Returns $|\mathtt{errvec}|$ of the input error vector. On the first call after construction, \code{setup} or \code{reset} the pair is only stored, \code{vec} and
\code{errvec} are unchanged, and the return value is 1.}
\end{lareference}
Typical use: in each iteration compute the new iterate $x$ and its error $e$ (e.g.\ $e=x_{\rm new}-x_{\rm old}$ for a
fixed-point iteration, or the commutator $FDS-SDF$ in SCF), call \code{diis.extrapolate(x,e)} and continue with the
extrapolated $x$. The stored copies are independent of the user's objects (deep copies by \code{|=}), so the user may modify
\code{vec} and \code{errvec} freely afterwards.
For complex \code{U} the matrix $B_{ij}=e_i^\dagger e_j$ is hermitian: \code{errvec.dot(}$e_i$\code{)}$=e_{\rm new}^\dagger e_i$ is
stored in the lower triangle of the complex \code{NRSMat}, which is hermitian in arithmetic (chapter~\ref{ch:smat}), and the
coefficients $c_i$ are complex (tested on a complex fixed-point iteration, in core and out of core).
%-----------------------------------------------------------------------------------------------------------------------------
\section{Search in sorted arrays}\label{sec:iterative:bisection}
\laheader{bisection.h}
The header is included by \header{la.h}; included alone it needs \code{laerror} declared before (it does not include
\header{laerror.h}).
\begin{lareference}
\lafunc{template<typename INDEX, typename COMPAR, typename SUBJECT> INDEX bisection_find(INDEX dm, INDEX hm, const SUBJECT *key, const SUBJECT *base, unsigned int lead_dimension_base, COMPAR (*cmp)(const SUBJECT *, const SUBJECT *))}{
Bisection search of \code{*key} among the elements \code{base[i*lead_dimension_base]}, $i=\mathtt{dm},\dots,\mathtt{hm}$
(inclusive), which must be sorted in ascending order with respect to \code{cmp}. \code{cmp(a,b)} returns 0 if equal, a positive
value if \code{*a} $>$ \code{*b}, negative otherwise; it is always called with the array element as the first argument.
\code{lead_dimension_base} allows searching e.g.\ the first column of a row-major matrix (\code{SUBJECT} is the element type, the
key is compared with one element only). Returns the index $i$ of a matching element, or \code{dm-1} if not found (also for
\code{dm>hm}). \code{INDEX} should be a signed type (for \code{dm=0} the failure value is $-1$).}
\lafunc{template<typename INDEX, typename DISTANCE, typename SUBJECT> int interpolation_find(INDEX dm0, INDEX high, const SUBJECT *x, const SUBJECT *base, unsigned int lead_dimension_base, DISTANCE (*dist)(const SUBJECT *, const SUBJECT *))}{
Interpolation search with the same layout and result convention (index or \code{dm0-1}). \code{dist(a,b)} is a signed distance
$a-b$ (positive if \code{*a} $>$ \code{*b}, zero if equal); the next probe is placed by linear interpolation, so the speed
depends on how well \code{dist} reflects the distribution of the keys. Calls \code{laerror} if an interpolated index falls
outside the current interval (inconsistent \code{dist}).}
\end{lareference}
%-----------------------------------------------------------------------------------------------------------------------------
\section{Examples}\label{sec:iterative:examples}
\subsection{Davidson with a user-defined direct matrix}
The matrix $H_{ij}=d_i\delta_{ij}+c\,(\delta_{i,j+1}+\delta_{i+1,j})$ is never stored; the class provides only the functions
of table~\ref{tab:iterative:matrix}. The program was compiled and run; the three eigenvalues agree with dense diagonalization
and the residual norms $|Hv-\lambda v|$ are $10^{-12}$--$10^{-10}$ for \code{eps=1e-8}.
\begin{lstlisting}
#include "la.h"
using namespace LA;
//H = diag(d) + c*(nearest neighbour coupling), never stored
class Chain {
NRVec<double> d;
double c;
public:
Chain(int n, double cc) : d(n), c(cc) {for(int i=0; i<n; ++i) d[i]=i+0.1*i*i;}
int nrows() const {return d.size();}
int ncols() const {return d.size();}
bool issymmetric() const {return true;}
const double *diagonalof(NRVec<double> &r, const bool divide=false, bool cache=false) const
{
if(divide) {r.copyonwrite(); for(int i=0; i<r.size(); ++i) r[i]/=d[i]; return NULL;}
r|=d; return &r[0]; //deep copy, r may share data with a stored Krylov vector
}
void gemv(const double beta, NRVec<double> &r, const char trans, const double alpha, const NRVec<double> &x) const
{
int n=d.size();
if(beta==0) r=0.; else r*=beta;
r.copyonwrite(); //before writing r[i]
for(int i=0; i<n; ++i)
{
double t=d[i]*x[i];
if(i>0) t+=c*x[i-1];
if(i<n-1) t+=c*x[i+1];
r[i]+=alpha*t;
}
}
};
int main()
{
const int n=2000, nroots=3;
Chain h(n,0.5);
NRVec<double> e(nroots);
NRVec<double> *v = new NRVec<double>[nroots];
davidson(h,e,v,NULL,nroots,false,1e-8); //vectors in memory
std::cout << "eigenvalues "<<e;
davidson(h,e,v,NULL,nroots,false,1e-8,false); //vectors in scratch files
lanczos(h,e,v,NULL,nroots,false,1e-10); //the same by Lanczos
NRMat<double> hd = explicit_matrix<double>(h); //for checking by dense diagonalization
delete[] v;
}
\end{lstlisting}
The \code{gemv} above ignores \code{trans} since the matrix is symmetric; for \code{conjgrad} or \code{gmres} on a
non-symmetric direct matrix, \code{'t'} has to be implemented as well (or \code{doguess=false} used with a square matrix).
\subsection{Linear systems and DIIS}
\begin{lstlisting}
NRMat<double> a(n,n); ... //non-symmetric, diagonally dominant
NRVec<double> b(n), x(n); ...
bool ok=gmres(a,b,x,true,1e-10,50,false); //x: solution
NRSMat<double> s(n); ... //symmetric positive definite
ok=conjgrad(s,b,x,true,1e-10,1000,false);
//DIIS acceleration of a fixed-point iteration x=f(x) (from testoptimize.cc)
DIIS<NRVec<double>,NRVec<double> > diis(4);
NRVec<double> x(0.,n);
for(int it=0; it<maxit; ++it)
{
NRVec<double> xn=f(x);
NRVec<double> err=xn-x;
diis.extrapolate(xn,err); //xn becomes the extrapolated vector
x=xn;
}
\end{lstlisting}
t.cc contains further calls: \code{davidson} on \code{NRMat}, \code{NRSMat}, \code{SparseMat} (symmetric and non-symmetric,
out of core), \code{lanczos} and \code{davidson} compared with dense diagonalization, and \code{gmres} on dense and sparse
matrices. testoptimize.cc tests the in-core and out-of-core \code{DIIS} and \code{reset()}.
%-----------------------------------------------------------------------------------------------------------------------------
\section{Pitfalls}\label{sec:iterative:pitfalls}
\begin{itemize}
\item A user \code{gemv} or \code{diagonalof} writing into \code{r} via \code{operator[]} without \code{r.copyonwrite()}
corrupts the stored Krylov vectors (sec.~\ref{sec:iterative:matrix}).
\item A literal \code{NULL} for \code{eivecs}, \code{initguess} or \code{target} breaks template argument deduction of
\code{davidson}/\code{lanczos}; cast it or write \code{davidson<double>(...)}.
\item \code{NRMat::issymmetric()} is \code{false}: \code{davidson} on an \code{NRMat} always uses the non-symmetric algorithm, and
\code{lanczos} refuses it. Use \code{NRSMat} (or a \code{SparseMat} with the symmetric flag) for symmetric matrices.
\item The Davidson threshold \code{eps} bounds the last expansion coefficient, the Lanczos one the change of the eigenvalues,
the CG one the norm of the search direction (absolute) and the GMRES one the residual relative to the initial one.
\item \code{davidson} with several roots fails if \code{maxkrylov} is reached after the first root has converged; \code{lanczos}
has no restart at all.
\item The diagonal used for preconditioning must have no small elements of the wrong sign (zero elements are skipped by the
library classes); in \code{davidson} the denominators $|A_{ii}-\lambda|$ are bounded by 0.1 from below.
\item \code{doguess=true} (the default of \code{conjgrad} and \code{gmres}) discards the contents of \code{x}.
\item Scratch files of the out-of-core variants are created in the current directory and survive an exception.
\item \code{DIIS} is not copyable (copy constructor and assignment are deleted, it owns raw storage); use references or pointers,
or default-construct it in arrays (\code{NRVec<DIIS<...> >}) and call \code{setup()}.
\end{itemize}
+65
View File
@@ -0,0 +1,65 @@
%common macros and package setup for the LA library manual
%all chapters use only these macros for code and reference entries, so that the layout can be changed in one place
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{lmodern}
\usepackage{amsmath,amssymb}
\usepackage{xcolor}
\usepackage{listings}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{array}
\usepackage[colorinlistoftodos,textsize=small]{todonotes}
\usepackage[hidelinks]{hyperref}
%code listings
\lstdefinestyle{lacode}{
language=C++,
basicstyle=\ttfamily\small,
keywordstyle=\bfseries,
commentstyle=\itshape\color{black!60},
columns=fullflexible,
keepspaces=true,
tabsize=8,
breaklines=true,
frame=leftline,
xleftmargin=1.5em,
framexleftmargin=0.5em,
showstringspaces=false,
escapechar=@
}
\lstset{style=lacode}
%inline code: \code{NRVec<double>}; underscores, &, <, > need no escaping (no % and no unbalanced braces)
\newcommand{\code}[1]{\texttt{\detokenize{#1}}}
%name of a header file
\newcommand{\header}[1]{\texttt{\detokenize{#1}}}
%header(s) where the described entities are declared, placed at the beginning of a chapter or section
\newcommand{\laheader}[1]{\par\noindent\textbf{Header:} \texttt{\detokenize{#1}}\par\medskip}
%reference list of functions; usage:
%\begin{lareference}
%\lafunc{void axpy(const T alpha, const NRVec &x)}{this $\leftarrow$ this + alpha x}
%\end{lareference}
\newenvironment{lareference}
{\begin{list}{}{\setlength{\leftmargin}{2em}\setlength{\itemindent}{-2em}\setlength{\itemsep}{3pt plus 1pt}\setlength{\parsep}{0pt}\setlength{\topsep}{4pt}}\raggedright}
{\end{list}}
\newcommand{\lafunc}[2]{\item{\ttfamily\small\detokenize{#1}}\par\nopagebreak\hspace*{2em}\begin{minipage}[t]{\dimexpr\linewidth-2em\relax}#2\end{minipage}}
%a remark visible in the text for the author to decide (unclear intended usage, possibly obsolete code, possible bug ...)
%(the list of todos shows only the section number, since the text may contain \code, which cannot be moved)
\newcommand{\latodo}[1]{\todo[inline,color=yellow!40,caption={Question in section \thesection}]{#1}}
%a warning paragraph (pitfalls)
\newcommand{\lawarning}[1]{\par\smallskip\noindent\fbox{\parbox{\dimexpr\linewidth-2\fboxsep-2\fboxrule\relax}{\textbf{Caution:} #1}}\par\smallskip}
%notation
\newcommand{\LA}{\textsf{LA}}
\newcommand{\Tr}{^{\mathsf T}}
\newcommand{\Hc}{^{\dagger}}
%long unbreakable code names: allow looser lines rather than overfull ones
\setlength{\emergencystretch}{3em}
BIN
View File
Binary file not shown.
+39
View File
@@ -0,0 +1,39 @@
%LA library manual - master file
%compile: pdflatex manual; pdflatex manual (twice, for contents and references)
%the visible \latodo notes are listed after the contents; to hide them, load todonotes with the option disable in lamacros.tex
\documentclass[11pt,a4paper]{report}
\input{lamacros}
\title{\LA: a C++ linear algebra library\\[1ex]\large User manual}
\author{Ji\v{r}\'{\i} Pittner}
\date{\today}
\begin{document}
\maketitle
\tableofcontents
\listoftodos[List of open questions for the author]
\include{introduction}
\include{building}
\include{concepts}
\include{vec}
\include{mat}
\include{smat}
\include{sparse}
\include{nonclass}
\include{iterative}
\include{optimize}
\include{tensor}
\include{fourindex}
\include{permutation}
\include{polynomial}
\include{vecmat3}
\include{quaternion}
\include{bitvector}
\include{regression}
\include{utilities}
\include{biblio}
\end{document}
+327
View File
@@ -0,0 +1,327 @@
\chapter{Dense matrices: NRMat}\label{ch:mat}
\laheader{mat.h, mat.cc, strassen.cc (sparse-related members: sparsemat.h, sparsemat.cc, sparsesmat.h, sparsesmat.cc)}
\section{Purpose}\label{sec:mat:purpose}
\code{NRMat<T>} is the general dense rectangular matrix of the \LA{} library, the
workhorse for everything that is passed to BLAS level~3 and LAPACK. It is a reference counted
handle to a contiguous row-major array of $n\times m$ elements of type \code{T}.
For symmetric/Hermitian matrices stored in packed form see \code{NRSMat} (chapter~\ref{ch:smat}),
for sparse matrices chapter~\ref{ch:sparse}, for vectors \code{NRVec} (chapter~\ref{ch:vec}).
Decompositions, eigenvalue problems, linear solvers and matrix functions (exponential etc.) are
free functions described in chapter~\ref{ch:nonclass}.
\code{NRMat_from1<T>} (section~\ref{sec:mat:from1}) is a thin derived class with 1-based
\code{operator()}.
\section{Storage and class-specific concepts}\label{sec:mat:storage}
\paragraph{Layout.} Element $A_{ij}$ ($0\le i<n$, $0\le j<m$) is stored at offset $i\,m+j$ of one
contiguous block, i.e.\ C row-major order (sec.~\ref{sec:concepts:storage}). BLAS calls use
\code{CblasRowMajor}; where Fortran routines are called, the transposition implied by the
column-major convention is handled internally. Dimensions are \code{int}; offsets are mostly
computed in \code{size_t}.
\paragraph{Reference counting.} \code{NRMat} follows the general copy-on-demand scheme of
section~\ref{sec:concepts:refcount}: the copy constructor and \code{operator=} share data,
\code{|=} makes a deep copy (sec.~\ref{sec:concepts:assign}), all modifying member functions
call \code{copyonwrite()} themselves, and an explicit \code{copyonwrite()} is needed only before writing
through \code{operator[]}, \code{operator()} or the raw pointer \code{operator T*()}.
In DEBUG builds the non-const \code{operator[]} and \code{operator()} raise \code{laerror} when the
reference count is larger than one (controlled by \code{_LA_count_check}); read elements of a
possibly shared matrix through a const reference.
\paragraph{Element types.} \code{mat.cc} explicitly instantiates \code{NRMat<T>} for
\code{double}, \code{std::complex<double>}, and the integer types \code{char}, \code{short}, \code{int},
\code{long}, \code{long long} and their unsigned variants. Many numerical members are only
specialized (BLAS-backed) for \code{double} and \code{complex<double>} and have no generic
definition, so using them with an integer type results in a link error. Table~\ref{tab:mat:types}
summarizes this. Nested element types (e.g.\ \code{NRMat<NRVec<double> >}) are supported by the
reference counting (type-nested \code{copyonwrite}, sec.~\ref{sec:concepts:traits}), but several
members are restricted to plain data (\code{submatrix(int,int,int,int)},
\code{storesubmatrix(int,int,const NRMat&)}, \code{operator&}).
\begin{table}[htbp]
\centering\small
\begin{tabular}{>{\raggedright\arraybackslash}p{0.55\linewidth}>{\raggedright\arraybackslash}p{0.35\linewidth}}
\toprule
Members & Available for \\
\midrule
\texttt{gemm}, \texttt{norm}, \texttt{axpy(T,const NRMat\&)}, \texttt{dot}, \texttt{transpose}, \texttt{diagmultl}, \texttt{diagmultr},
\texttt{operator*(const NRSMat\&)}, \texttt{diagonalset}, \texttt{inverse}, \texttt{svdinverse}, \texttt{absadjoint},
\texttt{randomize}, \texttt{amax}, \texttt{amin} & \texttt{double}, \texttt{complex<double>} \\
\texttt{operator*(const NRMat\&)} & \texttt{double}, \texttt{complex<double>}, \texttt{int} \\
\texttt{diagonalof}, \texttt{orthonormalize}, \texttt{strassen}, \texttt{gemm} with \texttt{SparseMat} & \texttt{double} only \\
\texttt{nonhermiticity} & \texttt{complex<double>} only \\
everything else & all instantiated types \\
\bottomrule
\end{tabular}
\caption{Availability of \texttt{NRMat<T>} members for the instantiated element types.}\label{tab:mat:types}
\end{table}
\paragraph{The MATPTR representation.}\label{sec:mat:matptr}
\code{configure --enable-matptr} adds \code{-DMATPTR}, which switches the internal pointer from
\code{T *v} to \code{T **v}: an array of row pointers into the same single contiguous block
(\code{v[0]} points to the beginning). For the user the interface is the same: \code{operator[](i)}
returns a \code{T*} to the beginning of row $i$ in both cases, \code{operator()(i,j)} and
\code{operator T*()} (pointer to the whole contiguous row-major block) behave identically,
so code passing the matrix to BLAS is unaffected. Differences:
\begin{itemize}
\item \code{NRMat(const NRVec<T>&, n, m, offset)} and \code{NRVec(const NRMat<T>&)} make deep
copies with MATPTR, whereas without it they share the data with the source.
\item It is incompatible with CUDA (MATPTR is undefined when \code{CUDALA} is set).
\end{itemize}
MATPTR is rarely used and less tested than the default representation; the library compiles with it and passes the optimization
regression test (\code{testoptimize}).
\paragraph{Strassen multiplication.}\label{sec:mat:strassen}
Unless \code{NO_STRASSEN} is defined, \code{NRMat} declares \code{strassen()} and \code{s_cutoff()}
(specialized for \code{double} only, in \code{strassen.cc}). They call the external Fortran-compatible
routines \code{fmm} and \code{strassen_cutoff}, which are \emph{not} part of \LA{}
(the test code in \code{t.cc} mentions linking \code{-lstrassen}). Strassen multiplication is never
used implicitly: \code{operator*} and \code{gemm} always call BLAS \code{gemm}. Note that
\code{Makefile.am} unconditionally adds \code{-DNO_STRASSEN}, so in the standard build these members do not exist.
\lawarning{The Strassen interface is \emph{experimental}: it has not been used or tested for a long time. It is kept for possible future work,
in particular if fast (sub-$O(n^3)$) matrix multiplication algorithms become practically useful.}
\section{Construction, assignment, conversion}\label{sec:mat:constr}
\begin{lareference}
\lafunc{NRMat()}{Empty matrix ($0\times 0$, no data, no reference counter).}
\lafunc{NRMat(const int n, const int m, const GPUID loc = undefined)}{$n\times m$ matrix, elements \emph{not} initialized.}
\lafunc{NRMat(const T &a, const int n, const int m); NRMat(const T &a, const int n, const int m, const GPUID loc)}{$n\times m$ matrix with \emph{all} elements equal to \code{a} (contrast with \code{operator=(const T&)}).}
\lafunc{NRMat(const T *a, const int n, const int m)}{Deep copy of $nm$ elements from a row-major C array.}
\lafunc{template<int R, int C> NRMat(const T (&a)[R][C])}{From a two-dimensional C array; also allows brace initialization \code{NRMat<int> x({{1,2,3},{4,5,6}});}.}
\lafunc{NRMat(const Mat3<T> &rhs)}{$3\times3$ matrix from the small fixed-size class \code{Mat3} (chapter~\ref{ch:vecmat3}).}
\lafunc{explicit NRMat(const int n) = delete}{A single integer argument is forbidden (it would otherwise silently create a $3\times3$ matrix
through \code{Mat3}); use \code{NRMat(n,m)}.}
\lafunc{NRMat(const NRMat &rhs)}{Shallow copy (shares data).}
\lafunc{NRMat(const typename LA_traits_complex<T>::NRMat_Noncomplex_type &rhs, bool imagpart = false)}{Complexifying (non-explicit) constructor, defined for \code{T = complex<double>}: \code{rhs} (\code{NRMat<double>}) becomes the real part (or the imaginary part if \code{imagpart}), the other part is zero.}
\lafunc{explicit NRMat(const NRMat<std::complex<T> > &rhs, bool imagpart = false)}{Decomplexifying constructor, defined for \code{T = double}: real part (or imaginary part if \code{imagpart}) of \code{rhs}.}
\lafunc{explicit NRMat(const NRSMat<T> &rhs)}{Full square matrix from a packed symmetric one ($A_{ij}=A_{ji}$; for complex types hermitian, $A_{ij}=A_{ji}^*$).}
\lafunc{explicit NRMat(const NRVec<T> &rhs, const int n, const int m, const int offset = 0)}{View of the vector data from position \code{offset} as an $n\times m$ matrix. Without MATPTR the data are \emph{shared} (reference count of the vector is incremented); with MATPTR they are copied. A nonzero offset gives a view into the middle of the vector without copying (a deliberate feature);
its condition of use is that the vector outlives the matrix, see Pitfalls. The constructor prints a reminder to \code{std::cout}.}
\lafunc{explicit NRMat(const NRPerm<int> &p, const bool direction, const bool parity = false)}{Permutation matrix: $A_{i,p(i)}=1$ if \code{direction}, else $A_{p(i),i}=1$ (permutation 1-based, matrix 0-based); if \code{parity}, the nonzero elements are the parity of $p$ instead of 1.}
\lafunc{explicit NRMat(const WeightPermutation<int,T> &p, const bool direction)}{Weight times permutation matrix.}
\lafunc{explicit NRMat(const PermutationAlgebra<int,T> &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<T> &rhs); explicit NRMat(const SparseSMat<T> &rhs); explicit NRMat(const SemiSparseMat<T> &rhs)}{Dense matrix from sparse ones (chapter~\ref{ch:sparse}).}
\lafunc{explicit NRMat(const CSRMat<T> &rhs)}{Not supported: \code{CSRMat} is unfinished, the declaration is kept for possible future work (no definition, a link error if used).}
\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.}
\lafunc{NRMat & operator=(const T &a)}{Sets the matrix to $a\,\mathbf 1$ (diagonal $a$, off-diagonal 0); the matrix must already be square (checked in DEBUG only), it is not resized.}
\lafunc{void identity()}{\code{*this = (T)1}.}
\lafunc{void resize(int n, int m)}{Change dimensions; if either is 0 the matrix is deallocated. If the data are shared, the matrix is detached and new (uninitialized) storage allocated; if the size changes, the contents are lost (not preserved); if the size is unchanged and unshared, nothing happens.}
\lafunc{void dealloc(void)}{\code{resize(0,0)}.}
\lafunc{void copyonwrite(bool detachonly = false, bool deep = true)}{Make the data unshared (sec.~\ref{sec:concepts:refcount}). With \code{detachonly} new storage is allocated but the data are not copied (contents undefined); \code{deep} controls the recursive \code{copyonwrite} of nested element types.}
\lafunc{int getcount() const}{Current reference count (0 for an empty matrix).}
\lafunc{void swap(NRMat &rhs)}{Exchange contents of two matrices in $O(1)$ (pointers, sizes, counters).}
\lafunc{GPUID getlocation() const; void moveto(const GPUID dest)}{GPU support, experimental, see sec.~\ref{sec:concepts:gpu}; without \code{CUDALA} they return \code{cpu} resp.\ do nothing.}
\end{lareference}
\section{Element access}\label{sec:mat:access}
\begin{lareference}
\lafunc{T* operator[](const int i); const T* operator[](const int i) const}{Pointer to the beginning of row $i$ (0-based), so that \code{a[i][j]} is $A_{ij}$. DEBUG: row range check, and the shared-data check for the non-const version (the column index is not checked).}
\lafunc{T& operator()(const int i, const int j); const T& operator()(const int i, const int j) const}{Element $A_{ij}$, 0-based; DEBUG: range checks for both indices, and shared-data check for the non-const version. CPU only (\code{NOT_GPU}).}
\lafunc{const T get_ij(const int i, const int j) const}{Element by value (works also for data in GPU memory).}
\lafunc{operator T*(); operator const T*() const}{Pointer to the contiguous row-major data block (no reference count check even in DEBUG).}
\lafunc{int nrows() const; int ncols() const; size_t size() const}{Number of rows, columns, elements.}
\end{lareference}
\section{Arithmetic and BLAS/LAPACK-backed operations}\label{sec:mat:arith}
Unless stated otherwise, dimension mismatches are detected only in DEBUG builds.
Note that scalar addition and subtraction act on the \emph{diagonal} only, i.e.\ they add $a\,\mathbf 1$.
\begin{lareference}
\lafunc{NRMat & operator+=(const T &a); NRMat & operator-=(const T &a)}{$A\leftarrow A\pm a\,\mathbf 1$ (square matrices only).}
\lafunc{inline const NRMat operator+(const T &a) const; inline const NRMat operator-(const T &a) const}{$A\pm a\,\mathbf 1$ by value (the scalar is added to the diagonal).}
\lafunc{NRMat & operator*=(const T &a); inline const NRMat operator*(const T &a) const}{Scaling by a scalar (BLAS \code{scal} for double/complex).}
\lafunc{const NRMat operator-() const}{Unary minus.}
\lafunc{NRMat & operator+=(const NRMat &rhs); NRMat & operator-=(const NRMat &rhs)}{$A\leftarrow A\pm B$ (BLAS \code{axpy} for double/complex).}
\lafunc{inline const NRMat operator+(const NRMat &rhs) const; inline const NRMat operator-(const NRMat &rhs) const}{$A\pm B$ by value.}
\lafunc{NRMat & operator+=(const NRSMat<T> &rhs); NRMat & operator-=(const NRSMat<T> &rhs); inline const NRMat operator+(const NRSMat<T> &rhs) const; inline const NRMat operator-(const NRSMat<T> &rhs) const}{Addition/subtraction of a packed symmetric matrix (square $A$).}
\lafunc{NRMat & operator^=(const NRMat<T> &rhs)}{Hadamard (element-wise) product $A_{ij}\leftarrow A_{ij}B_{ij}$.}
\lafunc{void axpy(const T alpha, const NRMat &x)}{$A\leftarrow A+\alpha X$ (BLAS \code{axpy}).}
\lafunc{const NRMat operator*(const NRMat &rhs) const}{Matrix product $AB$ (BLAS \code{gemm}; simple loops for \code{int}).}
\lafunc{void gemm(const T &beta, const NRMat &a, const char transa, const NRMat &b, const char transb, const T &alpha)}{$C\leftarrow\alpha\,\mathrm{op}(A)\,\mathrm{op}(B)+\beta C$ with $C$ = \code{*this}, which must already have the correct dimensions (it is not resized; if $\beta=0$ its contents may be uninitialized). \code{trans}: \code{'n'} no transposition; for \code{double} any other character means transposition; for \code{complex<double>} \code{'t'} transposition and \code{'c'} conjugate transposition (case insensitive). Returns immediately when $\alpha=0$ and $\beta=1$. \code{*this} must not share storage with \code{a} or \code{b}.}
\lafunc{void gemm(const T &beta, const SparseMat<T> &a, const char transa, const NRMat &b, const char transb, const T &alpha)}{The same with a sparse first factor; \code{double} only (the complex version calls \code{laerror("not implemented yet")}), see chapter~\ref{ch:sparse}.}
\lafunc{const NRMat operator*(const NRSMat<T> &rhs) const}{$AS$ with packed symmetric (real) or hermitian (complex) $S$ (BLAS \code{dspmv}/\code{zhpmv} per row).}
\lafunc{SparseSMat<T> operator*(const SparseSMat<T> &rhs) const}{Dense times sparse, the result is sparse (chapter~\ref{ch:sparse}).}
\lafunc{const NRVec<T> operator*(const NRVec<T> &rhs) const; const NRVec<std::complex<T> > operator*(const NRVec<std::complex<T> > &rhs) const}{Matrix--vector product via \code{NRVec::gemv} (the second form: real matrix times complex vector).}
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x) const; void gemv(const T beta, NRVec<std::complex<T> > &r, const char trans, const T alpha, const NRVec<std::complex<T> > &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}).}
\lafunc{const NRSMat<T> transposedtimes() const}{$A\Tr A$ ($A\Hc A$ for complex) as packed matrix.}
\lafunc{const NRSMat<T> timestransposed() const}{$AA\Tr$ ($AA\Hc$ for complex) as packed matrix. For complex matrices both results are hermitian (lower triangle stored).}
\lafunc{void diagmultl(const NRVec<T> &rhs); void diagmultr(const NRVec<T> &rhs)}{$A\leftarrow DA$ (scale rows) resp.\ $A\leftarrow AD$ (scale columns), $D=\mathrm{diag}(\mathtt{rhs})$.}
\lafunc{const NRMat operator&(const NRMat &rhs) const; const NRMat oplus(const NRMat &rhs) const}{Direct sum $A\oplus B$ (block-diagonal matrix). \code{oplus} returns the other operand when one is empty; \code{operator&} is plain-data only.}
\lafunc{const NRMat operator|(const NRMat<T> &rhs) const}{Direct (Kronecker) product $A\otimes B$, $(A\otimes B)_{i n_B+k,\,j m_B+l}=A_{ij}B_{kl}$.}
\lafunc{const NRMat otimes(const NRMat &rhs, bool reversecolumns = false) const}{Kronecker product as \code{operator|}; with \code{reversecolumns} the column index is ordered the other way, $R_{i n_B+k,\,l m_A+j}=A_{ij}B_{kl}$. Returns the empty operand when one is empty.}
\lafunc{void strassen(const T beta, const NRMat &a, const char transa, const NRMat &b, const char transb, const T alpha); void s_cutoff(const int, const int, const int, const int) const}{Only without \code{NO_STRASSEN}, \code{double} only: Strassen multiplication with \code{gemm} semantics (dimensions always checked) and setting of the cutoffs of the external Strassen library (section~\ref{sec:mat:strassen}).}
\end{lareference}
\section{Other member functions}\label{sec:mat:other}
\subsection{Rows, columns, submatrices}
\begin{lareference}
\lafunc{const NRVec<T> row(const int i, int len = -1, int offset = 0) const}{Copy of \code{len} elements of row $i$ starting at column \code{offset} (\code{len<0}: \code{ncols()}).}
\lafunc{void rowset(const NRVec<T> &r, const int i, int len = -1, int offset = 0)}{Inverse of \code{row}: stores \code{len} elements of \code{r} into row $i$ from column \code{offset}.}
\lafunc{const NRVec<T> column(const int j, int l = -1) const; void columnset(const NRVec<T> &r, const int j, int l = -1)}{Copy of the first \code{l} (default all) elements of column $j$, resp.\ store them.}
\lafunc{const NRMat submatrix(const int fromrow, const int torow, const int fromcol, const int tocol) const}{Copy of the block of rows \code{fromrow..torow} and columns \code{fromcol..tocol} (inclusive). Plain data only.}
\lafunc{const NRMat submatrix(const NRVec<int> &rows, const NRVec<int> &cols) const}{Copy of the elements $A_{r_i c_j}$ for given (arbitrary) lists of row and column indices; indices are always range-checked.}
\lafunc{void storesubmatrix(const int fromrow, const int fromcol, const NRMat &rhs)}{Store \code{rhs} as a block with the upper left corner at (\code{fromrow},\code{fromcol}). Plain data only.}
\lafunc{void storesubmatrix(const NRVec<int> &rows, const NRVec<int> &cols, const NRMat &rhs)}{Scatter: $A_{r_i c_j}\leftarrow R_{ij}$.}
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide = 0, bool cache = false) const}{\code{double} only. For a square matrix copy the diagonal into \code{r}, or with \code{divide} divide \code{r} element-wise by the (nonzero) diagonal elements. For a non-square matrix the diagonal of $A\Tr A$ (squared column norms) is used instead; \code{r} must have \code{ncols()} elements. Returns pointer to \code{r}'s data, or NULL if \code{divide}. \code{cache} is unused. Same interface as in other matrix classes, so that it can be used by iterative solvers as a preconditioner (chapter~\ref{ch:iterative}).}
\lafunc{void diagonalset(const NRVec<T> &r)}{Set the diagonal; \code{r} has $\min(n,m)$ elements.}
\end{lareference}
\subsection{Transposition and conjugation}
\begin{lareference}
\lafunc{const NRMat transpose(bool conj = false) const}{$A\Tr$, or $A\Hc$ if \code{conj} (complex). \code{double} and \code{complex<double>} only.}
\lafunc{NRMat& transposeme(const int n = 0)}{In-place transposition of a square matrix (no conjugation); with \code{n>0} only the leading $n\times n$ block is transposed (this also works for a non-square matrix with $n\le\min(\text{rows},\text{cols})$). Dimensions are not changed, so a non-square matrix cannot be transposed in place.}
\lafunc{NRMat& conjugateme(); const NRMat conjugate() const}{Complex conjugation in place resp.\ by value; no-op for real types.}
\end{lareference}
\subsection{Reordering, permutations, rotations, scaling}
\begin{lareference}
\lafunc{NRMat & swap_rows(); NRMat & swap_cols()}{Reverse the order of rows (columns).}
\lafunc{NRMat & swap_rows_cols()}{$A_{ij}\leftarrow A_{n-1-i,\,m-1-j}$ (reverse both, i.e.\ the whole element array).}
\lafunc{NRMat & swap_rows(const int i, const int j); NRMat & swap_cols(const int i, const int j)}{Exchange two rows (columns).}
\lafunc{NRMat & rotate_rows(const int i, const int j, const T phi); NRMat & rotate_cols(const int i, const int j, const T phi)}{Plane rotation of rows (columns) $i,j$: $x_i\leftarrow c\,x_i+s\,x_j$, $x_j\leftarrow c\,x_j-s\,x_i$ with $c=\cos\phi$, $s=\sin\phi$ (evaluated in type \code{T}).}
\lafunc{void scale_row(const int i, const T f); void scale_col(const int i, const T f)}{Multiply row (column) $i$ by $f$.}
\lafunc{const NRMat permuted_rows(const NRPerm<int> &p, const bool inverse = false) const}{Row $i$ of $A$ becomes row $p(i)$ of the result (indices of $p$ are 1-based); with \code{inverse} row $i$ of the result is row $p(i)$ of $A$.}
\lafunc{const NRMat permuted_cols(const NRPerm<int> &p, const bool inverse = false) const; const NRMat permuted_both(const NRPerm<int> &p, const NRPerm<int> &q, const bool inverse = false) const}{The same for columns, resp.\ rows by $p$ and columns by $q$ simultaneously.}
\lafunc{void permuteme_rows(const CyclePerm<int> &p); void permuteme_cols(const CyclePerm<int> &p)}{In-place permutation given in cycle notation: in each cycle $(c_1\,c_2\ldots c_L)$ row (column) $c_k$ moves to $c_{k+1}$ and $c_L$ to $c_1$.}
\lafunc{void axpy(const T alpha, const NRPerm<int> &p, const bool direction)}{Add $\alpha$ times the permutation matrix (same convention as the constructor from \code{NRPerm}).}
\end{lareference}
\subsection{Reductions, norms, properties}
\begin{lareference}
\lafunc{const typename LA_traits<T>::normtype norm(const T scalar = (T)0) const}{Frobenius norm $\|A-s\,\mathbf 1\|_F$ (BLAS \code{nrm2} when $s=0$).}
\lafunc{const T trace() const}{$\sum_i A_{ii}$ (square matrices).}
\lafunc{const T sum() const; const NRVec<T> rsum() const; const NRVec<T> csum() const}{Sum of all elements; vector of row sums (length \code{nrows()}, sum over the column index); vector of column sums (length \code{ncols()}).}
\lafunc{inline const T amax() const; inline const T amin() const}{The element (not its index) with the largest resp.\ smallest absolute value; for complex the measure is $|\mathrm{Re}|+|\mathrm{Im}|$ (BLAS \code{i?amax} convention).}
\lafunc{const typename LA_traits<T>::normtype nonsymmetry() const}{$\sum_{i>j}|A_{ij}-A_{ji}|^2$ (square of the norm, no square root).}
\lafunc{const typename LA_traits<T>::normtype nonhermiticity() const}{\code{complex<double>} only: $\sum_{i\ge j}|A_{ij}-A_{ji}^*|^2$.}
\lafunc{NRMat<typename LA_traits<T>::normtype> abs() const}{Matrix of element-wise absolute values.}
\lafunc{NRMat<typename LA_traits<T>::normtype> diffabs(const NRMat &rhs) const}{Element-wise $|A_{ij}|-|B_{ij}|$.}
\lafunc{const bool operator==(const NRMat &rhs) const; const bool operator!=(const NRMat &rhs) const}{Equality of dimensions and contents; for plain types a bitwise (\code{memcmp}) comparison, element-wise for nested types.}
\lafunc{bool issymmetric() const; inline void simplify()}{Always \code{false} resp.\ no-op; they exist for interface compatibility with the sparse matrix classes, so that generic code can use \code{NRMat} in their place.}
\end{lareference}
\subsection{Orthonormalization, inverse, random fill}
\begin{lareference}
\lafunc{void orthonormalize(const bool rowcol, const NRSMat<T> *metric = NULL)}{\code{double} only. Modified Gram--Schmidt orthonormalization of the rows (\code{rowcol==true}) or columns (\code{false}) of the matrix in place, optionally with respect to the inner product $x\Tr S y$ given by \code{metric}. A zero vector (or non-positive metric) raises \code{laerror}.}
\lafunc{NRMat inverse() const}{$A^{-1}$ via \code{calcinverse} (LAPACK linear solve, chapter~\ref{ch:nonclass}).}
\lafunc{NRMat svdinverse(const typename LA_traits<T>::normtype thr = 0) const}{Pseudo-inverse via SVD, singular values below \code{thr} are dropped.}
\lafunc{NRMat absadjoint(const typename LA_traits<T>::normtype thr = 0) const}{Adjugate (transposed cofactor) matrix computed via SVD up to the factor $\det U\det V$ (a sign, or a phase in the complex case), cf.\ \code{calcsvdinverse} in chapter~\ref{ch:nonclass}. \code{thr} is not used in this mode.}
\lafunc{void randomize(const typename LA_traits<T>::normtype &x)}{Fill with uniform pseudorandom numbers from $[-x,x)$ (\code{RANDDOUBLESIGNED}); for complex, real and imaginary parts independently. (The source comment saying $[0,x]$ is incorrect.)}
\lafunc{void clear()}{Set all elements to zero (detaches shared data without copying).}
\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 transposed = false, bool orcaformat = false) const}{Binary output to a file descriptor: optionally the two dimensions (as \code{int}), then the elements row by row. With \code{transposed} the matrix is written as its transpose (dimensions swapped, element by element). With \code{orcaformat} \code{sizeof(T)} is written as a third \code{int} after the dimensions (format of the ORCA program).}
\lafunc{void get(int fd, bool dimensions = 1, bool transposed = false, bool orcaformat = false)}{Binary input, counterpart of \code{put}; with \code{dimensions} the matrix is resized, otherwise it must already have the right size. With \code{orcaformat} the stored element size is checked.}
\lafunc{void fprintf(FILE *f, const char *format, const int modulo) const}{Formatted output via \code{lawritemat} with a printf-like \code{format}.}
\lafunc{void fscanf(FILE *f, const char *format)}{Formatted input: reads the two dimensions, resizes, then the elements with the scanf-like \code{format}.}
\lafunc{void printsorted(std::ostream &s, int direction = 1, bool absvalue = false, bool stable = false, typename LA_traits<T>::normtype thr = 0, int topmax = 0) const}{Print the elements sorted by value (or absolute value), each line containing row index, column index and value; delegates to \code{NRVec::printsorted} (chapter~\ref{ch:vec}), where \code{direction}, \code{stable}, threshold \code{thr} and maximum count \code{topmax} are described.}
\end{lareference}
\subsection{Sparse matrix interoperability}
Besides the constructors, \code{operator*(const SparseSMat<T>&)} and \code{gemm} with a \code{SparseMat}
factor listed above, \code{NRMat} declares
\begin{lareference}
\lafunc{NRMat & operator+=(const SparseMat<T> &rhs); NRMat & operator-=(const SparseMat<T> &rhs)}{Add/subtract the sparse matrix (defined in \code{sparsemat.h}; a symmetric \code{SparseMat} is unfolded, without conjugation).}
\end{lareference}
\section{Related free functions}\label{sec:mat:free}
\begin{lareference}
\lafunc{template<class T> const NRMat<T> operator+(const T &a, const NRMat<T> &rhs); template<class T> const NRMat<T> operator*(const T &a, const NRMat<T> &rhs)}{$a\,\mathbf 1+A$ and $aA$.}
\lafunc{template<class T> const NRMat<T> operator-(const T &a, const NRMat<T> &rhs) = delete}{Deliberately not available (compile error); write \code{-A+a} for $a\,\mathbf 1-A$.}
\lafunc{template<typename T> NRMat<std::complex<T> > complexify(const NRMat<T> &rhs)}{Complex matrix with real part \code{rhs}.}
\lafunc{template<typename T> NRMat<std::complex<T> > complexify(const NRMat<T> &rhsr, const NRMat<T> &rhsi)}{Complex matrix from real and imaginary parts.}
\lafunc{template <typename T, typename S> void NRMat_convert(NRMat<T> &a, const NRMat<S> &b)}{Element type conversion: resizes \code{a} and assigns \code{(T)b(i,j)}.}
\lafunc{template <typename T> std::ostream& operator<<(std::ostream &s, const NRMat<T> &x)}{Text output: a line with the dimensions, then one line per row.}
\lafunc{template <typename T> std::istream& operator>>(std::istream &s, NRMat<T> &x)}{Text input in the same format (resizes \code{x}).}
\end{lareference}
\section{NRMat\_from1: indexing from 1}\label{sec:mat:from1}
\code{NRMat_from1<T>} is derived publicly from \code{NRMat<T>} and differs only in
\code{operator()} and \code{get_ij}, which take 1-based indices ($1\le i\le n$, $1\le j\le m$);
it is meant for transcribing algorithms written with Fortran-like indexing. Everything else is
inherited unchanged, in particular \code{operator[]} remains 0-based (\code{a[i][j]} is element
$(i{+}1,j{+}1)$) and member functions taking indices (\code{row}, \code{submatrix}, \ldots) use 0-based
indices. Inherited members return \code{NRMat<T>}, which converts back implicitly.
\begin{lareference}
\lafunc{NRMat_from1(); NRMat_from1(const int n, const int m); NRMat_from1(const T &a, const int n, const int m); NRMat_from1(const T *a, const int n, const int m)}{As the corresponding \code{NRMat} constructors.}
\lafunc{template<int R, int C> explicit NRMat_from1(const T (&a)[R][C])}{From a two-dimensional C array / brace initializer.}
\lafunc{NRMat_from1(const NRMat<T> &rhs)}{Implicit conversion from the base class (shallow, shares data).}
\lafunc{explicit NRMat_from1(const NRSMat_from1<T> &rhs)}{Full matrix from a packed symmetric one.}
\lafunc{explicit NRMat_from1(const int n) = delete}{A single integer argument is forbidden (as for \code{NRMat} and \code{Mat3}); use \code{NRMat_from1(n,m)}.}
\lafunc{inline T& operator()(const int i, const int j); inline const T& operator()(const int i, const int j) const; inline const T get_ij(const int i, const int j) const}{1-based element access, with the same DEBUG checks as in \code{NRMat}.}
\end{lareference}
\section{Examples}\label{sec:mat:examples}
Products, reference counting, orthonormalization and submatrices:
\begin{lstlisting}
#include "la.h"
using namespace LA;
using namespace std;
int main()
{
NRMat<double> a(4,3), b(3,5);
a.randomize(1.); b.randomize(1.);
NRMat<double> c = a*b; // dgemm
NRMat<double> d(3,3); // result must have the right shape
d.gemm(0., a, 't', a, 'n', 1.); // d = a^T a
NRSMat<double> s = a.transposedtimes();// the same, packed symmetric
cout << (d - NRMat<double>(s)).norm() << endl;
NRMat<double> e = c; // shares data with c
e.copyonwrite(); // needed: we write through operator()
e(0,0) = 1.;
const NRMat<double> &cc = c; // read-only access via const reference
cout << cc(0,0) << " " << e(0,0) << endl;
NRMat<double> u(3,3); u.randomize(1.);
u.orthonormalize(false); // orthonormal columns
cout << (u.transpose()*u - 1.).norm() << endl; // "- 1." subtracts the unit matrix
NRMat<double> blk = c.submatrix(1,2,0,4); // rows 1..2, all columns
NRVec<double> r = c.row(3);
NRMat<double> big(0., 6, 6);
big.storesubmatrix(2, 1, blk);
cout << blk << r << big.trace() << endl;
return 0;
}
\end{lstlisting}
Complex matrices, 1-based indexing, Kronecker product and direct sum:
\begin{lstlisting}
NRMat_from1<int> m({{1,2,3},{4,5,6}}); // 2x3, indices from 1
const NRMat_from1<int> &mc = m;
cout << mc(2,3) << " " << mc[1][2] << endl; // both print 6: operator[] stays 0-based
NRMat<double> re(2,2), im(2,2);
re.randomize(1.); im.randomize(1.);
NRMat<complex<double> > z = complexify(re, im);
NRMat<complex<double> > h(2,2);
h.gemm(0., z, 'c', z, 'n', 1.); // h = z^dagger z
cout << h.nonhermiticity() << endl; // 0
NRMat<double> rp(z); // real part (explicit constructor)
NRMat<double> ip(z, true); // imaginary part
NRMat<double> k = re.otimes(im); // Kronecker product, 4x4
NRMat<double> ds = re.oplus(im); // direct sum, 4x4 block diagonal
\end{lstlisting}
\section{Pitfalls}\label{sec:mat:pitfalls}
\lawarning{Scalars mean multiples of the unit matrix: \code{A = 1.} makes a unit matrix (of the current, square size), \code{A + a} and \code{A -= a} change only the diagonal. In contrast \code{NRMat<double> A(a, n, m)} fills \emph{all} elements with \code{a}. \code{a - A} does not compile; write \code{-A + a}.}
\lawarning{\code{NRMat<T>(n,m)} does not initialize the elements; call \code{clear()} or use the fill constructor. Likewise \code{resize()} does not preserve the contents when the size changes.}
\lawarning{\code{gemm} does not resize the target; allocate it with the correct dimensions first. The target must not share storage with an operand (e.g.\ \code{c.gemm(0.,c,'n',b,'n',1.)} is wrong); after \code{c = a} the call \code{c.gemm(...,a,...)} is fine because \code{gemm} first detaches \code{c}.}
\lawarning{Writing through \code{operator[]}, \code{operator()} or \code{operator T*()} into a matrix obtained by copy construction or \code{=} modifies all sharing matrices unless \code{copyonwrite()} is called first. In DEBUG builds even \emph{reading} through the non-const operators of a shared matrix raises an error; use a const reference.}
\lawarning{\code{NRMat(const NRVec<T>&, n, m, offset)} shares the vector data (without MATPTR). With a nonzero offset, if the matrix happens to be the last owner of the data, it will \code{delete[]} a pointer that is not the beginning of the allocated block; the constructor therefore prints a warning. This is by design (a view without copying, avoiding the cost of a copy); the condition of use is: keep the vector alive longer than the
matrix, or make an independent copy (\code{|=}) if the matrix has to outlive it.}
\lawarning{Many numerical members (\code{gemm}, \code{transpose}, \code{norm}, \ldots) exist only for \code{double} and \code{complex<double>} (Table~\ref{tab:mat:types}); with other element types the program fails to link.}
\lawarning{\code{transposeme()} does not conjugate and works only on square matrices (or square leading blocks); use \code{transpose(true)} for $A\Hc$ or for a non-square matrix.}
\lawarning{In the non-DEBUG build no dimension checks are done in most operations (\code{operator+=}, \code{gemm}, \code{operator=(const T&)}, \ldots); mismatches lead to silent memory corruption.}
+377
View File
@@ -0,0 +1,377 @@
\chapter{Dense linear algebra functions}\label{ch:nonclass}
\laheader{nonclass.h, nonclass.cc, matexp.h}
\section{Purpose and general conventions}\label{sec:nonclass:purpose}
This chapter describes the free (non-member) functions operating on the dense classes
\code{NRVec}, \code{NRMat} and \code{NRSMat}: linear systems, determinants, inverses, Cholesky
decomposition, symmetric/hermitian and general eigenvalue problems, singular value decomposition,
matrix functions, and a few auxiliary products and conversions. Most of them are thin wrappers of
LAPACK routines (\header{nonclass.cc}); they exist for \code{T}${}={}$\code{double} and
\code{std::complex<double>} only. The templates in \header{matexp.h} (matrix polynomials, matrix
exponential, $\exp(A)\,x$, BCH expansion) are generic: they work for every class that provides the few
operations listed in section~\ref{sec:nonclass:matexp}, including sparse matrices
(chapter~\ref{ch:sparse}) and user-defined matrix classes which only implement \code{gemv}.
Iterative (Krylov) methods for large matrices are in chapter~\ref{ch:iterative}.
\paragraph{Destroyed arguments.} LAPACK works in place. All functions taking a matrix by non-const
reference (\code{linear_solve}, \code{diagonalize}, \code{gdiagonalize}, \code{singular_decomposition},
\code{cholesky}, \code{determinant_destroy}, \dots) overwrite it, and the generalized eigenvalue
routines overwrite also the metric \code{b}. These functions call \code{copyonwrite()} on every object they
modify (except where noted as a bug below), so passing a shallow copy is enough to preserve the
original (sec.~\ref{sec:concepts:refcount}):
\begin{lstlisting}
NRMat<double> work = a; // shallow copy, no data copied yet
diagonalize(work, w); // copyonwrite() inside detaches work; a is intact
\end{lstlisting}
Functions which take the matrix by value (\code{calcinverse}, \code{calcsvdinverse}, \code{determinant},
\code{matrixfunction}, \dots) do this themselves. Output vectors and matrices must be allocated by the
caller with the correct dimensions (they are checked, not resized), with the exceptions noted.
\paragraph{C versus Fortran order.} \LA{} matrices are row-major (sec.~\ref{sec:concepts:storage}), LAPACK
is column-major, so a Fortran routine called on the raw data sees the transposed matrix. The wrappers
take this into account; the visible consequence is the parameter \code{corder}, which selects whether
eigenvectors are returned in the \emph{columns} (\code{corder=1}, the default, the usual mathematical
convention $A V = V\Lambda$) or in the \emph{rows} (\code{corder=0}, the natural Fortran result, saves a
transposition). For complex matrices the row form is the ``bra'' form, i.e.\ the rows are the complex
conjugated eigenvectors; table~\ref{tab:nonclass:eivec} summarizes the conventions as verified numerically.
\paragraph{Active dimension.} Several routines have an argument \code{n} (sometimes \code{m}): if
positive, only the leading $n\times n$ submatrix of the (larger) arrays is processed, otherwise the
whole matrix. This is used by the iterative methods, which keep a large work matrix and diagonalize its
growing leading block.
\paragraph{Errors.} LAPACK error codes ($\mathrm{info}\ne0$) are turned into \code{laerror}
(sec.~\ref{sec:concepts:errors}), e.g.\ ``singular matrix'' or ``convergence problem''. Exceptions:
\code{linear_solve_x} and \code{multiply_by_inverse} return \code{info}.
\paragraph{Element types and availability.} The functions are declared for \code{double} and
\code{std::complex<double>} by the macro \code{declare_la}, but not all declared complex variants are
defined in \header{nonclass.cc}. Calling an undefined one gives a link error
(table~\ref{tab:nonclass:avail}).
\begin{table}[htbp]
\centering\small
\begin{tabular}{>{\raggedright\arraybackslash}p{0.5\linewidth}>{\raggedright\arraybackslash}p{0.4\linewidth}}
\toprule
Function & Available for \\
\midrule
\texttt{linear\_solve(NRMat\&, NRMat*, ...)} & \texttt{double}, \texttt{complex<double>} \\
\texttt{linear\_solve(NRMat\&, NRVec\&, ...)}, \texttt{linear\_solve(NRSMat\&, ...)} & \texttt{double}, \texttt{complex<double>} (complex \texttt{NRSMat}: hermitian) \\
\texttt{determinant}, \texttt{determinant\_destroy} & \texttt{NRMat}, \texttt{NRSMat}, real and complex \\
\texttt{diagonalize}, \texttt{singular\_decomposition}, \texttt{cholesky}, \texttt{diagofproduct},
\texttt{trace2(NRMat,NRMat)}, \texttt{linear\_solve\_x}, \texttt{multiply\_by\_inverse}, \texttt{MatrixNorm}, \texttt{CondNumber} & \texttt{double}, \texttt{complex<double>} \\
\texttt{gdiagonalize} & real matrix (real or complex eigenvector storage), complex matrix \\
\texttt{trace2} with \texttt{NRSMat} & \texttt{double}, \texttt{complex<double>} (hermitian) \\
\texttt{diagonalize3}, \texttt{realmatrixfunction}, \texttt{complexmatrixfunction},
\texttt{adjustphases}, \texttt{xcopy}, \texttt{xaxpy}, \texttt{xdot} & \texttt{double} only \\
templates of \texttt{matexp.h}, \texttt{power}, \texttt{commutator}, \texttt{twoside\_transform}, \dots & any type providing the needed operations \\
\bottomrule
\end{tabular}
\caption{Element types for which the functions of this chapter are available.}\label{tab:nonclass:avail}
\end{table}
\begin{table}[htbp]
\centering\small
\begin{tabular}{>{\raggedright\arraybackslash}p{0.33\linewidth}>{\raggedright\arraybackslash}p{0.28\linewidth}>{\raggedright\arraybackslash}p{0.3\linewidth}}
\toprule
Call & \texttt{corder=1} (default) & \texttt{corder=0} \\
\midrule
\texttt{diagonalize(NRMat\&,...)}, real & columns of \texttt{a}: $AV=V\Lambda$ & rows of \texttt{a} \\
\texttt{diagonalize(NRMat\&,...)}, complex & columns of \texttt{a}: $AV=V\Lambda$ & rows of \texttt{a} are $v_k\Hc$: $V\Hc A=\Lambda V\Hc$ \\
\texttt{diagonalize(NRSMat\&,...)}, real & columns of \texttt{*v} & rows of \texttt{*v} \\
\texttt{diagonalize(NRSMat\&,...)}, complex & columns of \texttt{*v}: $HV=V\Lambda$ & rows of \texttt{*v} are $v_k\Hc$ \\
\texttt{diagonalize3} & columns of \texttt{*v} & rows of \texttt{*v} \\
\texttt{gdiagonalize}, complex matrix & columns of \texttt{*vr}: $Ax=\lambda x$; columns of \texttt{*vl}: $y\Hc A=\lambda y\Hc$ (LAPACK convention) & rows of \texttt{*vr} are $\bar x$; rows of \texttt{*vl} are $\bar y$, i.e.\ $\bar y\Tr A=\lambda\bar y\Tr$ \\
\texttt{gdiagonalize}, real matrix, complex storage & as for a complex matrix & as for a complex matrix \\
\texttt{gdiagonalize}, real storage & columns; for a complex pair $(k,k+1)$ the eigenvector of $w_k$ is column $k$ + i column $k+1$ (as \texttt{dgeev}) & rows, analogously \\
\texttt{singular\_decomposition} & \multicolumn{2}{p{0.58\linewidth}}{\raggedright $A=U\,\mathrm{diag}(s)\,V\Hc$; \texttt{*u}${}=U$ (columns = left singular vectors), \texttt{*v}${}=V\Hc$ (rows), or $V$ if \texttt{vnotdagger}} \\
\bottomrule
\end{tabular}
\caption{Storage of eigenvectors and singular vectors (verified numerically). $x$ denotes right, $y$ left eigenvectors, bar complex conjugation.}\label{tab:nonclass:eivec}
\end{table}
\section{Linear systems, inverse, determinant}\label{sec:nonclass:linsolve}
The basic solver uses the LU decomposition with partial pivoting (\code{dgesv}/\code{zgesv}) for
\code{NRMat} and the Bunch--Kaufman factorization (\code{dspsv}) for packed symmetric \code{NRSMat}.
\textbf{The right-hand sides are the rows of \code{B}}: \code{B} has dimension $n_{\mathrm{rhs}}\times n$ and
each row $b$ is overwritten by the solution of $A x=b$ (i.e.\ $A X\Tr=B\Tr$). \code{A} is overwritten
by its factorization. With \code{B==NULL} only the factorization (and determinant) is computed.
\begin{lareference}
\lafunc{void linear_solve(NRMat<T> &a, NRMat<T> *b, T *det=0, int n=0)}{Solve $A x=b$ for all rows $b$ of \code{*b}
(\code{b->ncols()} must be $n$). If \code{det} is non-NULL, the determinant of $A$ is returned there (0 for a singular matrix,
then no error is raised if \code{b==NULL}); a singular matrix with \code{b!=NULL} raises \code{laerror}.
\code{n>0} restricts the problem to the leading $n\times n$ block of \code{a} and the first $n$ columns of \code{b}. Prints a warning when \code{b} is \code{NULL} (some LAPACK libraries skip the factorization for
$n_{\mathrm{rhs}}=0$).}
\lafunc{void linear_solve(NRMat<T> &a, NRVec<T> &b, T *det=0, int n=0)}{The same with a single right-hand side \code{b}.}
\lafunc{void linear_solve(NRSMat<T> &a, NRMat<T> *b, T *det=0, int n=0); void linear_solve(NRSMat<T> &a, NRVec<T> &b, T *det=0, int n=0)}{Symmetric (real) or hermitian (complex), possibly indefinite $A$ in packed storage, \code{dspsv}/\code{zhpsv}
(Bunch--Kaufman factorization; the determinant is computed from its $1\times1$ and $2\times2$ blocks and is real in the hermitian case).
\code{n} selects a leading block, which is contiguous in the packed storage.}
\lafunc{int linear_solve_x(NRMat<T> &A, T *B, const int rhsCount, const int eqCount, const bool eq, const bool saveA, double *rcond)}{Expert solver
(\code{dgesvx}/\code{zgesvx}): solves $A x=b$ for \code{rhsCount} right-hand sides stored consecutively (each of length \code{eqCount}) in the raw
array \code{B}, using the leading \code{eqCount}${}\times{}$\code{eqCount} block of \code{A}. \code{eq}: equilibrate $A$ (for badly conditioned matrices);
then \code{A} is overwritten unless \code{saveA}. The reciprocal condition number is stored in \code{*rcond} if non-NULL. Returns LAPACK \code{info}
(0 success, $>0$ singular or ill-conditioned). The solution replaces \code{B}.}
\lafunc{int multiply_by_inverse(NRMat<T> &A, NRMat<T> &B, bool useEq=false, double *rcond=NULL)}{$B\leftarrow A B^{-1}$ for square $A$, $B$ via
\code{dgesvx}/\code{zgesvx}; \code{useEq} as \code{eq} above. \code{A} is not modified (with \code{useEq} a temporary copy is
scaled). Returns \code{info}.}
\lafunc{const NRMat<T> calcinverse(NRMat<T> a, T *det=NULL)}{$A^{-1}$ by \code{linear_solve} with unit right-hand sides; optionally the determinant.
\code{NRMat::inverse()} (chapter~\ref{ch:mat}) calls it.}
\lafunc{const NRMat<T> calcsvdinverse(NRMat<T> a, double thr=0, bool do_absadjoint=false)}{Inverse via SVD, $V\,\mathrm{diag}(1/s_i)\,U\Hc$,
singular values $s_i<$\code{thr} are treated as zero (pseudoinverse). With \code{do_absadjoint} returns
$V\,\mathrm{diag}(\prod_{j\ne i}s_j)\,U\Hc$, the adjugate matrix up to the phase $\det U\det V$. Square matrices only.}
\lafunc{NRVec<T> svd_solve(NRMat<T> &a, const NRVec<T> &b, double thres=1e-12, int *nullity=NULL); NRMat<T> svd_solve(NRMat<T> &a, const NRMat<T> &b, double thres=1e-12, int *nullity=NULL)}{Least-squares
solution $x=V\,\mathrm{diag}(1/s_i)\,U\Hc b$ discarding singular values $\le$\code{thres} (no contamination by the null space);
the number of discarded ones is stored in \code{*nullity}. Here (unlike \code{linear_solve}) the right-hand sides are the
\emph{columns} of the matrix \code{b} ($m\times n_{\mathrm{rhs}}$); \code{a} ($m\times n$, $m\ge n$) is destroyed.}
\lafunc{const LA_traits<MAT>::elementtype determinant(MAT a)}{Determinant by LU (\code{NRMat}) or Bunch--Kaufman (\code{NRSMat}) factorization of a copy.}
\lafunc{const LA_traits<MAT>::elementtype determinant_destroy(MAT &a)}{The same, overwriting \code{a}. Unless \code{HAS_MKL} is defined, a dummy right-hand side
is passed to \code{linear_solve} (work-around for OpenBLAS, which skips the factorization without right-hand sides).}
\lafunc{void cholesky(NRMat<double> &a, bool upper=1); void cholesky(NRMat<std::complex<double> > &a, bool upper=1)}{Cholesky factorization of a
positive definite matrix in place (\code{dpotrf}/\code{zpotrf}). \code{upper=1}: \code{a} becomes the upper triangular $U$ with
$A=U\Hc U$; \code{upper=0}: lower triangular $L$ with $A=L L\Hc$. The other triangle is zeroed. Raises \code{laerror} if $A$ is not positive definite.}
\end{lareference}
\section{Eigenvalue problems}\label{sec:nonclass:eigen}
\subsection{Symmetric and hermitian matrices}
Eigenvalues are always returned in \textbf{ascending order} (LAPACK). Only the \textbf{lower triangle}
(row index ${}\ge{}$ column index) of an \code{NRMat} is referenced (verified); \code{NRSMat} stores only that
triangle and is interpreted as hermitian (sec.~\ref{sec:smat:hermitian}).
\begin{lareference}
\lafunc{void diagonalize(NRMat<T> &a, NRVec<double> &w, const bool eivec=1, const bool corder=1, int n=0, NRMat<T> *b=NULL, const int itype=1)}{Eigenvalues
\code{w} (size must equal \code{a.nrows()}) and, if \code{eivec}, eigenvectors stored in \code{a} (columns for \code{corder=1}, see table~\ref{tab:nonclass:eivec})
of a real symmetric or complex hermitian matrix (\code{dsyev}/\code{zheev}). If \code{b} is given, the generalized problem with positive definite $B$
is solved (\code{dsygv}/\code{zhegv}): \code{itype=1}: $A x=\lambda B x$, \code{itype=2}: $A B x=\lambda x$, \code{itype=3}: $B A x=\lambda x$;
eigenvectors are $B$-normalized ($x\Hc B x=1$ for \code{itype} 1, 2), \code{b} is overwritten by its Cholesky factor.}
\lafunc{void diagonalize(NRSMat<T> &a, NRVec<double> &w, NRMat<T> *v, const bool corder=1, int n=0, NRSMat<T> *b=NULL, const int itype=1)}{The same for packed
storage (\code{dspev}/\code{zhpev}, \code{dspgv}/\code{zhpgv}); eigenvectors go to \code{*v} (square, at least $n\times n$) or are not computed if \code{v==NULL}.
\code{a} (and \code{b}) are destroyed.}
\lafunc{void diagonalize3(NRVec<double> &d, NRVec<double> &d1, NRMat<double> *v, const bool corder=1, int n0=0)}{Real symmetric tridiagonal matrix
(\code{dsteqr}) with diagonal \code{d} and sub-diagonal \code{d1} (size ${}\ge n-1$). On return \code{d} holds the eigenvalues (ascending), \code{d1} is destroyed,
\code{*v} (exactly $n\times n$) the eigenvectors. \code{n0>0} uses only the first \code{n0} elements. Used by \code{lanczos} (chapter~\ref{ch:iterative}).}
\end{lareference}
\subsection{General (non-symmetric) matrices}
\code{gdiagonalize} wraps \code{dgeev}/\code{zgeev} and, if \code{b} and \code{beta} are given, the generalized
problem $A x=\lambda B x$ (\code{dggev}/\code{zggev}), where the eigenvalues are $\lambda_i=w_i/\beta_i$ (\code{beta}
may contain zeros for infinite eigenvalues). Right (\code{vr}) and left (\code{vl}) eigenvectors are computed only if the
pointers are non-NULL; storage according to table~\ref{tab:nonclass:eivec}. Without sorting, the eigenvalues come in
LAPACK order (unsorted, complex conjugate pairs adjacent with positive imaginary part first).
\begin{lareference}
\lafunc{void gdiagonalize(NRMat<double> &a, NRVec<double> &wr, NRVec<double> &wi, NRMat<double> *vl=NULL, NRMat<double> *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat<double> *b=NULL, NRVec<double> *beta=NULL)}{Real
matrix, eigenvalues $w_r+\mathrm{i}\,w_i$, eigenvectors in the real storage of \code{dgeev}: for a real eigenvalue the vector is real; for a complex pair
$(j,j{+}1)$ the vectors $j$ and $j{+}1$ hold the real and imaginary part. Verified: the right eigenvector belonging to
$w_{r,j}+\mathrm{i}w_{i,j}$ ($w_{i,j}>0$) is $\mathrm{Re}-\mathrm{i}\,\mathrm{Im}$, and $\mathrm{Re}+\mathrm{i}\,\mathrm{Im}$ of \code{vl} satisfies $y\Tr A=\lambda y\Tr$ --
note that this sign is opposite to the \code{dgeev} documentation.
\code{sorttype}: 0 no sorting; 1 ascending real part (for equal real parts descending imaginary part; with \code{beta}, by $w/\beta$, infinite eigenvalues last);
2 real eigenvalues first, then complex ones, each group ordered as for 1. Eigenvectors are permuted accordingly.
\code{biorthonormalize} (needs \code{vl} and \code{vr}, not with \code{b}): 1 scales the left, 2 the right eigenvectors so that $y_k\Tr x_k=1$;
complex pairs are rotated to biorthonormality. \code{a} is destroyed (and used as scratch space).}
\lafunc{void gdiagonalize(NRMat<T> &a, NRVec<std::complex<double> > &w, NRMat<std::complex<double> > *vl=NULL, NRMat<std::complex<double> > *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat<T> *b=NULL, NRVec<T> *beta=NULL)}{Template
specialized for \code{T=double} (calls the previous routine and converts to complex eigenvalues and eigenvectors) and \code{T=complex<double>} (\code{zgeev}/\code{zggev}).
For the complex matrix, \code{sorttype>0} raises \code{laerror} (not implemented) and \code{biorthonormalize} scales so that $\bar y_k\Tr x_k=1$ in the row storage.}
\lafunc{void gdiagonalize(NRMat<std::complex<double> > &a, NRVec<double> &wr, NRVec<double> &wi, NRMat<std::complex<double> > *vl=NULL, NRMat<std::complex<double> > *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat<std::complex<double> > *b=NULL, NRVec<std::complex<double> > *beta=NULL)}{Complex
matrix with real and imaginary parts of the eigenvalues in separate vectors; exists to make \code{davidson} compile for complex \code{T}.}
\end{lareference}
\section{Singular value decomposition}\label{sec:nonclass:svd}
\begin{lareference}
\lafunc{void singular_decomposition(NRMat<T> &a, NRMat<T> *u, NRVec<double> &s, NRMat<T> *v, const bool vnotdagger=0, int m=0, int n=0)}{SVD
$A=U\,\mathrm{diag}(s)\,V\Hc$ of the $m\times n$ matrix \code{a} (\code{dgesvd}/\code{zgesvd}; \code{m}, \code{n} select a leading block, default whole matrix).
\code{s} (size ${}\ge\min(m,n)$) receives the singular values in descending order. \code{u}: \code{NULL} (not computed), $m\times m$ (all columns) or
$m\times\min(m,n)$ (economy size, chosen by \code{u} being non-square). \code{v}: \code{NULL}, $n\times n$ or $\min(m,n)\times n$, receives $V\Hc$
(right singular vectors as conjugated rows), or $V$ if \code{vnotdagger} (square \code{v} only). \code{a} is destroyed.}
\end{lareference}
\section{Matrix functions}\label{sec:nonclass:matfunc}
Functions of matrices computed via the eigendecomposition $f(A)=V f(\Lambda) V^{-1}$. For general matrices
the eigenvalue problem is solved in complex arithmetic and the result converted back to the element type of the
argument; for a real argument the imaginary part is \emph{silently dropped} (its size is not checked). The matrix must be
diagonalizable; nothing is done about defective or ill-conditioned eigenvector matrices.
\begin{lareference}
\lafunc{NRMat<T> matrixfunction(NRSMat<T> a, double (*f)(double))}{$f(A)$ for a real symmetric or complex hermitian packed matrix, real function \code{f}
of the (real) eigenvalues.}
\lafunc{NRMat<T> matrixfunction(NRMat<T> a, std::complex<double> (*f)(const std::complex<double> &))}{$f(A)$ for a general real or complex
matrix by \code{gdiagonalize}, $f(A)=\sum_k f(\lambda_k)\,x_k y_k\Tr/(y_k\Tr x_k)$.}
\lafunc{NRMat<double> realmatrixfunction(NRMat<double> a, double (*f)(double))}{$f(A)$ for a real symmetric matrix in full storage (only the lower triangle is used).}
\lafunc{NRMat<std::complex<double> > complexmatrixfunction(NRMat<double> a, double (*fre)(double), double (*fim)(double))}{Complex function
$f=f_{\mathrm{re}}+\mathrm{i} f_{\mathrm{im}}$ of a real symmetric matrix, e.g.\ $\exp(\mathrm{i}A)$ with \code{cos}, \code{sin}.}
\lafunc{NRMat<double> sqrt(const NRSMat<double> &a); NRMat<double> sqrtinv(const NRSMat<double> &a)}{$A^{1/2}$, $A^{-1/2}$ of a symmetric matrix (e.g.\ symmetric orthogonalization).}
\lafunc{NRMat<double> realsqrt(const NRMat<double> &a); NRMat<double> realsqrtinv(const NRMat<double> &a)}{The same for a symmetric matrix in full storage.}
\lafunc{NRMat<double> log(const NRSMat<double> &a)}{Logarithm of a symmetric positive definite matrix.}
\lafunc{NRMat<double> log(const NRMat<double> &a); NRMat<std::complex<double> > log(const NRMat<std::complex<double> > &a)}{Principal logarithm of a
general matrix via complex eigendecomposition (for a real matrix the imaginary part of the result is dropped).}
\lafunc{NRMat<double> exp0(const NRMat<double> &a); NRMat<std::complex<double> > exp0(const NRMat<std::complex<double> > &a)}{$\exp(A)$ via
eigendecomposition, an alternative to the Taylor-series \code{exp()} of section~\ref{sec:nonclass:matexp}.}
\lafunc{NRMat<double> copytest(const NRMat<double> &a); NRMat<std::complex<double> > copytest(const NRMat<std::complex<double> > &a)}{\code{matrixfunction} with
the identity function, i.e.\ reconstructs $A$ from its eigendecomposition (a check of \code{gdiagonalize} available to users).}
\lafunc{double sqrtinv(const double); std::complex<double> sqrtinv(const std::complex<double> &)}{Scalar $1/\sqrt{x}$ (used as function argument above).}
\lafunc{void adjustphases(NRMat<double> &v)}{Changes signs of rows of an orthogonal matrix (eigenvectors in rows) so that the diagonal elements are positive
where possible and $\det v=+1$, to obtain a real logarithm of a rotation. The source comments say ``not rigorous yet''.}
\end{lareference}
\section{Matrix polynomials and exponentials (matexp.h)}\label{sec:nonclass:matexp}
\laheader{matexp.h}
These templates use only generic operations and therefore work for \code{NRMat}, \code{NRSMat},
\code{SparseMat}, \code{SparseSMat} and user classes. \code{exp} and the polynomials need for the matrix type
\code{T}: copy and assignment, \code{T*T}, \code{T*scalar}, \code{T+scalar} and \code{T+=T} where adding a scalar
means adding it to the \emph{diagonal} (this is how \LA{} matrix classes define it), \code{norm()}, and the traits
\code{LA_traits<T>::elementtype}, \code{normtype}, \code{deallocate} and \code{axpy}
(sec.~\ref{sec:concepts:traits}). The ``\code{times}'' variants never form the matrix: they need only
\code{nrows()}, \code{ncols()}, \code{norm()} and
\code{gemv(beta, NRVec &r, char trans, alpha, const NRVec &x)} computing $r\leftarrow\beta r+\alpha\,\mathrm{op}(A)x$,
so they can be used with implicitly defined (``direct'') matrices as in chapter~\ref{ch:iterative}. The vector type \code{V}
needs \code{size()}, \code{operator*=}, \code{axpy} and \code{resize()}.
The exponential uses scaling and squaring: the power $p$ is chosen so that $\|A\|/2^p\lesssim 0.75$
(\code{mynextpow2}), the Taylor series of the scaled argument is truncated when the next term would be below $10^{-14}$
relative to the norm, and the result is squared $p$ times. The scaling is put into the Taylor coefficients,
so the argument itself is not copied. \code{maxpower} (if ${}\ge0$) limits $p$, \code{maxtaylor} (if ${}\ge0$) limits
the order of the Taylor polynomial, which is useful and exact for nilpotent matrices (e.g.\ coupled-cluster excitation
operators).
\begin{lareference}
\lafunc{const T polynom0(const T &x, const NRVec<R> &c)}{$\sum_{i=0}^{N} c_i x^i$ by the Horner scheme ($N$=\code{c.size()-1}).}
\lafunc{const T polynom(const T &x, const NRVec<R> &c)}{The same with fewer matrix multiplications at the cost of storing powers $x,\dots,x^m$
(block summation of Paterson and Stockmeyer, $\sum_i s_i\,(x^m)^i$ with $s_i=\sum_{j<m}c_{im+j}x^j$): $m$ is chosen to minimize the number
$m-2+2\lfloor N/m\rfloor-[m\,|\,N]$ of multiplications, e.g.\ 5, 8, 13 instead of 8, 16, 32 for $N=8,16,32$; for $N\le4$ calls \code{polynom0}.}
\lafunc{const T exp(const T &x, bool horner=false, int maxpower= -1, int maxtaylor= -1)}{$\exp(x)$ by scaling and squaring; \code{horner} selects \code{polynom0}
or \code{polynom} (default) for the Taylor polynomial. Both give the same accuracy; \code{polynom} is 1.3--1.5 times faster for dense
matrices (tests with $n=100$--$800$), while for sparse matrices \code{horner=true} is faster, since the stored powers fill in.}
\lafunc{void sincos(T &s, T &c, const T &x, bool horner=false, int maxpower= -1, int maxtaylor= -1)}{$s=\sin x$, $c=\cos x$ simultaneously in real arithmetic
(doubling formulas instead of squaring); \code{T} also needs \code{operator*=(double)}.}
\lafunc{NRMat<std::complex<T> > expi(const NRMat<T> &x)}{$\exp(\mathrm{i}x)=\cos x+\mathrm{i}\sin x$ of a real matrix via \code{sincos}.}
\lafunc{const T ipow(const T &x, int i)}{$x^i$, $i\ge0$, by binary powering; $x^0$ is the unit matrix of the size of $x$.}
\lafunc{const V exptimes(const M &mat, V rhs, bool transpose=false, const LA_traits<V>::elementtype scale=1., int maxpower= -1, int maxtaylor= -1, bool mat_is_0=false)}{$\exp(s\,\mathrm{op}(A))\,\mathtt{rhs}$
with $\mathrm{op}(A)=A$ or $A\Tr$ (\code{transpose}), using only \code{mat.gemv}. The scaled Taylor polynomial is applied $2^p$ times instead of squaring.
\code{scale} $s$ may be complex for a real matrix and complex vectors (e.g.\ time evolution $\exp(-\mathrm{i}Ht)x$). \code{mat_is_0} short-cuts to a (deep) copy of \code{rhs}.
Raises \code{laerror} for an unrealistically large $p$ (use \code{maxpower}).}
\lafunc{const V exptimesreal(const M &mat, V rhs, bool transpose=false, const LA_traits<V>::normtype scale=1., int maxpower= -1, int maxtaylor= -1, bool mat_is_0=false)}{The
same with a real \code{scale} for a complex vector (overload resolution helper).}
\lafunc{void exptimesdestructive(const M &mat, V &result, V &rhs, bool transpose, const MEL scale, int maxpower= -1, int maxtaylor= -1, bool mat_is_0=false)}{Worker of
\code{exptimes}: \code{result}${}=\exp(s\,\mathrm{op}(A))\,\mathtt{rhs}$, \code{rhs} is used as scratch and destroyed.}
\lafunc{void sincostimes(const M &mat, V &si, V &co, const V &rhs, bool transpose=false, const LA_traits<V>::normtype scale=1., int maxpower= -1, int maxtaylor= -1, bool mat_is_0=false)}{\code{si}${}=\sin(sA)\,\mathtt{rhs}$,
\code{co}${}=\cos(sA)\,\mathtt{rhs}$ with \code{gemv} only. The doubling is done by recursion with $3^p$ evaluations; the source comment recommends complex \code{exptimes} instead.}
\lafunc{const T ncommutator(const T &x, const T &y, int nest=1, const bool right=1); const T nanticommutator(const T &x, const T &y, int nest=1, const bool right=1)}{Nested
(anti)commutator: \code{right}: $[\dots[[x,y],y]\dots,y]$, otherwise $[x,[x,\dots[x,y]]]$, \code{nest} levels.}
\lafunc{const T BCHexpansion(const T &h, const T &t, const int nmax, const bool verbose=0, const bool right=1)}{Truncated Baker--Campbell--Hausdorff series
$\sum_{n=0}^{n_{\max}}\frac1{n!}[\dots[h,t],\dots,t]_n=e^{-t}h\,e^{t}$ (\code{right}) or $e^{t}h\,e^{-t}$ (\code{right=0}); \code{verbose} prints the norm of each order to \code{std::cerr}.}
\lafunc{const V BCHtimes(const M1 &H, const char transH, const M2 &T, const char transT, const V &x, const int nmax, const bool right=1)}{$e^{-T}He^{T}x$ (\code{right})
or $e^{T}He^{-T}x$ truncated at order \code{nmax}, using only \code{gemv} of $H$ and $T$ (\code{transH}, \code{transT} select $\mathrm{op}$): $H T^j x$ is precomputed for $j\le n_{\max}$
and the nested commutators are built recursively in place, so $n_{\max}+1$ vectors are stored.}
\end{lareference}
The helpers \code{exp_aux}, \code{sincos_aux}, \code{sincostimes_aux}, \code{sincostimes_simple}, \code{mynextpow2} and the coefficient table
\code{exptaylor} are internal.
\section{Products, traces, transformations}\label{sec:nonclass:products}
\begin{lareference}
\lafunc{const NRVec<T> diagofproduct(const NRMat<T> &a, const NRMat<T> &b, bool trb=0, bool conjb=0)}{Diagonal of $AB$ without forming the product;
\code{trb}: of $AB\Tr$ (or $AB\Hc$ with \code{conjb}, complex only).}
\lafunc{T trace2(const NRMat<T> &a, const NRMat<T> &b, bool trb=0)}{$\mathrm{Tr}(AB)$, or $\mathrm{Tr}(AB\Tr)=\sum_{ij}a_{ij}b_{ij}$ with \code{trb}
(complex: $\mathrm{Tr}(AB\Hc)$).}
\lafunc{T trace2(const NRSMat<T> &a, const NRSMat<T> &b, const bool diagscaled=0)}{$\mathrm{Tr}(AB)=\sum_{ij}a_{ij}b_{ij}$ of symmetric matrices,
$\sum_{ij}a_{ij}\bar b_{ij}$ (real) of hermitian ones.
\code{diagscaled}: returns $2\sum_{i\ge j}a_{ij}b_{ij}$, i.e.\ the correct trace if the diagonal of one argument has been pre-multiplied by $1/2$.}
\lafunc{T trace2(const NRSMat<T> &a, const NRMat<T> &b, const bool diagscaled=0); T trace2(const NRMat<T> &a, const NRSMat<T> &b, const bool diagscaled=0)}{$\mathrm{Tr}(AB)$
of a symmetric (hermitian for complex) and a general matrix; \code{diagscaled} counts the diagonal twice.}
\lafunc{const NRSMat<T> twoside_transform(const NRSMat<T> &S, const NRMat<T> &C, bool transp=0); const NRMat<T> twoside_transform(const NRMat<T> &S, const NRMat<T> &C, bool transp=0)}{$C\Hc S C$,
or $C S C\Hc$ with \code{transp} (similarity/congruence transformation).}
\lafunc{const NRMat<T> diagonalmatrix(const NRVec<T> &x)}{Square matrix with diagonal \code{x}.}
\lafunc{const NRMat<T> commutator(const NRMat<T> &x, const NRMat<T> &y, const bool trx=0, const bool tryy=0); const NRMat<T> anticommutator(...)}{$\mathrm{op}(x)\mathrm{op}(y)\mp\mathrm{op}(y)\mathrm{op}(x)$
with two \code{gemm} calls; \code{trx}, \code{tryy} transpose the factors (no conjugation).}
\lafunc{T positive_power(const T &x, const int n)}{$x^n$, $n\ge1$, by binary powering (any type with \code{operator*}).}
\lafunc{T power(const T &x, const int n)}{$x^n$ for any integer $n$; needs \code{identity()} for $n=0$ and \code{inverse()} for $n<0$ (e.g.\ \code{NRMat}, permutations).}
\end{lareference}
\section{Norms and condition number}\label{sec:nonclass:norms}
\begin{lareference}
\lafunc{typename LA_traits<MAT>::normtype MatrixNorm(const MAT &A, const char norm)}{Matrix norm by \code{dlange}/\code{zlange}, implemented for
\code{NRMat<double>} and \code{NRMat<std::complex<double> >} (explicit specializations of the template, which can be added for other types).
\code{'1'} or \code{'O'}: 1-norm (maximum column sum); \code{'I'}: $\infty$-norm (maximum row sum); \code{'F'} or \code{'E'}: Frobenius norm;
\code{'M'}: largest absolute value of an element (lower case accepted). Works for non-square matrices.}
\lafunc{typename LA_traits<MAT>::normtype CondNumber(const MAT &A, const char norm)}{Condition number $\|A\|\,\|A^{-1}\|$ of a square matrix in the 1-norm
(\code{'1'}, \code{'O'}) or $\infty$-norm (\code{'I'}), from the LU factorization of a copy of \code{A} (\code{dgetrf}/\code{zgetrf}) and \code{dgecon}/\code{zgecon};
infinity for a singular matrix. It is LAPACK's estimate, a lower bound that is usually exact. Same types as \code{MatrixNorm}.}
\end{lareference}
\section{Real and imaginary parts}\label{sec:nonclass:complex}
\begin{lareference}
\lafunc{const LA_traits<T>::realtype realpart(const T &a); const LA_traits<T>::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<double>}.}
\lafunc{const LA_traits<T>::complextype realmatrix(const T &a); const LA_traits<T>::complextype imagmatrix(const T &a)}{Complex object $A$ resp.\ $\mathrm{i}A$ from a real one;
for \code{NRMat<double>} and \code{SparseSMat<double>}.}
\lafunc{const LA_traits<T>::complextype complexmatrix(const T &re, const T &im)}{Complex object $\mathrm{re}+\mathrm{i}\,\mathrm{im}$; for \code{NRMat<double>} and \code{SparseSMat<double>}.}
\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}).
\section{Miscellaneous}\label{sec:nonclass:misc}
\begin{lareference}
\lafunc{const NRMat<LA_traits<MAT>::elementtype> submatrix(const MAT a, const int nrows, const INDEX rows, const int ncols, const INDEX cols, int indexshift=0, bool ignoresign=false, bool equalsigns=false)}{General
(non-contiguous) submatrix $r_{ij}=a(\mathtt{rows}[i]+s,\ \mathtt{cols}[j]+s)$, $s$=\code{indexshift}; \code{INDEX} is anything indexable (\code{NRVec<int>}, \code{int*}).
\code{ignoresign}: use $|\mathtt{rows}[i]|$, $|\mathtt{cols}[j]|$; \code{equalsigns}: $r_{ij}=0$ if the two indices have opposite signs.
Application from the source: overlap of Slater determinants with signed spin-orbital indices (\code{indexshift=-1}, both flags true). \code{MAT} needs only
\code{operator()(int,int)}.}
\lafunc{NRMat<T> reconstructmatrix<M,T>(const M &implicitmat)}{Explicit matrix of an implicit one (applying \code{gemv} to unit vectors), for debugging;
both template arguments must be given. Equivalent to \code{explicit_matrix<T>} of \header{davidson.h} (chapter~\ref{ch:iterative}).}
\lafunc{void xcopy(int n, const T *x, int incx, T *y, int incy); void xaxpy(int n, const T &a, const T *x, int incx, T *y, int incy); T xdot(int n, const T *x, int incx, const T *y, int incy)}{Type-generic
names of the CBLAS routines (to ease switching between \code{float}/\code{double}/complex in user code); at present specialized only for \code{double}.}
\end{lareference}
\section{Examples}\label{sec:nonclass:examples}
Symmetric eigenproblem, eigenvectors in columns, and a linear system with two right-hand sides (rows of \code{b}):
\begin{lstlisting}
#include "la.h"
using namespace LA;
int main()
{
const int n=4;
NRSMat<double> s(n);
for(int i=0; i<n; ++i) for(int j=0; j<=i; ++j) s(i,j) = (i==j? i+2. : 0.5/(1+i+j));
NRSMat<double> work = s; //shallow copy, diagonalize detaches it
NRVec<double> w(n);
NRMat<double> v(n,n);
diagonalize(work, w, &v); //w ascending, columns of v are eigenvectors
NRMat<double> sv = NRMat<double>(s)*v;
for(int k=0; k<n; ++k) std::cout << (sv.column(k) - v.column(k)*w[k]).norm() << std::endl;
NRMat<double> a(s), b(2,n); //solve a x = b_i for the 2 rows of b
for(int i=0; i<2; ++i) for(int j=0; j<n; ++j) b(i,j) = i+j;
double det;
linear_solve(a, &b, &det); //a now holds the LU factors, rows of b the solutions
NRMat<double> s2 = sqrtinv(s); //S^{-1/2}
std::cout << det << std::endl << s2;
}
\end{lstlisting}
Exponential of a sparse matrix and its action on a vector (\code{t.cc} contains similar tests):
\begin{lstlisting}
SparseMat<double> h(n,n); //... filled with h.add(i,j,x)
NRMat<double> e = exp(NRMat<double>(h)); //dense exponential
SparseMat<double> es = exp(h); //sparse arithmetics
NRVec<double> y = exptimes(h, x); //exp(h) x, only gemv used
NRVec<std::complex<double> > xc(complexify(x));
NRVec<std::complex<double> > psi = exptimes(h, xc, false, std::complex<double>(0,-0.1)); //exp(-0.1 i h) x
\end{lstlisting}
General matrix: left and right eigenvectors in complex storage, rows for \code{corder=0}:
\begin{lstlisting}
NRMat<double> g(n,n); //... filled
NRMat<double> scratch = g;
NRVec<std::complex<double> > lambda(n);
NRMat<std::complex<double> > vl(n,n), vr(n,n);
gdiagonalize(scratch, lambda, &vl, &vr, false, 0, 0, 0, (NRMat<double>*)NULL, (NRVec<double>*)NULL);
//rows: vl[k]^T g = lambda[k] vl[k]^T, g conj(vr[k]) = lambda[k] conj(vr[k])
\end{lstlisting}
In the last call the null pointers must be cast, otherwise the template argument of \code{b} cannot be deduced.
\section{Pitfalls}\label{sec:nonclass:pitfalls}
\begin{itemize}
\item Right-hand sides of \code{linear_solve} are the \emph{rows} of \code{B} ($n_{\mathrm{rhs}}\times n$), those of \code{svd_solve} the \emph{columns}.
\item Eigenvectors are in columns only for \code{corder=1}; in row storage (\code{corder=0}) complex eigenvectors are conjugated
(table~\ref{tab:nonclass:eivec}).
\item \code{diagonalize(NRMat&, ...)} uses only the lower triangle; a non-symmetric \code{NRMat} is silently treated as symmetric.
\item Unsorted output of \code{gdiagonalize} unless \code{sorttype} is set (real matrices only).
\item \code{matrixfunction}, \code{log} etc.\ of a real general matrix drop the imaginary part of the result without any check.
\item The arguments passed by non-const reference are destroyed; keep a shallow copy if the original is needed (it is sufficient because of internal \code{copyonwrite()}).
\item Several declared complex variants are missing (link errors), see section~\ref{sec:nonclass:purpose}.
\end{itemize}
+1005
View File
File diff suppressed because it is too large. Load diff
+425
View File
@@ -0,0 +1,425 @@
\chapter{Permutations and the symmetric group}\label{ch:permutation}
\laheader{permutation.h, permutation.cc, qsort.h}
This chapter describes the classes for permutations (in the one-line and in the cycle notation),
for linear combinations of permutations (elements of the group algebra of $S_n$), for partitions and
Young tableaux, the character table of $S_n$, and the sorting templates which return the parity of the
sorting permutation. All class templates are explicitly instantiated in \header{permutation.cc} for
\code{T = int} and \code{T = unsigned int}; \code{WeightPermutation<T,R>} and \code{PermutationAlgebra<T,R>}
for \code{T = int} with \code{R = int}, \code{double}, \code{std::complex<double>}.
Ranks, counts of generated objects and class sizes have the type
\begin{lstlisting}
typedef unsigned long long PERM_RANK_TYPE;
\end{lstlisting}
(declared outside of namespace \code{LA}); $n!$ overflows it for $n>20$.
\section{Conventions}\label{sec:permutation:conventions}
\begin{itemize}
\item \textbf{Permutations are numbered from 1.} \code{NRPerm<T>}, \code{CyclePerm<T>}, \code{Partition<T>},
\code{CompressedPartition<T>} and \code{YoungTableaux<T>} are derived from \code{NRVec_from1}
(chapter~\ref{ch:vec}), i.e.\ \code{p[1]} \ldots\ \code{p[n]}. When a permutation is applied to a vector
or matrix indexed from 0, the offset 1 is subtracted internally (section~\ref{sec:permutation:apply}).
\item The one-line notation stores the images: \code{p[i]} $=p(i)$. A cycle $(c_1\,c_2\ldots c_l)$ maps
$c_1\mapsto c_2\mapsto\cdots\mapsto c_l\mapsto c_1$.
\item Product: $(pq)(i)=p(q(i))$, i.e.\ in \code{p*q} the right operand \code{q} is applied first.
\item Parity is returned as $+1$ (even) or $-1$ (odd) by the \code{parity()} members; the sorting
templates of section~\ref{sec:permutation:sort} return the parity bit 0 (even) or 1 (odd).
\item The generators (\code{generate_all}, \ldots) call a user callback for each generated object and
return their number. They keep their state in file-static variables, so they are neither reentrant nor
thread safe; do not start another generator of the same kind from inside the callback.
\end{itemize}
\paragraph{Copy on demand.} All classes of this chapter except \code{WeightPermutation}, \code{Sn_characters},
\code{CycleIndex} (which are plain aggregates of such objects) are derived from \code{NRVec}/\code{NRVec_from1}
and are reference counted as described in section~\ref{sec:concepts:refcount}: copy construction and
\code{=} share the data, \code{|=} copies; before writing through \code{operator[]} the user has to call
\code{copyonwrite()}. Nested types (\code{CyclePerm}, \code{YoungTableaux}, \code{PermutationAlgebra})
are vectors of vectors; their \code{copyonwrite()} is deep (it also detaches the inner vectors).
Most modifying members call \code{copyonwrite()}; the exceptions found are listed in the pitfalls
(section~\ref{sec:permutation:pitfalls}).
\section{NRPerm: permutations in one-line notation}\label{sec:permutation:nrperm}
\code{template <typename T> class NRPerm : public NRVec_from1<T>} stores a permutation of $\{1,\ldots,n\}$ as the
vector of images. Relational operators \code{== != < >} (lexicographic) and the I/O operators \code{<<}, \code{>>}
(the size followed by the elements) are inherited from \code{NRVec} (section~\ref{sec:concepts:io}).
Many members check \code{is_valid()} of their operands only in DEBUG builds.
\subsection{Construction and conversion}
\begin{lareference}
\lafunc{NRPerm(); NRPerm(const int n)}{empty; size $n$, elements uninitialized (call \code{identity()}).}
\lafunc{template<int SIZE> explicit NRPerm(const T (&a)[SIZE])}{from an array or brace list, e.g.\ \code{NRPerm<int> p({1,4,2,3})}.}
\lafunc{NRPerm(const NRVec_from1<T> &rhs); NRPerm(const T *a, const int n)}{from a vector (shares data) or from $n$ elements at \code{a} (copy).}
\lafunc{explicit NRPerm(const CyclePerm<T> &rhs, const int n=0)}{from the cycle notation; size \code{n}, or \code{rhs.max()} if \code{n==0}
(fixed points beyond the largest moved element are then lost).}
\lafunc{explicit NRPerm(const int type, const NRVec_from1<T> &inversions)}{reconstruct from an inversion table of the given \code{type}
(see \code{inversions()}).}
\lafunc{explicit NRPerm(const int n, const PERM_RANK_TYPE rank)}{unranking: the permutation of size $n$ with the lexicographic rank
\code{rank} $\in[0,n!-1]$ (checked in DEBUG).}
\end{lareference}
\subsection{Basic operations}
\begin{lareference}
\lafunc{void resize(int n); int size() const}{as for \code{NRVec}.}
\lafunc{void identity(); void identity(int n)}{set to the identity (of size $n$); calls copyonwrite.}
\lafunc{bool is_valid() const}{true if the elements are a permutation of $1\ldots n$.}
\lafunc{bool is_identity() const}{true for the identity (also for size 0).}
\lafunc{int parity() const}{$+1$/$-1$; returns 0 for an invalid permutation. Counts inversions for $n<10$, sorts a copy otherwise.}
\lafunc{NRPerm inverse() const}{$p^{-1}$.}
\lafunc{void inverseme()}{in-place inversion, $O(n^2)$ cycle-leader algorithm (cf.\ arXiv:1901.01926); detaches shared data first.}
\lafunc{void reverse_cycle(int i); int cycle_leader(int i) const}{auxiliary for \code{inverseme()}: reverse in place the cycle containing $i$
(no \code{copyonwrite()}, the caller detaches first); return the smallest element of that cycle.}
\lafunc{NRPerm reverse() const}{the sequence in backward order: $r(i)=p(n-i+1)$.}
\lafunc{NRPerm operator*(const NRPerm &q) const}{product $pq$, $(pq)(i)=p(q(i))$; sizes must agree.}
\lafunc{NRPerm multiply(const NRPerm<T> &q, bool inverse) const}{$pq$, or $pq^{-1}$ if \code{inverse} (without forming $q^{-1}$).}
\lafunc{NRPerm operator*(const CyclePerm<T> &r) const}{product with a permutation in cycle notation, converted to size \code{size()}.}
\lafunc{template<typename R> PermutationAlgebra<T,R> operator*(const PermutationAlgebra<T,R> &pa) const}{left multiplication of each term of \code{pa} by $p$.}
\lafunc{NRPerm conjugated_by(const NRPerm &q, bool reverse=false) const}{$q^{-1}pq$, or $qpq^{-1}$ if \code{reverse}.}
\lafunc{NRPerm commutator(const NRPerm &q, bool inverse=false) const}{$p^{-1}q^{-1}pq$, or $q^{-1}p^{-1}qp$ if \code{inverse}.}
\lafunc{NRPerm pow(const int n) const}{$p^n$, also for $n\le 0$ (by repeated squaring, \code{power()} of \header{nonclass.h}).}
\lafunc{CompressedPartition<T> cycles() const}{cycle type: number of cycles of length $1,2,\ldots,n$ (fixed points included).}
\lafunc{NRPerm operator&(const NRPerm &rhs) const}{direct sum: $r=(p(1),\ldots,p(n),\,n+q(1),\ldots,n+q(m))$ with \code{q=rhs}, $m$ = \code{rhs.size()}.}
\lafunc{NRPerm operator|(const NRPerm &rhs) const}{$r=(n+q(1),\ldots,n+q(m),\,p(1),\ldots,p(n))$, i.e.\ the renumbered \code{rhs} comes first.}
\lafunc{NRPerm cutleft(int n) const}{inverse of \code{&} with an identity on the left: requires $p(i)=i$ for $i\le n$ (laerror otherwise), returns the remaining part renumbered to $1\ldots$\code{size()}$-n$.}
\lafunc{NRPerm cutright(int n) const}{requires the last $n$ elements to be fixed points and removes them.}
\lafunc{void randomize(void)}{uniformly random permutation (Fisher--Yates shuffle with \code{RANDINT32()}); the size must be set.}
\end{lareference}
\subsection{Ranking and inversion tables}
\begin{lareference}
\lafunc{PERM_RANK_TYPE rank() const}{lexicographic rank, $0$ for the identity and $n!-1$ for $(n,\ldots,1)$.}
\lafunc{NRVec_from1<T> inversions(const int type, PERM_RANK_TYPE *prank=NULL) const}{inversion table $I$ of size $n$:
\code{type} 0: $I_j$ = number of elements greater than $j$ standing left of the value $j$ (Knuth's inversion table);
1: number of elements smaller than $j$ standing right of the value $j$;
2: $I_j=|\{k<j: p(k)>p(j)\}|$;
3: $I_j=|\{k>j: p(k)<p(j)\}|$ (Lehmer code; $\mathrm{rank}=\sum_j I_j\,(n-j)!$).
Inverse: \code{NRPerm(type,I)}. If \code{prank} is given (only for type 3, otherwise \code{laerror}), \code{*prank} is set to the rank computed
from the Lehmer code, the same value as \code{rank()}.}
\end{lareference}
\subsection{Generating all permutations}
The generators overwrite \code{*this}, which is passed to the callback \code{void callback(const NRPerm<T>&)}; a \code{NULL} callback only counts.
\begin{lareference}
\lafunc{bool next()}{replace by the lexicographically next permutation (works also for repeated elements); returns false (and leaves \code{*this} unchanged) for the last one. Calls copyonwrite.}
\lafunc{PERM_RANK_TYPE generate_all_lex(void (*callback)(const NRPerm<T>&))}{all $n!$ permutations in lexicographic order, starting from the identity, using \code{next()}.}
\lafunc{PERM_RANK_TYPE generate_all(void (*callback)(const NRPerm<T>&), int parity_select=0)}{all permutations by adjacent transpositions
(plain changes, Steinhaus--Johnson--Trotter order \cite{permutation:knuth4a}), so the parity alternates; fastest but not lexicographic.
This is Knuth's Algorithm~P. \code{parity_select}: 0 all, $+1$ (positive) only even, $-1$ (negative) only odd permutations; then $n!/2$ is returned.}
\lafunc{PERM_RANK_TYPE generate_all2(void (*callback)(const NRPerm<T>&))}{recursive generation, not lexicographic.}
\lafunc{PERM_RANK_TYPE generate_all_multi(void (*callback)(const NRPerm<T>&))}{all distinct permutations of a multiset: \code{*this} must be filled
beforehand with the (possibly repeated) elements in any order; they are sorted and permuted in lexicographic order (Knuth's
algorithm for multisets). The result is not a permutation in the strict sense.}
\lafunc{PERM_RANK_TYPE generate_restricted(void (*callback)(const NRPerm<T>&), const NRVec_from1<T> &classes, int restriction_type=0)}{permutations
restricted with respect to a partitioning of $1\ldots n$ into classes (\code{classes[i]} is the class label of $i$, size $n$):
\code{restriction_type} 0: all; 1: only $p$ with \code{classes[p(i)]==classes[i]} (permutations within classes);
$-1$: only $p$ with \code{classes[p(i)]!=classes[i]} for all $i$; $-2$: only permutations keeping the elements of each class in increasing order
(``shuffles'', the $n!/\prod_c n_c!$ coset representatives used in antisymmetrizers like $P(ij/kl)$).}
\lafunc{PermutationAlgebra<T,T> list_all(int parity_select=0)}{all permutations (order of \code{generate_all}) as a \code{PermutationAlgebra} with weights $\pm1$ = parity,
i.e.\ the unnormalized antisymmetrizer; with \code{parity_select} $\pm1$ only the even/odd permutations, all with weight \code{parity_select}.}
\lafunc{PermutationAlgebra<T,T> list_all_lex()}{all permutations in lexicographic order (term $k$ has rank $k$), all with weight 1, i.e.\ the unnormalized symmetrizer.}
\lafunc{PermutationAlgebra<T,T> list_restricted(const NRVec_from1<T> &classes, int restriction_type=0, bool inverted=false)}{the permutations of
\code{generate_restricted}, weight = parity; with \code{inverted} the inverse permutations are stored.}
\end{lareference}
\section{CyclePerm: cycle notation}\label{sec:permutation:cycleperm}
\code{template <typename T> class CyclePerm}, derived from \code{NRVec_from1< NRVec_from1<T> >}, is a list of cycles. The representation is not
unique (order of cycles, starting element, presence of cycles of length 1); \code{CyclePerm(const NRPerm&)} omits fixed points.
The size of the permuted set is not stored: wherever it matters it is \code{max()} unless given explicitly.
\begin{lareference}
\lafunc{CyclePerm(); template<int SIZE> explicit CyclePerm(const NRVec_from1<T> (&a)[SIZE])}{empty (the identity); from an array of cycles, e.g.\ \code{CyclePerm<int> c({NRVec_from1<int>({1,2}),NRVec_from1<int>({3,4,5})})}.}
\lafunc{explicit CyclePerm(const NRPerm<T> &rhs)}{cycle decomposition, nontrivial cycles only.}
\lafunc{bool is_valid() const}{no empty cycle, positive elements, no element repeated.}
\lafunc{bool is_identity() const; void identity()}{test for no cycle longer than 1; set to the identity (no cycles).}
\lafunc{CyclePerm inverse() const}{reverse all cycles.}
\lafunc{int parity() const}{$-1$ for an odd number of even-length cycles.}
\lafunc{T max() const}{largest element occurring.}
\lafunc{CompressedPartition<T> cycles(T n = 0) const}{cycle type for size $n$ (default \code{max()}), fixed points counted as 1-cycles.}
\lafunc{PERM_RANK_TYPE order() const}{order in the group, the lcm of the cycle lengths.}
\lafunc{CyclePerm operator*(const CyclePerm &q) const}{product (\code{q} applied first), computed through \code{NRPerm} of the size of the larger maximum of the two operands.}
\lafunc{NRPerm<T> operator*(const NRPerm<T> &r) const}{mixed product, result of size \code{r.size()}.}
\lafunc{CyclePerm conjugated_by(const CyclePerm &q) const}{$q^{-1}pq$.}
\lafunc{bool operator==(const CyclePerm &rhs) const}{compares via conversion to \code{NRPerm} (so independent of the cycle representation).}
\lafunc{void simplify(bool keep1=false)}{remove cycles of length 0 and 1 (with \code{keep1} only empty ones); detaches shared data first.}
\lafunc{CyclePerm pow(const int n, const bool keep1=false) const}{$p^n$ (also $n<0$) computed cycle by cycle (a cycle of length $l$ splits into $\gcd(n,l)$ cycles);
with \code{keep1} the resulting fixed points are kept as 1-cycles.}
\lafunc{CyclePerm pow_simple(const int n) const}{$p^n$ through \code{NRPerm::pow}.}
\lafunc{void readfrom(const std::string &line)}{parse cycles like \code{(1 2 3)(4,5)}; elements separated by blanks or commas, empty cycles skipped.}
\lafunc{std::istream & operator>>(std::istream &s, CyclePerm<T> &x)}{reads one line and calls \code{readfrom}.}
\lafunc{std::ostream & operator<<(std::ostream &s, const CyclePerm<T> &x)}{writes \code{(1 2 3)(4 5)} (no newline).}
\end{lareference}
\section{Weighted permutations and the group algebra}\label{sec:permutation:algebra}
\subsection{WeightPermutation}
\code{template <typename T, typename R> class WeightPermutation} is a pair of public members \code{R weight; NRPerm<T> perm;}, i.e.\ the term
$w\,p$ of the group algebra $\mathbb{R}S_n$ (or with coefficients of type \code{R}). The \code{LA_traits} specialization
(section~\ref{sec:concepts:traits}) declares it non-plain with \code{coefficienttype} and \code{elementtype} \code{R}, so that \code{NRVec}
sorting and \code{NRVec_simplify} work on vectors of it.
\begin{lareference}
\lafunc{WeightPermutation(); explicit WeightPermutation(const NRPerm<T> &p, const R w=1)}{weight 0 and empty permutation; $w\,p$.}
\lafunc{int size() const}{size of the permutation.}
\lafunc{bool is_zero() const; bool is_scaledidentity() const; bool is_identity() const}{$w=0$; $p$ = identity; $w=1$ and $p$ = identity.}
\lafunc{bool is_plaindata() const; void copyonwrite()}{returns false; \code{perm.copyonwrite()}.}
\lafunc{void identity(); void identity(int n)}{$w=1$, $p$ = identity (of size $n$).}
\lafunc{WeightPermutation operator&(const WeightPermutation &rhs) const; ... operator|(...) const}{direct sums of the permutations (as for \code{NRPerm}), weights multiplied.}
\lafunc{WeightPermutation operator*(const WeightPermutation &rhs) const; WeightPermutation operator*(const R &x) const}{product of terms; scaling of the weight.}
\lafunc{bool operator==(const WeightPermutation &rhs) const; != < > <= >=}{compare \emph{only the permutations}, ignoring the weights (needed for sorting and simplification).}
\lafunc{WeightPermutation & operator=(const WeightPermutation &rhs)}{memberwise (the permutation is shared).}
\lafunc{std::ostream & operator<<(std::ostream &s, const WeightPermutation<T,R> &x); std::istream & operator>>(...)}{weight followed by the permutation.}
\end{lareference}
\subsection{PermutationAlgebra}
\code{template <typename T, typename R> class PermutationAlgebra : public NRVec<WeightPermutation<T,R> >} represents
$\sum_k w_k p_k$ as a vector of terms indexed \emph{from 0}. Products and sums simplify the result; a canonical form (sorted by permutation,
equal permutations merged, zero terms removed) is obtained by \code{simplify()}. Such objects are used e.g.\ by
\code{Tensor::apply_permutation_algebra} (chapter~\ref{ch:tensor}) and by the \code{NRMat} constructor of section~\ref{sec:permutation:apply}.
\begin{lareference}
\lafunc{PermutationAlgebra(); PermutationAlgebra(int n); PermutationAlgebra(const NRVec<WeightPermutation<T,R> > &x)}{empty; $n$ uninitialized terms; from a vector of terms.}
\lafunc{template <typename U> PermutationAlgebra(const PermutationAlgebra<T,U> &rhs)}{conversion of the weight type (cast \code{(R)}).}
\lafunc{int size() const; void resize(int n); void copyonwrite()}{as for \code{NRVec}.}
\lafunc{int sort(int direction = 0, int from = 0, int to = -1, int *permut = NULL, bool stable=false)}{\code{NRVec::sort} of the terms by their permutations.}
\lafunc{void simplify(const typename LA_traits<R>::normtype thr=0)}{sort, add weights of equal permutations, drop zero terms (\code{NRVec_simplify}; terms
are dropped if $w=0$ or if \code{abs2(w)<thr}, i.e.\ \code{thr} is compared with $|w|^2$).}
\lafunc{PermutationAlgebra operator*(const PermutationAlgebra &rhs) const}{algebra product (all pairs of terms), simplified; permutation sizes must agree.}
\lafunc{PermutationAlgebra operator+(const PermutationAlgebra &rhs) const}{sum (concatenation), simplified.}
\lafunc{PermutationAlgebra operator&(const PermutationAlgebra &rhs) const; ... operator|(...) const}{all pairs of terms combined by \code{&} or \code{|}
(tensor product of algebras acting on disjoint sets); not simplified.}
\lafunc{PermutationAlgebra operator*(const NRPerm<T> &rhs) const; operator&(const NRPerm<T> &rhs) const; operator|(const NRPerm<T> &rhs) const}{applied to all terms: $\sum_k w_k\,p_k q$, $p_k\,\&\,q$, $p_k\,|\,q$.}
\lafunc{PermutationAlgebra cutleft(int n) const; PermutationAlgebra cutright(int n) const}{\code{NRPerm::cutleft/cutright} of all terms.}
\lafunc{PermutationAlgebra conjugated_by(const NRPerm<T> &q, bool reverse=false) const; PermutationAlgebra commutator(const NRPerm<T> &q, bool inverse=false) const}{applied to all terms.}
\lafunc{PermutationAlgebra &operator*=(const R &x); PermutationAlgebra operator*(const R &x) const}{scale all weights.}
\lafunc{bool operator==(PermutationAlgebra &rhs)}{simplifies \emph{both} operands (hence non-const) and compares terms including weights.}
\lafunc{bool is_zero() const; bool is_scaled_identity() const; bool is_identity() const}{tests valid for simplified objects only: no term; single term with identity permutation (and weight 1).}
\lafunc{void identity(int n)}{the single term $1\cdot\mathrm{id}_n$.}
\end{lareference}
Inherited \code{NRVec} I/O prints the number of terms followed by the terms.
\paragraph{Related free functions.}
\begin{lareference}
\lafunc{template <typename T> PermutationAlgebra<T,T> symmetrizer(int n); ... antisymmetrizer(int n)}{$\sum_{p\in S_n} p$ and $\sum_{p\in S_n}\mathrm{sgn}(p)\,p$
(unnormalized), built as Young operators of a single row/column.}
\lafunc{template<typename T> PermutationAlgebra<T,T> general_antisymmetrizer(const NRVec<NRVec_from1<T> > &classgroups, int restriction_type=0, bool inverted=false)}{direct
product (by \code{&}) of \code{list_restricted(classgroups[i], restriction_type, inverted)} for all groups; group $i$ acts on the next
\code{classgroups[i].size()} elements. Used for antisymmetrizers in coupled-cluster-like expressions (see the tensor chapter).}
\lafunc{template <typename T, typename R, typename U> void cast_permutation_algebra(PermutationAlgebra<T,R> &lhs, const PermutationAlgebra<T,U> &rhs)}{conversion of the weight type into an existing object.}
\lafunc{template<typename T> NRMat<PERM_RANK_TYPE> Multable(T n)}{multiplication table of $S_n$ ($n!\times n!$) in the basis of permutations ordered by lexicographic rank:
\code{r(i,j)} = rank of $p_ip_j$. Consistency (Latin square) is checked in DEBUG.}
\lafunc{template<typename T, typename R> NRMat<R> RegularRepresentation(const PermutationAlgebra<T,R> &a, const NRMat<PERM_RANK_TYPE> &mtable)}{matrix of left multiplication by
\code{a} in the regular representation: $R_{\mathrm{rank}(p_kp_j),\,j} \mathrel{+}= w_k$; \code{mtable} from \code{Multable(n)}.}
\end{lareference}
\section{Partitions}\label{sec:permutation:partitions}
Two representations of a partition $\lambda\vdash n$ are used, both as vectors of size $n$ (indexed from 1):
\code{Partition<T>} stores the parts in non-increasing order, padded with zeros ($[3,1]$ of 4 is \code{3 1 0 0});
\code{CompressedPartition<T>} stores the multiplicities $m_i$ of parts of size $i$ ($[3,1]$ is \code{1 0 1 0}), printed as \code{3,1};
$[2,1^2]$ is printed \code{2,1^2}. Partitions label the conjugacy classes of $S_n$ (cycle types) and its irreducible representations.
\subsection{CompressedPartition}
\begin{lareference}
\lafunc{CompressedPartition(); CompressedPartition(const int n); template<int SIZE> explicit CompressedPartition(const T (&a)[SIZE])}{empty; size $n$ uninitialized; from an array.}
\lafunc{explicit CompressedPartition(const Partition<T> &rhs)}{conversion.}
\lafunc{T sum() const; T nparts() const; T nclasses() const}{$\sum_i i\,m_i$; number of parts $\sum_i m_i$; number of distinct part sizes.}
\lafunc{bool is_valid() const}{\code{size()==sum()}.}
\lafunc{PERM_RANK_TYPE Sn_class_size() const}{size of the conjugacy class of $S_n$ with this cycle type, $n!/\prod_i i^{m_i}m_i!$.}
\lafunc{int parity() const}{parity of permutations with this cycle type.}
\lafunc{std::ostream & operator<<(std::ostream &s, const CompressedPartition<T> &x)}{e.g.\ \code{3,2^2,1}.}
\end{lareference}
\subsection{Partition}
\begin{lareference}
\lafunc{Partition(); Partition(const int n); template<int SIZE> explicit Partition(const T (&a)[SIZE])}{empty; size $n$ uninitialized; from an array (zero-padded to $n$ by the user).}
\lafunc{explicit Partition(const CompressedPartition<T> &rhs); explicit Partition(const YoungTableaux<T> &x)}{conversion; the shape of a Young tableau.}
\lafunc{T nparts() const; bool is_valid() const}{number of nonzero parts; \code{size()==sum()} and non-increasing.}
\lafunc{Partition adjoint() const}{conjugate (transposed) partition $\lambda'$.}
\lafunc{PERM_RANK_TYPE Sn_irrep_dim() const}{dimension of the irrep $[\lambda]$ of $S_n$ (hook length formula).}
\lafunc{PERM_RANK_TYPE Un_irrep_dim(const int n) const}{dimension of the irrep of $U(n)$ with the highest weight $\lambda$ \cite[eq.~10-25]{permutation:hamermesh}; 0 if $\lambda$ has more than $n$ parts.}
\lafunc{int parity() const}{parity of permutations with the cycle type $\lambda$.}
\lafunc{PERM_RANK_TYPE generate_all(void (*callback)(const Partition<T>&), int nparts=0)}{all partitions of \code{size()} in reverse lexicographic order
($[n]$ first, $[1^n]$ last); \code{nparts}$>0$: exactly \code{nparts} parts, $<0$: at most $-$\code{nparts} parts, 0: all. Overwrites \code{*this}; returns the count.}
\end{lareference}
\begin{lareference}
\lafunc{PERM_RANK_TYPE partitions(int n, int k= -1)}{number of partitions of $n$ into exactly $k$ parts, or the total number $p(n)$ for $k=-1$ \cite{permutation:aigner};
tabulated recursively up to $n=260$ (larger $n$ only for $k\ge n/2$).}
\end{lareference}
\section{Young tableaux}\label{sec:permutation:young}
\code{template <typename T> class YoungTableaux}, derived from \code{NRVec_from1< NRVec_from1<T> >}, stores the rows of a Young diagram (row $i$ has
length $\lambda_i$), each box holding a number (0 = empty).
\begin{lareference}
\lafunc{YoungTableaux(); explicit YoungTableaux(const Partition<T> &frame)}{empty; empty (zero-filled) diagram of the shape \code{frame}.}
\lafunc{template<int SIZE> explicit YoungTableaux(const NRVec_from1<T> (&a)[SIZE])}{from an array of rows.}
\lafunc{bool is_valid() const}{nonempty rows of non-increasing length.}
\lafunc{int nrows() const; int ncols() const}{number of rows; length of the first row.}
\lafunc{bool is_standard() const}{numbers non-decreasing along rows and columns (repeated numbers allowed).}
\lafunc{T sum() const; T max() const}{number of boxes $n$; largest number filled in.}
\lafunc{NRVec_from1<T> yamanouchi() const}{Yamanouchi symbol of a standard tableau: element $n-k+1$ is the row containing the number $k$.}
\lafunc{PERM_RANK_TYPE generate_all_standard(void (*callback)(const YoungTableaux<T>&))}{fill \code{*this} in all standard ways with $1\ldots n$, call the callback for each; returns their number
($=$ \code{Sn_irrep_dim()} of the shape).}
\lafunc{PermutationAlgebra<T,T> young_operator() const}{Young operator of a standard tableau, $Y=\sum_{a\in C}\sum_{s\in R}\mathrm{sgn}(a)\,a\,s$ ($R$, $C$: row and column groups), unnormalized,
$\prod_i\lambda_i!\,\prod_j\lambda'_j!$ terms.}
\lafunc{T character_contribution(int ncyc=0) const}{auxiliary for \code{Sn_character}: sign contribution of a tableau filled by a regular application of \code{ncyc} cycles (default \code{max()}).}
\lafunc{std::ostream & operator<<(std::ostream &s, const YoungTableaux<T> &x)}{one row per line.}
\end{lareference}
\section{Characters of the symmetric group}\label{sec:permutation:characters}
\begin{lareference}
\lafunc{template <typename T> T Sn_character(const Partition<T> &irrep, const Partition<T> &cclass)}{character $\chi^{[\lambda]}(\mu)$ of the irrep
\code{irrep} on the class of cycle type \code{cclass}, computed by enumerating the regular applications of the cycles to the Young diagram
(Murnaghan--Nakayama type rule, \cite{permutation:hamermesh}).}
\lafunc{T Sn_character(const CompressedPartition<T> &irrep, const CompressedPartition<T> &cclass)}{the same for compressed partitions.}
\end{lareference}
\code{template <typename T> class Sn_characters} is the whole character table of $S_n$ with public members
\begin{lstlisting}
T n;
NRVec_from1<CompressedPartition<T> > classes; //cycle types
NRVec_from1<CompressedPartition<T> > irreps; //irrep labels
NRVec_from1<PERM_RANK_TYPE> classsizes;
NRMat_from1<T> chi; //chi(irrep,class)
\end{lstlisting}
The irreps are ordered as in \code{Partition::generate_all} ($[n]$ first, $[1^n]$ last) and \code{classes[i]} is the conjugate of \code{irreps[i]},
so class 1 is the identity and \code{chi(i,1)} the dimension.
\begin{lareference}
\lafunc{Sn_characters(const int n0)}{compute the table (cost grows quickly with $n$).}
\lafunc{bool is_valid() const}{consistency checks: dimensions vs.\ hook formula, $\sum d^2=n!$, class sizes, trivial and sign irreps, orthogonality.}
\lafunc{T irrepdim(T i) const; T sumirrepdims() const}{dimension of irrep $i$; $\sum_i d_i$ (number of standard tableaux of size $n$).}
\lafunc{std::ostream & operator<<(std::ostream &s, const Sn_characters<T> &c)}{prints class sizes, class labels \code{(..)}, and rows \code{[irrep] chi}.}
\end{lareference}
\code{template <typename T> class CycleIndex} (public \code{classes}, \code{classsizes}) is the cycle index of $S_n$ for P\'olya counting:
\begin{lareference}
\lafunc{CycleIndex(const Sn_characters<T> &rhs); bool is_valid() const}{take the classes from a character table; check that all classes are partitions of the same $n$.}
\lafunc{Polynomial<T> substitute(const Polynomial<T> &p, PERM_RANK_TYPE *denom) const}{$\sum_{\mu}|C_\mu|\prod_j p(x^j)^{m_j(\mu)}$ and \code{*denom} $=\sum_\mu|C_\mu|=n!$;
the generating function of inequivalent colorings is the result divided by \code{*denom}. See chapter~\ref{ch:polynomial} for \code{Polynomial}.}
\end{lareference}
\section{Applying permutations to vectors and matrices}\label{sec:permutation:apply}
The following members are declared in the vector and matrix headers (chapters~\ref{ch:vec}, \ref{ch:mat}, \ref{ch:smat}, \ref{ch:sparse}) and take
\code{NRPerm<int>} or \code{CyclePerm<int>}; the permutation is numbered from 1 while \code{NRVec} and \code{NRMat} are indexed from 0.
With $p$ = \code{p}, $v$ the source and $r$ the result (indices from 1):
\begin{center}\small
\begin{tabular}{@{}>{\raggedright}p{0.47\linewidth}>{\raggedright\arraybackslash}p{0.47\linewidth}@{}}
\toprule
call & result \\
\midrule
\code{r=v.permuted(p)} & $r_{p(i)}=v_i$ \\
\code{r=v.permuted(p,true)} & $r_i=v_{p(i)}$ \\
\code{v.permuteme(p,inverse)} & in place, as \code{permuted} \\
\code{v.permuteme(c)} (\code{CyclePerm}) & in place, $v_{c(i)}\leftarrow v_i$ \\
\code{applypermutation(p,v)} (\code{NRVec_from1}) & $r_{p(i)}=v_i$, as \code{permuted} \\
\code{applypermutation(p,v,true)} & $r_i=v_{p(i)}$ \\
\code{A.permuted_rows(p)}, \code{permuted_cols}, \code{permuted_both(p,q)} & $r_{p(i),j}=A_{ij}$ etc.; with \code{true}: $r_{ij}=A_{p(i),j}$ \\
\code{A.permuteme_rows(c)}, \code{permuteme_cols(c)} (\code{CyclePerm}) & in place, as \code{permuted_rows(NRPerm(c))} \\
\code{S.permuted(p)} (\code{NRSMat}) & $r_{p(i)p(j)}=S_{ij}$; with \code{true}: $r_{ij}=S_{p(i)p(j)}$ \\
\code{NRMat<T>(p, direction, parity=false)} & permutation matrix: $A_{i,p(i)}=1$ if \code{direction}, else $A_{p(i),i}=1$ (times \code{p.parity()} if \code{parity}) \\
\code{NRMat<T>(wp,direction)}, \code{NRMat<T>(pa,direction,nforce=0)} & $\sum_k w_kP_k$ for a \code{WeightPermutation} / \code{PermutationAlgebra} \\
\code{A.axpy(alpha,p,direction)} & $A\mathrel{+}=\alpha P$ \\
\bottomrule
\end{tabular}
\end{center}
\begin{lareference}
\lafunc{template <typename T, typename X> NRVec_from1<X> applypermutation(const NRPerm<T> &p, const NRVec_from1<X> &set, bool inverse=false)}{a free function
(element type independent of \code{T}), same convention as \code{NRVec::permuted}.}
\lafunc{int NRVec<T>::sort(int direction, NRPerm<int> &perm, bool stable=false)}{sort the vector (0 ascending, 1 descending), return the parity bit;
afterwards \code{sorted == original.permuted(perm,true)}.}
\end{lareference}
For the matrix representation of $S_n$ by permutation matrices note that it is reducible ($[n]\oplus[n-1,1]$), so e.g.\ Young projectors are not
faithfully represented; use \code{RegularRepresentation} instead.
\section{Small number-theoretic helpers}
\begin{lareference}
\lafunc{template <typename T> T gcd(T big, T small)}{greatest common divisor by Euclid's algorithm; laerror if both are 0. For negative arguments the sign of the result is not normalized.}
\lafunc{template <typename T> inline T lcm(T a, T b)}{$(a/\gcd(a,b))\,b$.}
\end{lareference}
\section{Sorting}\label{sec:permutation:sort}
\laheader{qsort.h}
The sorting templates are used by \code{NRVec::sort} and by other classes and return the parity bit (0 even, 1 odd) of the permutation
performed, which is needed e.g.\ for signs of determinants or of fermionic operator strings. The quicksorts use median-of-3 pivoting and recurse
first into the shorter part; they are not stable (except \code{memqsortstable}).
\begin{lareference}
\lafunc{template<typename INDEX, typename COMPAR> int genqsort(INDEX l, INDEX r, COMPAR (*cmp)(const INDEX, const INDEX), void (*swap)(const INDEX,const INDEX))}{sort
the abstract range $[l,r]$ (inclusive) in ascending order; \code{cmp(i,j)} returns negative/zero/positive like \code{strcmp}, \code{swap(i,j)} exchanges the items.}
\lafunc{template<int type, typename SORTABLE, typename INDEX, typename PERMINDEX> int memqsort(SORTABLE &object, PERMINDEX *perm, INDEX l, INDEX r)}{sort
elements $l\ldots r$ of \code{object}, ascending for \code{type=0}, descending for \code{type=1}; the comparisons go through \code{LA_sort_traits<SORTABLE,INDEX,type>},
which call \code{object.bigger(i,j)} / \code{object.smaller(i,j)}; \code{object.swap(i,j)} exchanges. If \code{perm!=NULL}, \code{perm[l..r]} is permuted in the same way.}
\lafunc{template<int type, typename SORTABLE, typename INDEX, typename PERMINDEX> int memqsortstable(SORTABLE &object, PERMINDEX *perm, INDEX l, INDEX r)}{stable
variant (original position as secondary key; requires also \code{object[i]==object[j]}), uses \code{memqsortaux}.}
\lafunc{template<int type, typename SORTABLE, typename INDEX, typename PERMINDEX> int memqsortaux(SORTABLE &object, PERMINDEX *perm, INDEX l, INDEX r, INDEX *aux)}{worker of \code{memqsortstable} with the auxiliary key array \code{aux[l..r]}.}
\lafunc{template<typename S, typename PERMINDEX> int ptrqsortup(S *l, S *r, PERMINDEX *perm=NULL); ... ptrqsortdown(...)}{sort the array $[l,r]$ (pointers, inclusive) ascending/descending
by \code{operator<}, \code{operator>}; \code{perm[0..r-l]} is permuted along.}
\lafunc{template<typename INDEX, typename DATA> void myheapsort(INDEX n, DATA *ra, int typ=1)}{heapsort of \code{ra[0..n-1]}, ascending for \code{typ>0}, descending otherwise; no parity.}
\lafunc{template<typename T> int netsort(const int n, T *data)}{ascending sort of a short array by optimal sorting networks for $n\le 8$ (\code{netsort_0} \ldots\ \code{netsort_8}),
\code{ptrqsortup} for longer; returns the parity bit.}
\end{lareference}
To make a class sortable by \code{memqsort} it has to provide \code{bool bigger(int i, int j) const}, \code{bool smaller(int i, int j) const},
\code{void swap(int i, int j)} (and \code{operator[]} for the stable variant), as \code{NRVec} does.
\section{Examples}\label{sec:permutation:examples}
Basic operations, ranking and conversion to cycles:
\begin{lstlisting}
NRPerm<int> p({2,3,1});
NRPerm<int> q=p*p; // (3 1 2)
std::cout << p.rank() << ' ' << p.parity() << std::endl; // 3 1
NRPerm<int> r(3,p.rank()); // unranking, r==p
CyclePerm<int> c(p); // (1 2 3)
std::cout << c << ' ' << c.cycles() << std::endl;
NRVec_from1<double> v({10.,20.,30.});
std::cout << v.permuted(p) << applypermutation(p,v); // 30 10 20 / 30 10 20
\end{lstlisting}
Generating permutations with a callback (store copies with \code{|=}, cf.\ the pitfalls):
\begin{lstlisting}
static void printme(const NRPerm<int> &p) {std::cout << p.parity() << ' ' << p;}
...
NRPerm<int> p(4);
PERM_RANK_TYPE n=p.generate_all_lex(printme); // 24
NRVec_from1<int> classes({1,1,2,2});
PermutationAlgebra<int,int> a=p.list_restricted(classes,-2); // 6 shuffles with signs
\end{lstlisting}
Character table, Young operators and their regular representation (cf.\ \header{t.cc}):
\begin{lstlisting}
static NRMat<PERM_RANK_TYPE> mtable;
static void ycallback(const YoungTableaux<int> &y)
{
PermutationAlgebra<int,int> Y = y.young_operator();
NRMat<int> R = RegularRepresentation(Y,mtable);
std::cout << y << "Y = " << Y << "trace of R = " << R.trace() << std::endl;
}
int main()
{
Sn_characters<int> Sn(4);
std::cout << Sn;
if(!Sn.is_valid()) laerror("internal error");
mtable = Multable(4);
Partition<int> lambda({2,1,1,0});
YoungTableaux<int> y(lambda);
PERM_RANK_TYPE dim = y.generate_all_standard(ycallback); // 3 == lambda.Sn_irrep_dim()
}
\end{lstlisting}
\section{Pitfalls}\label{sec:permutation:pitfalls}
\begin{itemize}
\item Permutations are numbered from 1, \code{PermutationAlgebra} terms from 0; vectors and matrices from 0 (the library subtracts the offset).
\item The two conventions of applying a permutation to a vector (table in section~\ref{sec:permutation:apply}) differ; check with a small example.
\item \code{CyclePerm} does not know the size of the permuted set; pass \code{n} to \code{NRPerm(const CyclePerm&,n)} and \code{cycles(n)}.
\item The \code{operator==} of \code{WeightPermutation} ignores the weights; that of \code{PermutationAlgebra} simplifies its operands.
\item In the callbacks of the generators the argument refers to the working object of the generator. All generators
detach it after the callback, so a shallow copy stored by the callback stays intact.
\item Copy on demand: the auxiliary \code{NRPerm::reverse_cycle()} writes through \code{operator[]} without
\code{copyonwrite()}; call \code{copyonwrite()} before using it directly (\code{inverseme()} does so).
\item Generators and \code{Sn_character} use static variables: not reentrant, not thread safe.
\item $n!$ and class sizes are \code{unsigned long long}: overflow for $n>20$.
\end{itemize}
+207
View File
@@ -0,0 +1,207 @@
\chapter{Polynomials and continued fractions}\label{ch:polynomial}
This chapter describes the polynomial class \code{Polynomial<T>} and the exact rational and continued-fraction arithmetics
(\code{Rational<T>}, \code{ContFrac<T>} with Gosper's algorithms).
\section{Polynomial}\label{sec:polynomial:polynomial}
\laheader{polynomial.h, polynomial.cc}
\subsection{Purpose and storage}
\code{template <typename T> class Polynomial : public NRVec<T>} represents $p(x)=\sum_{i=0}^{d}c_ix^i$; the coefficient $c_i$ is the vector element
\code{p[i]} (indexed from 0, i.e.\ by the power) and the vector has $d+1$ elements. The member \code{size()} is made private to prevent confusion; use
\code{degree()}. The leading coefficient may be zero (e.g.\ after a subtraction); \code{simplify()} removes such terms. The class is explicitly
instantiated for \code{int}, \code{unsigned int}, \code{double}, \code{std::complex<double>}; root finding and SVD-based gcd require a floating point type.
All inherited \code{NRVec} functionality (norms, \code{randomize}, \code{clear}, I/O operators printing the number of coefficients followed by the
coefficients, \ldots) is available. \code{Polynomial} follows the copy-on-demand scheme of section~\ref{sec:concepts:refcount}: its modifying members call
\code{copyonwrite()} or \code{resize()}; writing coefficients through \code{operator[]} requires an explicit \code{copyonwrite()} as for \code{NRVec}.
The operations are CPU only (\code{NOT_GPU}, section~\ref{sec:concepts:gpu}).
\subsection{Construction}
\begin{lareference}
\lafunc{Polynomial()}{empty (no coefficients).}
\lafunc{Polynomial(const int n)}{degree $n$, coefficients uninitialized. Note that \code{Polynomial<int>(3)} is \emph{not} the constant 3.}
\lafunc{Polynomial(const T &a, const int n)}{degree $n$ with $c_0=a$ and all other coefficients zero.}
\lafunc{template<int SIZE> Polynomial(const T (&a)[SIZE])}{from coefficients $c_0,c_1,\ldots$, e.g.\ \code{Polynomial<double> p({1,2,3})} is $1+2x+3x^2$.}
\lafunc{Polynomial(const NRVec<T> &v)}{implicit conversion from a vector of coefficients (shares data).}
\lafunc{void binomial(const int n)}{set to $(1+x)^n$.}
\end{lareference}
\subsection{Arithmetic}
\begin{lareference}
\lafunc{int degree() const}{the degree $d$ (number of coefficients minus 1).}
\lafunc{void resize(const int n, const bool preserve=true)}{set the degree to $n$; added coefficients are zero when preserving.}
\lafunc{Polynomial& operator+=(const T &a); Polynomial& operator-=(const T &a); Polynomial operator+(const T &a) const; Polynomial operator-(const T &a) const}{add/subtract a constant.}
\lafunc{Polynomial operator-() const}{negation.}
\lafunc{Polynomial operator*(const T &a) const; Polynomial operator/(const T &a) const; Polynomial& operator*=(const T &a); Polynomial& operator/=(const T &a)}{scaling.}
\lafunc{Polynomial& operator+=(const Polynomial &rhs); Polynomial& operator-=(const Polynomial &rhs); Polynomial operator+(const Polynomial &rhs) const; Polynomial operator-(const Polynomial &rhs) const}{sum
and difference; the degree is the maximum of both (no simplification).}
\lafunc{Polynomial operator*(const Polynomial &rhs) const; Polynomial& operator*=(const Polynomial &rhs)}{product, naive $O(d_1d_2)$ algorithm (no FFT).
It hides the inherited \code{NRVec} scalar product.}
\lafunc{void polydiv(const Polynomial &rhs, Polynomial &q, Polynomial &r) const}{division with remainder, $p=q\cdot\mathrm{rhs}+r$, $\deg r<\deg\mathrm{rhs}$ (the remainder
has the degree $\deg\mathrm{rhs}-1$ formally); laerror if the leading coefficient of \code{rhs} is zero.}
\lafunc{Polynomial operator/(const Polynomial &rhs) const}{quotient of \code{polydiv}; \texttt{operator\%} with the same signature returns the remainder.}
\lafunc{Polynomial pow(const int i) const}{$p^i$, $i\ge0$ (repeated squaring).}
\lafunc{Polynomial shifted(const int shift) const}{multiplication by $x^{\mathrm{shift}}$; for negative \code{shift} division by $x^{-\mathrm{shift}}$, dropping the low-order terms.}
\lafunc{void simplify(const typename LA_traits<T>::normtype thr=0)}{remove leading coefficients with $|c_i|\le$ \code{thr} (the degree stays $\ge0$).}
\lafunc{void normalize()}{divide by the leading coefficient (monic polynomial); laerror if it is zero.}
\end{lareference}
\paragraph{Free operators.}
\begin{lareference}
\lafunc{template <typename T> Polynomial<T> operator+(const T &a, const Polynomial<T> &rhs)}{$a+p$.}
\lafunc{template <typename T> Polynomial<T> operator-(const T &a, const Polynomial<T> &rhs) = delete}{Deliberately not available (compile error); write \code{-p+a}.}
\end{lareference}
\subsection{Calculus and transformations}
\begin{lareference}
\lafunc{Polynomial derivative(const int d=1) const}{$d$-th derivative ($d<0$: integral); the zero polynomial of degree 0 if $d>\deg p$.}
\lafunc{Polynomial integral(const int d=1) const}{$d$-fold integral with zero integration constants ($d<0$: derivative).}
\lafunc{Polynomial composition(const Polynomial &rhs) const}{$p(\mathrm{rhs}(x))$, the same as \code{value(p,rhs)}.}
\lafunc{Polynomial powx(const int i) const}{$p(x^i)$; for $i=0$ the constant $p(1)$.}
\lafunc{Polynomial even_powers() const; Polynomial odd_powers() const}{$E$ and $O$ such that $p(x)=E(x^2)+x\,O(x^2)$.}
\end{lareference}
\subsection{Evaluation}
\begin{lareference}
\lafunc{template <typename T, typename C> C value(const Polynomial<T> &p, const C &x)}{Horner scheme; \code{C} may be a scalar, a square matrix
(\code{NRMat}, \code{NRSMat}, \ldots; constants are added to the diagonal), or another polynomial (composition). \code{C} needs a copy constructor,
\code{=0}, \code{+=} with \code{T} and \code{operator*}.}
\lafunc{template <typename T, typename C> C even_value(const Polynomial<T> &p, const C &x); C odd_value(const Polynomial<T> &p, const C &x)}{sums of the even resp.\ odd
terms only, $E(x^2)$ and $x\,O(x^2)$; \code{C} may also be a square matrix.}
\lafunc{template <typename T, typename C> NRVec<C> values(const Polynomial<T> &p, const NRVec<C> &x)}{\code{value} at each element of \code{x}.}
\end{lareference}
For polynomials of matrices with fewer matrix multiplications see \code{polynom()} in \header{matexp.h}, which accepts the coefficient
vector, hence also a \code{Polynomial} (section~\ref{sec:nonclass:matexp}).
\subsection{Roots, interpolation, gcd}
\begin{lareference}
\lafunc{NRMat<T> companion() const}{companion matrix ($d\times d$, ones on the superdiagonal, last row $-c_i/c_d$) whose characteristic polynomial is $p/c_d$.}
\lafunc{NRVec<typename LA_traits<T>::complextype> roots() const}{all roots as eigenvalues of the companion matrix (\code{gdiagonalize}, chapter~\ref{ch:nonclass}); for \code{double}
and \code{std::complex<double>} only (laerror for integer types).}
\lafunc{NRVec<T> realroots(const typename LA_traits<T>::normtype thr) const}{roots with $|\mathrm{Im}|<$ \code{thr}, real parts sorted ascending (\code{T = double}).}
\lafunc{T newton(const T x0, const typename LA_traits<T>::normtype thr=1e-14, const int maxit=1000) const}{Newton iteration from \code{x0} until $|p(x)|<$ \code{thr}; no convergence report.}
\lafunc{template <typename T> Polynomial<T> polyfromroots(const NRVec<T> &roots, const int skip= -1)}{monic $\prod_{i\ne\mathrm{skip}}(x-r_i)$.}
\lafunc{template <typename T> Polynomial<T> lagrange_interpolation(const NRVec<T> &x, const NRVec<T> &y)}{interpolating polynomial of degree $n-1$ through the $n$ points; laerror for repeated $x$.}
\lafunc{template <typename T> NRMat<T> Sylvester(const Polynomial<T> &p, const Polynomial<T> &q)}{Sylvester matrix of dimension $\deg p+\deg q$ (its determinant is the resultant).}
\lafunc{template <typename T> Polynomial<T> poly_gcd(const Polynomial<T> &p, const Polynomial<T> &q, const typename LA_traits<T>::normtype thr=0, const int d= -1)}{Euclid's
algorithm with \code{simplify(thr)} of the remainders, result not normalized. It stops at a zero remainder (degree 0 with $|c_0|\le$\code{thr}); with \code{d>=0} also when the remainder degree drops below \code{d}.
For coprime polynomials the result is a nonzero constant; $\gcd(p,0)=p$.}
\lafunc{template <typename T> Polynomial<T> svd_gcd(const Polynomial<T> &p, const Polynomial<T> &q, const typename LA_traits<T>::normtype thr=0)}{numerically more robust gcd:
the degree $\deg p+\deg q-\mathrm{rank}\,S$ is determined from the singular values of the Sylvester matrix $S$ (threshold \code{thr} scaled by $\sqrt{\deg p\deg q}$),
then \code{poly_gcd} with this degree. Floating point types only.}
\lafunc{template <typename T> Polynomial<T> poly_lcm(const Polynomial<T> &p, const Polynomial<T> &q, const typename LA_traits<T>::normtype thr=0)}{$pq/\gcd(p,q)$.}
\end{lareference}
\subsection{Orthogonal polynomials}
\begin{lareference}
\lafunc{template <typename T> Polynomial<T> hermite_polynomial(int n)}{Hermite polynomial $H_n$ in the physicists' normalization (leading coefficient $2^n$).}
\end{lareference}
No other families are implemented.
\subsection{Example}
\begin{lstlisting}
Polynomial<double> p=polyfromroots(NRVec<double>({1.,2.,3.})); // (x-1)(x-2)(x-3)
Polynomial<double> q({1.,1.}); // 1+x
Polynomial<double> a,b;
p.polydiv(q,a,b); // p = a*q + b
std::cout << (a*q+b-p).norm() << std::endl;
NRVec<double> r = p.realroots(1e-10); // 1 2 3
Polynomial<double> pp = p.integral(2).derivative(2); // == p
NRMat<double> m(3,3); m.randomize(1.);
NRMat<double> pm = value(p,m); // p(m)
Polynomial<double> h = hermite_polynomial<double>(4);
std::cout << value(h,0.5) << ' ' << even_value(h,0.5) << std::endl;
\end{lstlisting}
\section{Rational numbers and continued fractions}\label{sec:polynomial:contfrac}
\laheader{contfrac.h, contfrac.cc}
The classes are instantiated for \code{T = int}, \code{long}, \code{long long}. Integer overflow is not detected.
\subsection{Rational}
\code{template <typename T> class Rational} is a small value class with the public members \code{T num, den}; it is not reference counted.
Results of \code{+} and \code{-} of two rationals are reduced and have a positive denominator; other operations cancel common factors only partially.
\begin{lareference}
\lafunc{Rational(); Rational(const T p, const T q); explicit Rational(const T (&a)[2])}{uninitialized; $p/q$ (not reduced); from \code{{p,q}}.}
\lafunc{explicit Rational(const ContFrac<T> &cf)}{value of the (full) continued fraction.}
\lafunc{void simplify()}{make the denominator positive and divide by the gcd. All operations keep the denominator positive.}
\lafunc{Rational operator-() const}{negation.}
\lafunc{Rational & operator+=(const T &rhs); -=, *=, /= (const T&); Rational operator+(const T &rhs) const; -, *, /}{arithmetic with an integer.}
\lafunc{Rational & operator*=(const Rational &rhs); /=; Rational operator+(const Rational &rhs) const; -, *, /; +=, -=}{rational arithmetic (\code{+}/\code{-} via the lcm of the denominators).}
\lafunc{bool operator==(const Rational &rhs) const; != >= <= > <}{via the sign of the numerator of the difference.}
\lafunc{ContFrac<T> operator+(const ContFrac<T> &rhs) const; -, *, /}{mixed arithmetic, the result is a continued fraction.}
\lafunc{std::ostream & operator<<(std::ostream &s, const Rational<T> &x); std::istream & operator>>(...)}{format \code{p/q} (input: any single separator character).}
\end{lareference}
\subsection{ContFrac}
\code{template <typename T> class ContFrac : public NRVec<T>} stores a finite simple continued fraction
\[
[a_0;a_1,\ldots,a_n]=a_0+\cfrac{1}{a_1+\cfrac{1}{\ddots+\cfrac{1}{a_n}}}
\]
in vector elements $0\ldots n$; \code{length()} returns $n$ (\code{size()} is private). A zero term $a_i=0$ at a position $i>0$ means $\infty$, i.e.\ the fraction
terminates before it. The canonical form has $a_i\ge1$ for $i>0$ and $a_n>1$ for $n>0$; comparisons are guaranteed only for canonical forms. Arithmetic with
continued fractions uses Gosper's algorithms \cite{polynomial:hakmem} (cf.\ also \url{https://perl.plover.com/classes/cftalk/TALK}), which produce the terms of the
result while consuming the terms of the operands, without forming the rational value. \code{ContFrac} is reference counted (section~\ref{sec:concepts:refcount});
its modifying members call \code{copyonwrite()} or \code{resize()}.
\begin{lareference}
\lafunc{ContFrac(); ContFrac(const int n)}{empty; length $n$ uninitialized (not the value $n$!).}
\lafunc{template<int SIZE> ContFrac(const T (&a)[SIZE]); ContFrac(const NRVec<T> &v); explicit ContFrac(const std::list<T> &x)}{from the terms $a_0,a_1,\ldots$}
\lafunc{ContFrac(T p, T q); explicit ContFrac(const Rational<T> &r)}{exact expansion of $p/q$ in canonical form (also for negative values: $a_0=\lfloor p/q\rfloor$).}
\lafunc{explicit ContFrac(double x, const int n, const T thres=0)}{expansion of a real number to at most $n$ terms; stops earlier when the remainder is zero or
(if \code{thres!=0}) when the next term would exceed \code{thres} in absolute value. The result may be non-canonical.}
\lafunc{int length() const; void resize(const int n, const bool preserve=true)}{$n$; set the length, new terms are 0 (= termination) when preserving.}
\lafunc{void canonicalize()}{bring to the canonical form (absorbs a trailing 1, truncates at the first zero term, handles negative terms).
It works directly on the terms without forming $p/q$ (no overflow for fixed-size integer \code{T}), and therefore requires that the terms
$a_1,\ldots,a_n$ all have the same sign; with mixed signs the result is wrong (use \code{ContFrac(p,q)} from \code{convergent()} if the values fit into \code{T}).}
\lafunc{void convergent(T *p, T *q, const int trunc= -1) const}{numerator and denominator of the convergent $[a_0;a_1,\ldots,a_{\mathrm{trunc}}]$ (all terms for $-1$;
\code{trunc} must not exceed \code{length()}).}
\lafunc{Rational<T> rational(const int trunc= -1) const; double value(const int trunc= -1) const}{the convergent as a \code{Rational}; as \code{double}.}
\lafunc{ContFrac reciprocal() const}{$1/x$ (removes or prepends $a_0=0$).}
\lafunc{ContFrac operator-() const}{$-x$ in canonical form, $[-a_0-1;1,a_1-1,a_2,\ldots]$.}
\lafunc{ContFrac & operator+=(const T &rhs); -=; ContFrac operator+(const T &rhs) const; -}{shift of $a_0$.}
\lafunc{ContFrac operator*(const T &rhs) const; /; ContFrac operator+(const Rational<T> &rhs) const; -, *, /}{via a homographic function (\code{Homographic::value}).}
\lafunc{ContFrac operator+(const ContFrac &rhs) const; -, *, /}{via a bihomographic function (\code{BiHomographic::value}).}
\lafunc{T compare(const ContFrac &rhs) const}{negative, zero or positive as $x<,=,>$ \code{rhs} (canonical forms).}
\lafunc{bool operator==(const ContFrac &rhs) const; != > < >= <=}{via \code{compare}.}
\lafunc{iterator begin() const; iterator end() const; iterator beyondend() const}{forward iterator over the terms; \code{end()} points past $a_n$ and
\code{beyondend()} one further (used by Gosper's algorithm to feed the final $\infty$).}
\end{lareference}
Output by the inherited \code{NRVec} operator \code{<<} prints the number of terms and the terms.
\subsection{Gosper's arithmetics}
\code{template <typename T> class Homographic} holds \code{T v[2][2]} $=\{\{a,b\},\{c,d\}\}$ representing $z=(a+bx)/(c+dx)$
(first index: numerator/denominator, second: power of $x$). \code{template <typename T> class BiHomographic} holds \code{T v[2][2][2]}
$=\{\{\{a,b\},\{c,d\}\},\{\{e,f\},\{g,h\}\}\}$ representing
\[
z=\frac{a+bx+cy+dxy}{e+fx+gy+hxy}
\]
(indices: numerator/denominator, power of $y$, power of $x$); e.g.\ $x+y$ is \code{{{{0,1},{1,0}},{{1,0},{0,0}}}}.
\begin{lareference}
\lafunc{Homographic(); explicit Homographic(const T (&a)[2][2])}{uninitialized; from the coefficients.}
\lafunc{ContFrac<T> value(const ContFrac<T> &z) const}{the continued fraction of $h(z)$.}
\lafunc{BiHomographic(); explicit BiHomographic(const T (&a)[2][2][2])}{uninitialized; from the coefficients.}
\lafunc{ContFrac<T> value(const ContFrac<T> &x, const ContFrac<T> &y) const}{the continued fraction of $z(x,y)$.}
\lafunc{input(const T &x, const bool inf) const; inputx, inputy; output(const T &z) const; bool outputready(T &x, bool first) const; int inputselect() const; bool terminate() const}{the
elementary steps of Gosper's algorithm (consume a term or $\infty$, emit a term, test whether the next term is determined, choose the operand to read next,
test for the end); used by \code{value}.}
\end{lareference}
\subsection{Example}
\begin{lstlisting}
ContFrac<int> x(11,101), v(3,7);
std::cout << Rational<int>(x+v) << ' ' << Rational<int>(x*v) << std::endl; // 380/707 33/707
BiHomographic<int> h({{{12,4},{3,1}},{{0,1},{-1,0}}}); // (x+3)(y+4)/(x-y)
std::cout << Rational<int>(h.value(x,v)) << std::endl; // -4867/113
ContFrac<long long> pi(3.14159265358979,10,1000000);
Rational<long long> r = pi.rational(3); // 355/113
\end{lstlisting}
\subsection{Pitfalls}
\begin{itemize}
\item \code{Polynomial<T>(n)} and \code{ContFrac<T>(n)} with an integer argument allocate a polynomial of degree $n$ / a fraction of length $n$; they do not
represent the number $n$.
\item The leading coefficient of a \code{Polynomial} can be zero after arithmetic; call \code{simplify()} before \code{polydiv}, \code{companion}, \code{normalize}.
\item \code{Rational} and \code{ContFrac} arithmetics silently overflow for large terms; use \code{long long}.
\item Relational operators of \code{ContFrac} are reliable only for canonical forms (e.g.\ after \code{canonicalize()} or from \code{ContFrac(p,q)}).
\end{itemize}
+102
View File
@@ -0,0 +1,102 @@
\chapter{Quaternions}\label{ch:quaternion}
\laheader{quaternion.h, quaternion.cc}
Quaternions; plain data, no reference counting.
\section{Purpose}
\code{Quaternion<T>} implements the quaternion algebra $\mathbb H$, $q=q_0+q_1\mathrm i+q_2\mathrm j+q_3\mathrm k$,
and in particular unit quaternions as a parametrization of SO(3) (and SU(2)): conversions to and
from rotation matrices, Euler angles and axis--angle, rotation of vectors, derivatives of the
rotation matrix with respect to the quaternion components, and the functions $\exp$, $\log$, power.
Like \header{vecmat3.h} it can be compiled without the rest of \LA{}: define \code{AVOID_LA}
(do not include \header{la.h}, no \code{LA_traits}), and optionally \code{AVOID_STDSTREAM},
\code{QUAT_NO_RANDOM}, \code{QUAT_NO_DOUBLE}. The class and functions are in namespace
\code{LA_Quaternion} (\emph{not} imported globally, use \code{using namespace LA_Quaternion;}).
The member functions and \code{exp}, \code{log}, \code{pow} are instantiated for \code{float}
and \code{double} only; the conversion functions taking a generic matrix type \code{M} are
templates defined in the header.
\section{Storage}
Plain data \code{T q[4]}, $q_0$ being the real (scalar) part; no reference counting.
\code{LA_traits<Quaternion<T>>} declares it plain data. The stream operators are templates in the
header, so that e.g.\ \code{Quaternion<NRMat<double>>} (a quadruple of matrices, used for
rotation matrix derivatives) can be printed.
\section{Reference}
\begin{lareference}
\lafunc{Quaternion(); Quaternion(const T (&a)[4])}{Uninitialized; from a C array (brace initialization \code{Quaternion<double> q({1,2,3,4})}).}
\lafunc{Quaternion(const T x, const T u=0, const T v=0, const T w=0)}{$x+u\mathrm i+v\mathrm j+w\mathrm k$; with one argument a real quaternion.}
\lafunc{Quaternion(const std::complex<T> &rhs)}{$\mathrm{Re} + \mathrm{Im}\,\mathrm i$.}
\lafunc{explicit Quaternion(const T *x, const int shift=1)}{Sets $q_0=0$ and copies $4-\mathrm{shift}$ values to \code{q+shift}; with the default, a pure quaternion from a 3D vector (also works with a \code{Vec3} through its pointer conversion).}
\lafunc{void identity(); void clear()}{Set to $1$ / to $0$.}
\lafunc{T operator[](const int i) const; T &operator[](const int i); operator T*()}{Element access and pointer to data.}
\lafunc{Quaternion &operator=(const T x)}{Real quaternion $x$.}
\lafunc{Quaternion &operator+=(const T); Quaternion &operator-=(const T)}{Add/subtract to the real part only (non-mutating \code{+}, \code{-} versions exist).}
\lafunc{Quaternion &operator*=(const T); Quaternion &operator/=(const T)}{Scaling (non-mutating \code{*}, \code{/} versions exist).}
\lafunc{Quaternion operator-() const; operator+=, operator-=, operator+, operator-}{Algebra with quaternions.}
\lafunc{Quaternion operator*(const Quaternion &rhs) const}{Hamilton product.}
\lafunc{Quaternion times_vec3(const T *rhs) const; Quaternion vec3_times(const T *rhs) const}{Product $q\,(0,\mathbf r)$ resp.\ $(0,\mathbf r)\,q$ with a pure quaternion given as a 3-vector (saves flops).}
\lafunc{Quaternion &conjugateme(); Quaternion conjugate() const}{$\bar q = q_0 - q_1\mathrm i - q_2\mathrm j - q_3\mathrm k$.}
\lafunc{T dot(const Quaternion &rhs) const; T normsqr() const; T norm() const}{Euclidean scalar product in $\mathbb R^4$, $|q|^2$, $|q|$.}
\lafunc{Quaternion &normalize(T *getnorm=NULL, bool unique_sign=false)}{Normalize to unit norm; optionally return the original norm; with \code{unique_sign} the sign is chosen so that $q_0\ge0$ (fixes the double cover).}
\lafunc{Quaternion &fast_normalize(bool unique_sign=false)}{As \code{normalize}, using \code{fast_sqrtinv} for \code{float}.}
\lafunc{Quaternion inverse() const; Quaternion operator/(const Quaternion &rhs) const}{$q^{-1}=\bar q/|q|^2$; $q\,\mathrm{rhs}^{-1}$.}
\lafunc{void rotate(T *to, const T *from, Quaternion<T> *grad=NULL) const}{Rotates the 3-vector \code{from} by the \emph{normalized} \code{*this}: $(0,\mathbf{to}) = \bar q\,(0,\mathbf{from})\,q$, i.e.\ $\mathbf{to}=A\,\mathbf{from}$ with $A$ from \code{normquat2rotmat(q,A)}. If \code{grad} is given (array of 3 quaternions), \code{grad[i][k]} receives $\partial\,\mathrm{to}_i/\partial q_k$.}
\lafunc{Quaternion rotateby(const Quaternion &rhs)}{Returns $\bar r\,q\,r$ for normalized $r$=\code{rhs}; \emph{the real part of} \code{*this} \emph{is ignored} (taken as 0), so this is meant for pure quaternions representing vectors.}
\lafunc{Quaternion rotate_match(T *to, const T *from, const T *match) const}{Computes \code{to} as in \code{rotate} and returns the gradient of $\frac12|\mathbf{to}-\mathbf{match}|^2$ with respect to $q$ (for gradient-descent fitting of a rotation).}
\lafunc{Quaternion commutator(const Quaternion &rhs) const; Quaternion anticommutator(const Quaternion &rhs) const}{$qr-rq$, $qr+rq$.}
\lafunc{T geodesic_distance(const Quaternion &rhs) const}{$\arccos(2(q\cdot r)^2-1)$, the rotation angle between the two (normalized) rotations.}
\lafunc{void normquat2euler(T *eul, const char *type) const}{Euler angles of the rotation, implemented as \code{rotmat2euler(eul, normquat2rotmat(q), type)}.}
\lafunc{void euler2normquat(const T *eul, const char *type)}{Unit quaternion from Euler angles by explicit formulas, consistent with \code{euler2rotmat} (see the conventions below).}
\lafunc{void normquat2eulerzyx(T *eul) const; void eulerzyx2normquat(const T *eul)}{Special pair for yaw--pitch--roll; equivalent to \code{euler2rotmat(...,"xyz",true,true,true)}; \code{eulerzyx2normquat} calls \code{euler2normquat(eul,"zyx")}.}
\lafunc{void axis2normquat(const T *axis, const T &angle); void normquat2axis(T *axis, T &angle) const}{$q=(\cos\frac\phi2, \mathbf n\sin\frac\phi2)$ for a unit axis $\mathbf n$, and back (angle in $[0,2\pi]$; undefined for the identity).}
\lafunc{void random_rotation()}{Uniformly distributed random unit quaternion (Marsaglia's method \cite{quaternion:marsaglia1972}).}
\lafunc{int fprintf(FILE *f, const char *format) const; int sprintf(char *f, const char *format) const; int fscanf(FILE *f, const char *format); int sscanf(const char *f, const char *format)}{C-style I/O with four conversions.}
\end{lareference}
Free functions (namespace \code{LA_Quaternion}; \code{M} is any type with \code{a[i][j]}
indexing: C array, \code{Mat3}, \code{NRMat}):
\begin{lareference}
\lafunc{std::ostream &operator<<(std::ostream &, const Quaternion<T> &); std::istream &operator>>(std::istream &, Quaternion<T> &)}{Four components separated by spaces.}
\lafunc{void normquat2su2mat(const Quaternion<T> &q, M &a)}{SU(2) matrix $\begin{pmatrix} q_0+\mathrm i q_1 & q_2+\mathrm i q_3\\ -q_2+\mathrm i q_3 & q_0-\mathrm i q_1\end{pmatrix}$ (elements \code{std::complex<T>}); $\pm q$ give different elements.}
\lafunc{void normquat2rotmat(const Quaternion<T> &q, M &a, bool transpose=false)}{Rotation matrix of a unit quaternion, $a_{00}=q_0^2+q_1^2-q_2^2-q_3^2$, $a_{01}=2(q_1q_2+q_0q_3)$, $a_{10}=2(q_1q_2-q_0q_3)$, \dots{} ($\pm q$ give the same matrix). \code{transpose} should give $a\Tr$ (``to match the NASA paper'').}
\lafunc{void quat2rotmat(Quaternion<T> q, M &a, bool transpose=false, const bool already_normalized=false)}{Normalizes a copy of $q$ first unless \code{already_normalized}.}
\lafunc{void normquat2rotmatder(const Quaternion<T> &q, Quaternion<M> &a, bool transpose=false)}{\code{a[k]} $=\partial A/\partial q_k$, $k=0..3$, for $A$ from \code{normquat2rotmat} (the 3$\times$3 matrices must be preallocated if \code{M} is \code{NRMat}; \code{M} must have \code{transposeme()} if \code{transpose} is used).}
\lafunc{void rotmat2normquat(const M &a, Quaternion<T> &q, bool transpose=false)}{Unit quaternion from a rotation matrix (method of Sarabandi and Thomas, branch on the sign of the trace); inverse of \code{normquat2rotmat}, convention compatible with Madgwick's MEMS paper.}
\lafunc{Quaternion<T> exp(const Quaternion<T> &x)}{$e^{x_0}(\cos|\mathbf v|, \hat{\mathbf v}\sin|\mathbf v|)$, $\mathbf v=(x_1,x_2,x_3)$.}
\lafunc{Quaternion<T> log(const Quaternion<T> &x)}{$(\ln|x|, \hat{\mathbf v}\arccos(x_0/|x|))$ -- principal branch; $\log(\exp(x))\neq x$ in general and $\log(xy)\neq\log x+\log y$.}
\lafunc{Quaternion<T> pow(const Quaternion<T> &x, const T &y)}{$x^y = |x|^y(\cos y\varphi, \hat{\mathbf v}\sin y\varphi)$, $\varphi=\arccos(x_0/|x|)$.
For real quaternions ($\mathbf v=0$) the limits are used; for a negative real $x_0$ (where $\hat{\mathbf v}$ is undefined) \code{log} and \code{pow}
choose the $i$ axis as for complex numbers: $\log x=(\ln|x_0|,\pi,0,0)$, $x^y=|x_0|^y(\cos\pi y,\sin\pi y,0,0)$.}
\end{lareference}
\paragraph{Conventions.} Note the passive convention: $A$=\code{normquat2rotmat(q)} satisfies
$(0,A\mathbf v)=\bar q\,(0,\mathbf v)\,q$; it is the transpose of the ``textbook'' active matrix
$q\,\mathbf v\,\bar q$. For all 12 Euler sequences \code{euler2normquat(e,type)} is consistent with
\code{euler2rotmat}: \code{normquat2rotmat(q)} $=$ \code{euler2rotmat(e,type)}, and \code{normquat2euler} (defined via
\code{rotmat2euler(normquat2rotmat(q))}) is its inverse, i.e.\ \code{euler2normquat(normquat2euler(q,type),type)} $=\pm q$
(the same rotation). \code{normquat2eulerzyx}/\code{eulerzyx2normquat} are the yaw--pitch--roll pair for \code{"zyx"}.
\section{Example}
\begin{lstlisting}
#include "la.h"
using namespace LA;
using namespace LA_Quaternion;
Quaternion<double> q(1.,2.,3.,4.);
q.normalize(NULL,true); //unit quaternion with q[0]>=0
Mat3<double> a;
normquat2rotmat(q,a);
Vec3<double> v(1.,-2.,.5), w;
q.rotate(w,v); //w == a*v
Quaternion<double> back;
rotmat2normquat(a,back); //back == q
Vec3<double> axis={0,1/sqrt(2),1/sqrt(2)};
Quaternion<double> r;
r.axis2normquat(axis,0.5);
Quaternion<double> rv = Quaternion<double>(v).rotateby(r);
\end{lstlisting}
\section{Pitfalls}
\lawarning{Most functions assume a \emph{normalized} quaternion (names \code{normquat...}) and do not check it. Use \code{quat2rotmat} or call \code{normalize()} first.}
\lawarning{\code{Vec3::operator*(const Vec3&)} is the cross product, not the dot product; \code{Vec3*Mat3} is a row vector times a matrix.}
+174
View File
@@ -0,0 +1,174 @@
\chapter{Regression}\label{ch:regression}
This chapter describes the kernel regression module: the class \code{REG} for kernel
regression of a scalar function of points in $\mathbb R^d$, and the class \code{REGSURF} for
regression of (potential energy and other) surfaces of molecular geometries, including the
computation of absorption spectra by the machine-learning nuclear ensemble approach (ML-NEA).
\paragraph{Authorship and status.} The files \header{reg.h}, \header{reg.cc},
\header{regsurf.h}, \header{regsurf.cc} and the test programs \header{test_reg.cc},
\header{test_regsurf.cc} state ``Kernel ridge regression module Copyright (C) 2024 Pavel Florian
\texttt{<florian43@seznam.cz>} and Jiri Pittner'', under the same GNU GPL version~3 (or later)
licence as the rest of \LA{}. The module is written in a different style from the rest of the
library: it works mostly with raw pointers into internally allocated \code{NRVec}/\code{NRMat}
objects, returns \code{unsigned int} status codes (0 = success) in addition to calling
\code{laerror}, and reads its input from text files. The headers are \emph{not} included by
\header{la.h}; include \header{reg.h} or \header{regsurf.h} explicitly. Note that both headers
include \code{<bits/stdc++.h>} (GCC specific) and contain \code{using namespace std;} at global
scope. The classes are instantiated for \code{T=double} only.
The descriptions below say what the code does.
\lawarning{The regression module is experimental and has not yet been tested much in practice: a number of
errors were found and fixed only in October 2026. The comments in the code mention an elastic-net loss, which
the fitting algorithms do not implement; the descriptions below document what the code actually does.}
%=====================================================================
\section{Kernel functions}\label{sec:regression:kernels}
\laheader{reg.h, reg.cc}
All kernels take a difference vector \code{x} of length \code{dimensions} and a width $\sigma$.
\begin{lareference}
\lafunc{T Gaussian_kernel(const T *x, const unsigned int dimensions, const T sigma)}{$\exp(-|\mathbf x|^2/2\sigma^2)$.}
\lafunc{T Laplacian_kernel(const T *x, const unsigned int dimensions, const T sigma)}{$\exp(-\sum_i|x_i|/\sigma)$ (1-norm).}
\lafunc{T Exponential_kernel(const T *x, const unsigned int dimensions, const T sigma)}{$\exp(-|\mathbf x|/\sigma)$ (2-norm).}
\lafunc{T Matern_kernel(const T *x, const unsigned int dimensions, const T sigma, const unsigned int n, const T *Matern_comb_number)}{Mat\'ern kernel of half-integer order $\nu=n+\frac12$: $e^{-r/\sigma}\sum_{k=0}^{n} c_k\,(2r/\sigma)^{n-k}$, $r=|\mathbf x|$, i.e.\ the usual form with length scale $l=\sigma\sqrt{2\nu}$ ($n=0$ exponential, $n=1$: $(1+r/\sigma)e^{-r/\sigma}$); evaluated by the Horner scheme, $k(0)=1$. \code{Matern_comb_number} must have $n+1$ elements.}
\lafunc{unsigned int Matern_coefficient(const unsigned int n, T *comb_number)}{Fills $c_k=\frac{(n+k)!}{(2n)!}\binom nk$, $k=0..n$ ($n+1$ elements), by the recurrence $c_n=1$, $c_{k-1}=c_k\,k/((n+k)(n-k+1))$ (no factorials, no overflow for large $n$).}
\end{lareference}
The kernel is selected by name: \code{"Gaussian"}, \code{"Laplacian"}, \code{"Exponential"},
\code{"Matern"}; for Mat\'ern the order $n$ is an additional parameter (rounded to an integer).
%=====================================================================
\section{REG: kernel regression in $\mathbb R^d$}\label{sec:regression:reg}
\laheader{reg.h, reg.cc}
\subsection{Model and parameters}
The training set consists of $N$ points $\mathbf x_i\in\mathbb R^d$ (array of $N\cdot d$ values,
point after point) with values $y_i$. The model is a weighted sum of kernels centred at the
training points. The parameter array is $(\lambda, \alpha, \sigma[, n])$: regularization strength
$\lambda$, mixing $\alpha\in[0,1]$ (clamped) between the ``kernel ridge'' part $\kappa=\lambda(1-\alpha)/2$
and the ``lasso'' part $\lambda\alpha$, kernel width $\sigma$ and, for the Mat\'ern kernel, the order $n$.
As implemented, the prediction for a point $\mathbf x$ is
\[
\hat y(\mathbf x) = \Bigl(\sum_j w_j k(\mathbf x-\mathbf x_j)\Bigr)\,\frac{S+\kappa}{S} + \lambda\alpha,
\qquad S=\sum_j k(\mathbf x-\mathbf x_j),
\]
(the factor is omitted if $\kappa=0$ or $S=0$).
\subsection{Construction}
\begin{lareference}
\lafunc{REG(const T *x, const T *y, const T *params, const unsigned int data_size, const unsigned int x_dimensions, const string kernel)}{Model from memory (data are copied).}
\lafunc{REG(const string x_file, const string y_file, const string param_file)}{Model from text files, see below.}
\lafunc{void Init(const T *x, const T *y, const T *params, const unsigned int data_size, const unsigned int x_dimensions, const string kernel)}{Called by the constructors; allocates the working storage. With an unknown kernel name it returns silently and the object stays uninitialized (later calls fail with laerror).}
\lafunc{~REG()}{Frees the internal buffers.}
\end{lareference}
File formats (one number per line): \code{param_file} contains $N$, $d$, the kernel name,
$\lambda$, $\alpha$, $\sigma$ and for \code{"Matern"} $n$; \code{x_file} contains $N\cdot d$
coordinates, \code{y_file} $N$ values. If a file cannot be read the constructor returns silently
without initializing the object.
\subsection{Fitting and prediction}
\begin{lareference}
\lafunc{unsigned int REG_Fit(const T max_Loss, const unsigned int max_learning_cycles)}{Iterative fit. Builds the kernel matrix $K_{ij}=k(\mathbf x_i-\mathbf x_j)$ (unit diagonal) and its row sums $s_i$ on first use, starts from $w=y$ and repeats at most \code{max_learning_cycles} times the multiplicative update $p=Kw$, $p_i\leftarrow p_i(s_i+\kappa)/s_i+\lambda\alpha$, $w_i\leftarrow w_i y_i/p_i$. If \code{max_Loss}$>0$, stops when \code{Loss_Function()}$<$\code{max_Loss}.}
\lafunc{unsigned int REG_Fit_grad(const T max_Loss, const unsigned int max_learning_cycles, const T learning_rate)}{Gradient-type variant: with $p$ as above (same $\kappa$), $g_j=\sum_k(p_k-y_k)K_{jk}/\sqrt N$ and $w_j\leftarrow w_j/(1-\eta g_j)$, $\eta$=\code{learning_rate}.}
\lafunc{T REG_RMSE()}{Root mean square deviation of the predictions stored during the last fitting cycle from the training values.}
\lafunc{T Loss_Function()}{Currently identical to \code{REG_RMSE()} (the regularization terms mentioned in the comment are not added).}
\lafunc{unsigned int REG_Get_predictions(const T *x, T *y_preds, const unsigned int data_size)}{Predictions for \code{data_size} new points (array of \code{data_size}$\cdot d$ values).}
\lafunc{unsigned int REG_Get_Score(const T *x, T *y_preds, const unsigned int data_size, const T *y, T *score)}{Predictions and \code{score[0]}$=\sum_i(y_i-\hat y_i)^2$ (sum of squares, not RMSE).}
\lafunc{unsigned int REG_Save_fitted(const string model_file); unsigned int REG_Load_fitted(const string model_file)}{Write/read the $N$ weights, one per line (the model itself must be constructed with the same data and parameters).}
\lafunc{unsigned int REG_Set_params(const T *params, const string kernel); unsigned int REG_Get_params(T *params, string *kernel)}{Set/get $(\lambda,\alpha,\sigma[,n])$ and the kernel name. If the kernel, $\sigma$ or $n$ changes, the kernel matrix is rebuilt in the next fit (a change of $\lambda$, $\alpha$ only needs no rebuild).}
\lafunc{unsigned int REG_Get_weight_coeff(T *weight)}{Copy the $N$ weights.}
\lafunc{unsigned int Build_REG_distances_mat(); unsigned int Build_REG_kernels_mat(); unsigned int Build_REG_kernels_mat_sums(); T Kernel(const T *x, const unsigned int dimensions, const unsigned int kernel_id)}{Internal steps, public in the class: difference vectors, kernel matrix, its row sums, kernel by numeric id (0 Gaussian, 1 Laplacian, 2 Exponential, 3 Mat\'ern).}
\end{lareference}
\subsection{Example}
Abridged from \header{test_reg.cc}:
\begin{lstlisting}
#include "reg.h"
using namespace LA;
REG<double> reg("reg_test_x.csv", "reg_test_y.csv", "reg_test_p.csv");
reg.REG_Fit(0.0, 20); //20 cycles, no loss threshold
double x=0.5, y;
reg.REG_Get_predictions(&x, &y, 1);
reg.REG_Save_fitted("reg_test_m.csv");
\end{lstlisting}
%=====================================================================
\section{REGSURF: regression of molecular surfaces and ML-NEA spectra}\label{sec:regression:regsurf}
\laheader{regsurf.h, regsurf.cc}
\subsection{Model}
A \code{REGSURF} model describes \code{count_levels} surfaces (e.g.\ the ground and excited state
energies) as functions of the geometry of a system of \code{count_kernels} particles (atoms) in
\code{count_dimensions} dimensions, trained on \code{count_geometries} geometries. For each
geometry the matrix of interparticle distances $r_{jk}$ is converted to a \emph{descriptor}
matrix $D_{jk}$:
\begin{itemize}
\item \code{"RE"}: $D_{jk}=r^{(0)}_{jk}/r_{jk}$, with the first training geometry as reference, zero diagonal;
\item \code{"Coulomb_matrix"}: $D_{jk}=Z_jZ_k/r_{jk}$, $D_{jj}=Z_j^{2.4}$ (needs the nuclear charges $Z$);
\item \code{"Inverse_pairs"}: $D_{jk}=1/r_{jk}$, zero diagonal;
\item a user function set by \code{s_Set_extern_descriptor}.
\end{itemize}
For geometry $g$ the kernel matrix is $K^{(g)}_{jk}=k(D^{(g)}_{jk}-D^{(0)}_{jk})$ (one-dimensional
kernel of the descriptor difference with respect to the first geometry) and its row sums
$s^{(g)}_j$ enter the prediction for level $\ell$, which is essentially $\sum_j w^{(\ell)}_j s^{(g)}_j$
with one weight per particle and level, corrected by a kernel ridge factor and the additive
$\lambda_\ell\alpha_\ell$ as in \code{REG}. The parameter array is
$(\lambda_0..\lambda_{L-1}, \alpha_0..\alpha_{L-1}, \sigma[, n])$, $L$=\code{count_levels}.
\subsection{Construction and data files}
\begin{lareference}
\lafunc{REGSURF(const unsigned int count_dimensions, const unsigned int count_kernels, const unsigned int count_geometries, const unsigned int count_levels, const string descriptor, const string kernel, const T *params, const T *geometries, const T *values, const unsigned int *Z = nullptr)}{Model from memory. \code{geometries}: $G\cdot K\cdot d$ coordinates (geometry, particle, coordinate); \code{values}: $L\cdot G$ values, level-major (all geometries of level 0 first); \code{Z}: $K$ charges (needed for \code{"Coulomb_matrix"}, otherwise \code{laerror}; may be omitted for the other descriptors).}
\lafunc{REGSURF(const string geometries_file, const string energies_file, const string parameters_file, const string Z_file = "")}{Model from text files, see below.}
\lafunc{void Init(...)}{Same arguments as the memory constructor; called by both constructors. Unknown kernel: laerror.}
\lafunc{~REGSURF()}{Frees the internal buffers.}
\end{lareference}
File formats: the parameters file contains, one per line, $d$, $K$, $G$, $L$, the descriptor
name, the kernel name, $\lambda_0..\lambda_{L-1}$, $\alpha_0..\alpha_{L-1}$, $\sigma$ and for
\code{"Matern"} $n$. The geometries file is free format: all numbers containing a decimal point
(or comma) are read in order, other tokens (atom counts, element symbols, comments) are skipped,
so concatenated XYZ files can be used. The energies file has one value per line in level-major
order, the $Z$ file one integer per line.
\subsection{Fitting, prediction and parameters}
\begin{lareference}
\lafunc{unsigned int s_Fit(const T max_Loss, const unsigned int max_learning_cycles, const T learning_rate, const bool element_invariant=true, unsigned int *kernel_classes=nullptr)}{Fits the weights of all levels: builds distances, descriptors, kernel matrices and row sums on first use, initializes the weights of level $i$ to the constant $(y_{i,0}-\lambda_i\alpha_i)/(K^2+\kappa_i)$ ($y_{i,0}$ value at the base geometry 0, $\kappa_i=\lambda_i(1-\alpha_i)K/2$) and performs at most \code{max_learning_cycles} sweeps of gradient-like updates over geometries $1..G-1$ with step \code{learning_rate}; stops a level early if \code{max_Loss}$>0$ and its loss is below \code{max_Loss}. With \code{element_invariant}, the update of each weight is replaced by the mean over the particles of the same class (\code{kernel_classes}, default the charges $Z$; without both no averaging is done).}
\lafunc{T s_RMSE(unsigned int level); T Loss_Function(unsigned int level)}{RMSE of the predictions on the training geometries for one level; \code{Loss_Function} currently returns the same.}
\lafunc{unsigned int s_Get_predictions(const unsigned int count_geometries, const unsigned int count_levels, const T *geom, const unsigned int *surfaces, T *predictions)}{Predictions for new geometries \code{geom} and the levels listed in \code{surfaces[0..count_levels-1]}; \code{predictions[i*count_levels+j]} is geometry $i$, level \code{surfaces[j]}.}
\lafunc{unsigned int s_Score(const unsigned int count_geometries, const unsigned int count_levels, const T *geom, const unsigned int *surfaces, T *predictions, const T *y_values, T *score)}{Predictions and their RMSE against \code{y_values} in \code{score[0]}.}
\lafunc{unsigned int s_Save_fitted(const string weights); unsigned int s_Load_fitted(const string weights)}{Write/read the $L\cdot K$ weights, one per line.}
\lafunc{unsigned int s_Set_params(const string descriptor, const string kernel, const T *params); unsigned int s_Get_params(string *descriptor, string *kernel, T *params)}{Change/query descriptor, kernel and parameter array; a change of descriptor, kernel, $\sigma$ or $n$ invalidates the precomputed matrices (rebuilt in the next fit; $\lambda$, $\alpha$ need no rebuild). Unknown descriptor or kernel names raise \code{laerror} (also in the constructors). The descriptor is always set from its name, so a descriptor installed by \code{s_Set_extern_descriptor} must be installed again after \code{s_Set_params}.}
\lafunc{unsigned int s_Set_extern_descriptor(unsigned int (*extern_func)(const unsigned int, const unsigned int, const T*, T*))}{Use a user descriptor function \code{f(count_particles, count_geometries, distances, desc)} which maps the $K\times K$ distance matrices of all geometries (consecutive) to descriptor matrices of the same layout.}
\lafunc{unsigned int s_Get_weight_coeff(const unsigned int s_number, T *weights)}{Copy the $K$ weights of level \code{s_number}.}
\lafunc{T Kernel(const T *x, const unsigned int dimensions, const unsigned int kernel_id); unsigned int Build_s_distances_mat(...); unsigned int Build_s_descriptor_mat(...); unsigned int Build_s_kernels_mat(...); unsigned int Build_s_kernels_mat_sums()}{Internal steps, public in the class.}
\end{lareference}
Free functions \code{Build_s_RE_descriptor_mat}, \code{Build_s_coulomb_}\allowbreak\code{matrix_descriptor_mat},
\code{Build_s_inverse_}\allowbreak\code{pairs_descriptor_mat} (\code{count_particles, count_geometries, distances, desc}
and the reference distances resp.\ $Z$) implement the built-in descriptors.
\subsection{ML-NEA absorption spectra}
\begin{lareference}
\lafunc{unsigned int s_Load_ML_NEA_geometries(const unsigned int count_geometries, const T *geometries); unsigned int s_Load_ML_NEA_geometries(const unsigned int count_geometries, const string geometries_file)}{Load an ensemble of geometries (e.g.\ sampled from a ground-state distribution) from memory or from a free-format geometry file as above.}
\lafunc{unsigned int s_Predict_ML_NEA_geometries()}{Predict all levels for all ensemble geometries.}
\lafunc{unsigned int s_Compute_ML_NEA_spectra(const T E_min, const T step, const unsigned int count_steps, const T delta)}{Spectrum on the grid $E_k=E_{\min}+k\cdot\mathrm{step}$, $k<\mathrm{count\_steps}$, as a sum over geometries and levels of Gaussians of width $\delta$ centred at the predicted excitation energies $\Delta E$ (energies assumed in atomic units), weighted by $\Delta E\,f/E$. The Gaussians are normalized, $\sqrt{2/\pi}\,\delta^{-1}\exp(-2(E-\Delta E)^2/\delta^2)$ (standard deviation $\delta/2$), and only grid points within $20\delta$ of $\Delta E$ are evaluated (beyond that the exponential underflows to zero). The oscillator strength $f=\Delta E_{\min}/\Delta E$ ($\Delta E_{\min}$ the minimum of the level over the ensemble) is only a placeholder for the true oscillator strength, which the model does not predict.}
\lafunc{unsigned int s_Save_ML_NEA_spectra(const string ML_NEA_spectra_file)}{Write the spectrum normalized to unit sum, one value per line.}
\end{lareference}
\subsection{Example}
Abridged from \header{test_regsurf.cc} (10 levels):
\begin{lstlisting}
#include "regsurf.h"
using namespace LA;
REGSURF<double> regsurf("xyz.dat", "E_sum.dat", "conf_surf.dat", "Z.dat");
regsurf.s_Fit(0.0, 100, 0.5);
for(unsigned int i=0; i<10; ++i) cout << regsurf.s_RMSE(i) << endl;
regsurf.s_Load_ML_NEA_geometries(50000, "nea_geoms.xyz");
regsurf.s_Predict_ML_NEA_geometries();
regsurf.s_Compute_ML_NEA_spectra(0.00, 0.0025, 4000, 0.2);
regsurf.s_Save_ML_NEA_spectra("spectra.dat");
\end{lstlisting}
\section{Pitfalls}\label{sec:regression:pitfalls}
\lawarning{\code{REG} and \code{REGSURF} hold raw pointers to their own buffers and have no user-defined copy constructor or assignment: copying an object leads to double deletion. Pass them by reference.}
\lawarning{Errors in the input files are partly reported by \code{laerror} and partly lead to a silently uninitialized object (\code{REG}); check the return codes and the results.}
+265
View File
@@ -0,0 +1,265 @@
\chapter{Packed symmetric and hermitian matrices: NRSMat}\label{ch:smat}
\laheader{smat.h, smat.cc}
\section{Purpose}\label{sec:smat:purpose}
\code{NRSMat<T>} 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<T>} 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<T>}.
\code{NRSMat_from1<T>} (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{std::complex<double>} and the integer types \code{char}, \code{short}, \code{int}, \code{long}, \code{long long} and
their unsigned variants (\code{float} is not instantiated).
Most numerical members have BLAS-based specializations only for the two types \code{double} and
\code{std::complex<double>}. Some members are defined only for these two 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<n),
\end{equation}
so the order is $A_{00}, A_{10}, A_{11}, A_{20}, A_{21}, A_{22}, \ldots$
\code{size()} returns the length of the packed data $n(n+1)/2$, \emph{not} $n$; \code{nrows()} and
\code{ncols()} both return $n$.
The packed data can be accessed linearly by \code{operator[]} and by the conversion to a raw pointer.
The data of an \code{NRSMat<T>} can be viewed as an \code{NRVec<T>} 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,j<n$, either order: $(i,j)$ and $(j,i)$ give the same result.}
\lafunc{size_t SMat_index_igej(T i, T j); size_t SMat_index_ilej(T i, T j)}{As \code{SMat_index}, faster, assuming $i\ge j$ resp.\ $i\le j$ (not checked).}
\lafunc{size_t SMat_index_1(T i, T j)}{Index for indices counting from 1 ($1\le i,j\le n$): $i(i-1)/2+j-1$ for $i\ge j$; the resulting packed index counts from 0.}
\lafunc{size_t SMat_index_1igej(T i, T j); size_t SMat_index_1ilej(T i, T j)}{As \code{SMat_index_1}, assuming $i\ge j$ resp.\ $i\le j$.}
\lafunc{size_t ASMat_index(T i, T j)}{Packed index of the strictly lower triangle (antisymmetric matrices, used by \ref{ch:fourindex}): $i(i-1)/2+j$ for $i>j$, 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<j$. For real \code{T} this is just the
symmetric matrix. The arithmetic of the library follows this convention:
\begin{itemize}
\item products: \code{NRSMat*NRVec}, \code{NRSMat*NRMat}, \code{NRMat*NRSMat}, \code{NRVec*NRSMat} ($x\Tr H$),
\code{NRSMat*NRSMat}, \code{NRVec::gemv} (\code{zhpmv}, \code{trans}='t' gives $H\Tr x = H^* x$),
\code{rank2update} (\code{zhpr2}), \code{diagonalize()}, \code{linear_solve()}, \code{trace2()} and the other
functions of chapter~\ref{ch:nonclass};
\item conversions: \code{NRMat<T>(const NRSMat<T>&)} conjugates the upper triangle and
\code{NRSMat(const NRMat<T>&)} takes the hermitian part $(A+A\Hc)/2$;
\item \code{fprintf()}, \code{sum()} and \code{NRMat<T>::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<int>&)}, \code{rsum()} and \code{csum()} are built on
\code{operator()} and therefore use the stored element also for $i<j$ (no conjugation);
\item \code{randomize()} produces a hermitian matrix (real diagonal); \code{conjugateme()}/\code{conjugate()}
conjugate all stored elements, which is the transposition of a hermitian matrix.
\end{itemize}
\lawarning{Element access does \emph{not} follow the hermitian convention: \code{operator()(i,j)} (both the const
and the non-const version, also of \code{NRSMat_from1}) and \code{operator[]} just access the storage. $(i,j)$ and $(j,i)$
refer to the same stored element $A_{\max(i,j),\min(i,j)}$, and \emph{no} complex conjugation is performed for $i<j$:
\code{s(0,1)} returns $A_{10}$, not $A_{01}=A_{10}^*$. Code reading the upper triangle of a complex hermitian matrix must
conjugate itself, e.g.\ \code{std::conj(s(j,i))} for $i<j$, and must write only the elements with $i\ge j$.}
\textbf{Planned extension.} \code{NRSMat} might in the future be extended by a \code{bool} member \code{hermitian}
which would toggle the behaviour for complex types between the hermitian and the complex symmetric interpretation
(element access, conversions, products, \code{operator<<}, \code{permuted()}, \code{submatrix()}, \code{rsum()}, \code{csum()}).
The conversion \code{Tensor(const NRSMat&)} (chapter~\ref{ch:tensor}), which now marks complex matrices as hermitian (symmetry 2),
would then have to respect the flag.
\subsection{Reference counting}
\code{NRSMat} follows the copy-on-demand scheme of section~\ref{sec:concepts:refcount}: the copy
constructor and \code{operator=} share the data, \code{operator|=} copies deeply, all modifying members
call \code{copyonwrite()} (I found no member modifying data without it, except the broken constructor
from a raw pointer, see below), \code{resize()} detaches, and in DEBUG builds the non-const
\code{operator[]} and \code{operator()} raise an error if the data are shared.
Note that the constructor \code{NRSMat(const NRVec<T>&, int)} and \code{NRVec<T>(const NRSMat<T>&)}
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<T>::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<double>}.}
\lafunc{explicit NRSMat(const NRMat<T> &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<T> &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<T> &rhs); explicit NRSMat(const SparseSMat<T> &rhs)}{Dense from sparse, see \ref{ch:sparse}.}
\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<T>(const NRSMat<T>&)} expands to a full square matrix
(hermitian for complex types, see section~\ref{sec:smat:hermitian}); \code{explicit NRVec<T>(const NRSMat<T>&)} views the
packed data as a vector (shared data). \code{NRMat<T>::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,j<n$, either order; $(i,j)$ and $(j,i)$ refer to the same storage; plain storage access, no complex conjugation for $i<j$ (see section~\ref{sec:smat:hermitian}). Range checked in DEBUG.}
\lafunc{T& operator[](const size_t ij); const T& operator[](const size_t ij) const}{Packed element number \code{ij}, $0\le$ \code{ij} $<n(n+1)/2$, see (\ref{eq:smat:index}).}
\lafunc{operator T*(); operator const T*() const}{Pointer to the packed data.}
\lafunc{int nrows() const; int ncols() const}{Order $n$.}
\lafunc{size_t size() const}{Number of stored elements $n(n+1)/2$.}
\lafunc{bool transp(const int i, const int j) const}{Returns \code{i>j}; 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<T> operator+(const NRMat<T> &rhs) const; const NRMat<T> operator-(const NRMat<T> &rhs) const}{Sum/difference with a dense matrix, dense result.}
\lafunc{const NRMat<T> 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<T> operator*(const NRMat<T> &rhs) const}{$S A$ by \code{dspmv}/\code{zhpmv} column by column.}
\lafunc{const NRVec<T> operator*(const NRVec<T> &rhs) const}{$S x$ (\code{dspmv}/\code{zhpmv}).}
\lafunc{const NRVec<std::complex<T> > operator*(const NRVec<std::complex<T> > &rhs) const}{Real symmetric matrix times complex vector (real and imaginary parts separately).}
\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 S x$, forwards to \code{r.gemv(beta,*this,trans,alpha,x)}; \code{trans} is ignored.}
\lafunc{void gemv(const T beta, NRVec<std::complex<T> > &r, const char trans, const T alpha, const NRVec<std::complex<T> > &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<T> &x, const NRVec<T> &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} for \code{double}/\code{complex<double>}, 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<T> &rhs) const}{Dot product of the packed data with a vector of length $n(n+1)/2$.}
\lafunc{const typename LA_traits<T>::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<T> rsum() const; const NRVec<T> 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|$); \code{double} and \code{complex<double>} only.}
\lafunc{const T* diagonalof(NRVec<T> &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<T>(*this).inverse()}, symmetrized back. \code{double} and \code{complex<double>} only.}
\lafunc{NRSMat& conjugateme(); const NRSMat conjugate() const}{Complex conjugation of all stored elements (no-op for real types).}
\lafunc{NRSMat<typename LA_traits<T>::normtype> abs() const}{Elementwise absolute values.}
\lafunc{NRSMat<typename LA_traits<T>::normtype> diffabs(const NRSMat &rhs) const}{Elementwise $|A_{ij}|-|B_{ij}|$.}
\lafunc{void randomize(const typename LA_traits<T>::normtype &x)}{Uniformly distributed elements in $(-x,x)$; complex: real and imaginary parts independent, imaginary part of the diagonal set to zero. \code{double} and \code{complex<double>} 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<int> &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<int> &selection, const NRSMat &rhs)}{$A_{s_i s_j} = B_{ij}$.}
\lafunc{const NRSMat permuted(const NRPerm<int> &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<T>::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<T> &x)}{Text output: \code{n n}, then the full square matrix row by row.}
\lafunc{std::istream& operator>>(std::istream &s, NRSMat<T> &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<T> operator+(const T &a, const NRSMat<T> &rhs); const NRSMat<T> operator*(const T &a, const NRSMat<T> &rhs)}{Scalar from the left: $a\cdot 1 + S$, $aS$.}
\lafunc{const NRSMat<T> operator-(const T &a, const NRSMat<T> &rhs) = delete}{Deliberately not available (compile error); write \code{-S+a} for $a\cdot1-S$.}
\lafunc{NRSMat<std::complex<T> > complexify(const NRSMat<T> &rhs)}{Complex copy of a real matrix.}
\lafunc{void NRSMat_convert(NRSMat<T> &a, const NRSMat<S> &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<T>} has \code{operator+=}, \code{operator-=},
\code{operator+}, \code{operator-} and \code{operator*} with an \code{NRSMat<T>} right operand, and
\code{NRVec<T>} has \code{gemv} with an \code{NRSMat} and \code{operator*(const NRSMat<T>&)} (vector from the left);
see \ref{ch:mat} and \ref{ch:vec}.
\section{NRSMat\_from1}\label{sec:smat:from1}
\code{NRSMat_from1<T>} is derived from \code{NRSMat<T>} 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<T>}, 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<T> &rhs)}{Implicit conversion from the base class (shallow).}
\lafunc{explicit NRSMat_from1(const NRMat<T> &rhs); explicit NRSMat_from1(const NRVec<T> &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<double> s(n);
for(int i=0; i<n; ++i) for(int j=0; j<=i; ++j) s(i,j) = 1./(1.+i+j); //fill lower triangle
NRVec<double> x(1.,n);
NRVec<double> y = s*x; //dspmv
NRMat<double> full(s); //full square copy
NRSMat<double> 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<double> &sc = s;
double sum=0; for(size_t k=0; k<sc.size(); ++k) sum += sc[k]; //packed loop
NRVec<double> w(n); NRMat<double> v(n,n);
NRSMat<double> 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<T>(s).norm()} or \code{trace2()}.
\item For complex types read section~\ref{sec:smat:hermitian} carefully.
\item \code{NRSMat(const NRVec<T>&,int)} and \code{NRVec<T>(const NRSMat<T>&)} share data with their argument.
\item \code{NRSMat(const NRMat<T>&)} silently symmetrizes (takes the hermitian part for complex types), it does not check symmetry.
\item Complex matrices: \code{operator()(i,j)} with $i<j$ does not conjugate (plain storage access, section~\ref{sec:smat:hermitian}).
\item Products of two symmetric matrices return \code{NRMat<T>}; 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}
+367
View File
@@ -0,0 +1,367 @@
\chapter{Sparse matrices}\label{ch:sparse}
\laheader{sparsemat.h, sparsemat.cc, sparsesmat.h, sparsesmat.cc}
\section{Overview}\label{sec:sparse:overview}
\LA{} offers two 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{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}
\code{CSRMat<T>} (section~\ref{sec:sparse:csr}) is unfinished.
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}{Threshold: elements with absolute value below (or equal to) it are dropped by \code{simplify()} and by most constructors and accumulating operations.}
\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} and \code{std::complex<double>}.
Several operations are implemented for \code{double} 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$ \code{SPARSEEPSILON} 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}|>$ \code{SPARSEEPSILON}.}
\lafunc{explicit SparseMat(const NRSMat<T> &rhs)}{From a packed symmetric matrix; symmetric flag set, lower triangle stored.}
\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$ \code{SPARSEEPSILON}, 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 double sparseepsilon=SPARSEEPSILON)}{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} 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} only (\code{complex<double>} raises ``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} 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>>}. 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}|>$ \code{SPARSEEPSILON}).}
\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)}{Declared but not implemented (\code{CSRMat} is not supported).}
\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}|<$ \code{SPARSEEPSILON} 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{CHOLESKYEPSILON}$=10^{-16}$ are skipped (rank-deficient case), elements below \code{SPARSEEPSILON} 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$ \code{SPARSEEPSILON} 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>>} from real ones. The generic \code{exp()} of \code{matexp.h} works with
\code{SparseSMat}.
\section{CSRMat}\label{sec:sparse:csr}
\laheader{csrmat.h}
\code{CSRMat<T>} 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.
\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;
}
\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.
\end{itemize}
+404
View File
@@ -0,0 +1,404 @@
\chapter{Tensors}\label{ch:tensor}
\laheader{tensor.h, tensor.cc}
\section{Purpose and main idea}\label{sec:tensor:purpose}
\code{Tensor<T>} is a dense tensor of arbitrary rank whose indices are organized in \emph{index groups}.
Each group has its own index range, offset and permutational symmetry (none, symmetric, antisymmetric,
hermitean, antihermitean), and only the permutationally non-equivalent elements of a group are stored.
Indices can optionally carry names (\code{INDEXNAME}) and most operations can address indices by name.
The class provides element access with automatic sign/conjugation handling, contractions implemented via
unwinding to matrices and BLAS \code{gemm}, reordering of indices and groups, (anti)symmetrization by a
general permutation algebra, the HOSVD/Tucker decomposition, conversions to and from \code{NRVec},
\code{NRMat}, \code{NRSMat} and the dense four-index classes of chapter~\ref{ch:fourindex}, and binary,
text and NumPy I/O.
Typical uses are many-body quantities (amplitudes, integrals, density matrices) where antisymmetry
in groups of indices should be exploited for storage.
Limitations stated in the source:
\begin{itemize}
\item the implementation is ``rudimentary''; only \code{Tensor<double>} and \code{Tensor<std::complex<double> >}
are instantiated (tensor.cc);
\item index types \code{LA_index} and \code{LA_largeindex} are \code{int}, so the total size is limited
to $2^{31}$ elements (\code{NRVec} limitation);
\item symmetry \emph{between} index groups (e.g.\ $(ij|kl)=(kl|ij)$) is not supported; one would have to
nest \code{Tensor<Tensor<T> >} or use the four-index classes;
\item covariant and contravariant indices are not distinguished: the header \code{#undef}s
\code{LA_TENSOR_INDEXPOSITION}, the code guarded by this macro is not active.
\end{itemize}
\section{Storage and concepts}\label{sec:tensor:storage}
\subsection{Index groups}
\begin{lstlisting}
typedef class INDEXGROUP {
public:
int number; // number of indices in the group (>0)
int symmetry; // -2,-1,0,1,2, see below
LA_index offset; // indices run offset ... offset+range-1
LA_index range;
bool operator==(const INDEXGROUP &rhs) const; bool operator!=(const INDEXGROUP &rhs) const;
} INDEXGROUP;
\end{lstlisting}
\code{INDEXGROUP} is a plain aggregate without constructor; all members must be set by the user.
If the macro \code{LA_TENSOR_ZERO_OFFSET} is defined before including the header, \code{offset} becomes a
\code{static const} zero (which saves some arithmetic); otherwise any offset, also negative, is allowed.
Index groups are compared memberwise.
\begin{center}
\begin{tabular}{@{}rp{0.3\linewidth}p{0.17\linewidth}p{0.3\linewidth}@{}}
\toprule
\code{symmetry} & meaning & stored elements & odd permutation gives\\
\midrule
0 & no symmetry & $r^m$ & ---\\
1 & symmetric & $\binom{r+m-1}{m}$ & $+x$\\
$-1$ & antisymmetric & $\binom{r}{m}$ & $-x$; zero for a repeated index\\
2 & hermitean (complex \code{T} only) & $\binom{r+m-1}{m}$ & $x^*$\\
$-2$ & antihermitean (complex \code{T} only) & $\binom{r+m-1}{m}$ & $-x^*$\\
\bottomrule
\end{tabular}
\end{center}
Here $m$ is \code{number} and $r$ is \code{range}. For real \code{T} the symmetries $\pm 2$ are illegal
(\code{canonicalize_shape()} raises an error); a group with a single index always gets symmetry 0.
For (anti)hermitean groups the diagonal elements (repeated indices) are stored including their
real/imaginary part which should vanish; this is \emph{not} checked automatically, see
\code{enforce_hermiticity()} and \code{fulfills_hermiticity()}.
The symmetry factors of different groups combine multiplicatively (for complex \code{T} via a small group
multiplication table, so that e.g.\ two conjugations cancel).
\subsection{Storage order}
The element data are kept in the public member \code{NRVec<T> data}. The \emph{leftmost} index is the least
significant, i.e.\ it changes fastest: group 0 is the innermost loop, and inside a non-symmetric group
the first index is the fastest. For a group with $m$ indices $I_0,\dots,I_{m-1}$ and offset subtracted, the
position inside the group is
\begin{align*}
&\textstyle\sum_{k} I_k\, r^k && \text{(no symmetry)},\\
&\textstyle\sum_{k} \binom{I_k+k}{k+1}, \quad I_0\le I_1\le\dots\le I_{m-1} && \text{(symmetric, (anti)hermitean)},\\
&\textstyle\sum_{k} \binom{I_k}{k+1}, \quad I_0< I_1<\dots< I_{m-1} && \text{(antisymmetric)},
\end{align*}
i.e.\ the canonical (stored) element has nondecreasing (increasing) indices; for $m=2$ this is the
usual lower-triangle packing \code{i1*(i1+1)/2+i0} of \code{NRSMat}. The total position is
$\sum_g p_g\,\mathtt{cumsizes}[g]$, where \code{groupsizes[g]} is the number of stored elements of group $g$
and \code{cumsizes[g]} the product of the sizes of groups $0\dots g-1$.
The binomial coefficients are taken from the precomputed table of \code{simplicial()} (miscfunc.h),
which limits symmetric groups to fewer than 16 indices and ranges below 1024.
\subsection{Public data members}
All data members are public:
\begin{lareference}
\lafunc{NRVec<INDEXGROUP> shape}{the index groups; \code{shape.size()==0} for a scalar (rank 0).}
\lafunc{NRVec<T> data}{the stored elements.}
\lafunc{int myrank}{total number of indices (sum of \code{number} over groups), $-1$ for a default-constructed tensor.}
\lafunc{NRVec<LA_largeindex> groupsizes; NRVec<LA_largeindex> cumsizes}{redundant, precomputed from \code{shape} by \code{calcsize()}.}
\lafunc{NRVec<INDEXNAME> names}{index names in flat order (all indices of group 0, then group 1, \dots);
either empty (unnamed tensor) or of length \code{rank()}.}
\end{lareference}
If \code{shape} is modified directly, \code{calcsize()}, \code{calcrank()} and possibly \code{data.resize()}
must be called by the user to restore consistency.
\subsection{Index types}
\begin{lareference}
\lafunc{typedef int LA_index; typedef int LA_largeindex}{an index value; a position in \code{data}.}
\lafunc{typedef NRVec<LA_index> FLATINDEX}{all indices of an element in flat order.}
\lafunc{typedef NRVec<FLATINDEX> SUPERINDEX}{indices structured by groups: \code{I[g][i]}.}
\lafunc{typedef NRVec<LA_largeindex> GROUPINDEX}{one position (inside the compressed group storage) per group.}
\lafunc{typedef NRMat<LA_index> INDEXMATRIX}{rows are the \code{FLATINDEX}es of all stored elements, in the order of \code{data}.}
\lafunc{struct INDEX {int group; int index;}}{position of one index: group number and index number inside the group (both from 0); constructors \code{INDEX()} and \code{INDEX(int g, int i)}, \code{operator==}.}
\lafunc{typedef NRVec<INDEX> INDEXLIST}{a list of indices.}
\lafunc{struct INDEXNAME {char name[N_INDEXNAME];}}{index name, \code{N_INDEXNAME}=8; constructible from \code{const char *} (copied by \code{strncpy}), so a string literal can be passed wherever an \code{INDEXNAME} is expected; \code{operator=}, \code{==}, \code{!=} compare by \code{strncmp}. Use at most 7 characters so that the name stays NUL-terminated.}
\end{lareference}
\code{LA_traits} are specialized for \code{INDEXNAME}, \code{INDEXGROUP} and \code{INDEX} as plain data,
so that vectors of them can be compared and written/read in binary form (section~\ref{sec:concepts:traits},
\ref{sec:concepts:io}).
\subsection{Reference counting and copy on demand}
\code{Tensor} itself has no reference counter, but all its large members are \code{NRVec}s, so the
scheme of section~\ref{sec:concepts:refcount} applies member by member: the copy constructor and
\code{operator=} share \code{data}, \code{shape}, \code{names} etc.\ (shallow copy), and
\code{copyonwrite()} detaches all of them. There is \emph{no} \code{operator|=}; a deep copy is made by
\code{Tensor<T> b(a); b.copyonwrite();}.
Arithmetic members delegate to \code{NRVec} methods which call \code{copyonwrite()}; contractions call
\code{data.copyonwrite()}; results of restructuring operations are freshly allocated.
An explicit \code{copyonwrite()} is needed before writing through \code{lhs()}, which, like a non-const
\code{operator[]}, raises an error in DEBUG builds when the data are shared. The element reading \code{operator()} is
\code{const} only, so reading a shared tensor is safe. \code{loopover()} and \code{grouploopover()} pass writable pointers
to the callback and therefore detach the data first (one copy if shared, otherwise only a check); use
\code{constloopover()}/\code{constgrouploopover()} for reading only, they never copy. \code{renameindex()},
\code{permute_inside_group()}, \code{enforce_hermiticity()} and \code{flatten()} also detach what they modify.
\section{Construction, assignment, conversion}\label{sec:tensor:construction}
\begin{lareference}
\lafunc{Tensor()}{empty tensor, \code{rank()==-1}.}
\lafunc{explicit Tensor(const T &x)}{scalar (rank 0) with value \code{x}.}
\lafunc{Tensor(const NRVec<INDEXGROUP> &s); Tensor(const INDEXGROUP &g)}{allocate a tensor of the given shape (data not initialized); a single group can be given directly. The shape is canonicalized (\code{canonicalize_shape()}).}
\lafunc{Tensor(const NRVec<INDEXGROUP> &s, const NRVec<INDEXNAME> &newnames); Tensor(const INDEXGROUP &g, const NRVec<INDEXNAME> &newnames)}{the same with index names; \code{newnames} must be empty or of length \code{rank()}.}
\lafunc{Tensor(const NRVec<INDEXGROUP> &s, const NRVec<T> &mydata); Tensor(const NRVec<INDEXGROUP> &s, const NRVec<T> &mydata, const NRVec<INDEXNAME> &newnames)}{use \code{mydata} as the data (shared, not copied); its size must match the shape.}
\lafunc{Tensor(int xrank, const NRVec<INDEXGROUP> &xshape, const NRVec<LA_largeindex> &xgroupsizes, const NRVec<LA_largeindex> xcumsizes, const NRVec<T> &xdata); Tensor(..., const NRVec<INDEXNAME> &xnames)}{low-level constructor setting all members directly without any check.}
\lafunc{Tensor(const Tensor &rhs); Tensor& operator=(const Tensor &rhs)}{shallow copy, see above.}
\lafunc{void resize(const NRVec<INDEXGROUP> &s)}{set a new shape, reallocate data (not initialized) and delete the names.}
\lafunc{void deallocate()}{release data, shape, names and auxiliary arrays.}
\lafunc{void copyonwrite()}{detach all members (deep copy if shared).}
\end{lareference}
\subsection{Conversions}
\begin{lareference}
\lafunc{explicit Tensor(const NRVec<T> &x)}{rank-1 tensor, offset 0, data shared with \code{x}.}
\lafunc{explicit Tensor(const NRMat<T> &x, bool flat=false)}{rank-2 tensor with a copy of the data and offset 0. A square matrix gives one non-symmetric group of 2 indices unless \code{flat} is true; otherwise two groups of one index. Because of the storage order, \code{t(i,j)==x(j,i)}: tensor index 0 is the column index.}
\lafunc{explicit Tensor(const NRSMat<T> &x)}{one group of two indices, data shared with \code{x}; symmetric (symmetry 1) for real, hermitian (symmetry 2) for complex types, as \code{NRSMat} itself (chapter~\ref{ch:smat}); equal to \code{Tensor(NRMat<T>(x))}. If \code{NRSMat} gets the planned \code{hermitian} flag (section~\ref{sec:smat:hermitian}), this constructor has to respect it.}
\lafunc{NRMat<T> matrix(int n=1) const}{reinterpret the data as a matrix whose column index runs over the $n$ leftmost index groups (e.g.\ an index made leftmost by \code{unwind_index()}) and whose row index runs over the remaining groups. The matrix \emph{shares} the data with the tensor. \code{Tensor<T>(x).matrix()} reproduces \code{x}.}
\lafunc{template<fourindexsymtype S, typename T, typename I> Tensor<T> fourindex2tensor(const fourindex_dense<S,T,I> &f)}{conversion from a dense four-index object (chapter~\ref{ch:fourindex}), see the table below; indices get offset 1.}
\lafunc{template<fourindexsymtype S, typename T, typename I> void tensor2fourindex(const Tensor<T> &t, fourindex_dense<S,T,I> &f)}{inverse conversion; shape, ranges, offsets and symmetries are checked.}
\end{lareference}
\code{fourindex2tensor}/\code{tensor2fourindex} are implemented (overloaded) for the following symmetry types.
Since the leftmost tensor index is the least significant, \code{f(i,j,k,l)} corresponds to \code{t(k,l,i,j)}
(for the types marked * this is equal to \code{t(i,j,k,l)} or \code{t(l,k,j,i)} by symmetry):
\begin{center}
\begin{tabular}{@{}p{0.5\linewidth}p{0.46\linewidth}@{}}
\toprule
\code{fourindexsymtype} & tensor shape\\
\midrule
\code{nosymmetry} & 4 groups of 1 index, range $n$\\
\code{twoelectronrealmullikanAB}* & 2 symmetric groups of 2 indices\\
\code{twoelectronrealmullikan}* & 2 symmetric groups of 2 indices (the pair symmetry is lost, data expanded)\\
\code{T2IjAb_aces} & 4 groups of 1 index, ranges \code{nvra, nvrb, noca, nocb}\\
\code{T2ijab_aces} & 2 antisymmetric groups of 2 indices, ranges \code{nvrt}, \code{nocc}\\
\code{antisymtwoelectronrealdiracAB}* & 4 groups of 1 index (data expanded)\\
\code{antisymtwoelectronrealdirac}* & 2 antisymmetric groups of 2 indices (data expanded)\\
\bottomrule
\end{tabular}
\end{center}
In the reverse direction the symmetric matrix of pairs is obtained by symmetrizing the tensor data.
\subsection{NumPy files}
\begin{lareference}
\lafunc{void numpy_write(const char *name, const char *descr="<f8") const}{write a flat tensor (\code{is_flat()}) as a NumPy \code{.npy} file (format version 1.0, C order, the shape is written in reverse so that the NumPy array index order equals the tensor index order). \code{descr} must match \code{T} (\code{"<f8"} for \code{double}, \code{"<c16"} for complex); this is not checked.}
\lafunc{void numpy_read(const char *name)}{read a \code{.npy} file (version 1.0, C or Fortran order) into a flat tensor with offsets 0; the type is not checked.}
\end{lareference}
\section{Element access}\label{sec:tensor:access}
\begin{lareference}
\lafunc{T operator()(LA_index i1...) const}{element by a variable list of \code{rank()} indices (of type \code{int}); returns the value including the symmetry sign/conjugation, 0 for a repeated index in an antisymmetric group.}
\lafunc{T operator()(const FLATINDEX &I) const; T operator()(const SUPERINDEX &I) const}{the same with the indices in a vector.}
\lafunc{Signedpointer<T> lhs(LA_index i1...); Signedpointer<T> lhs(const FLATINDEX &I); Signedpointer<T> lhs(const SUPERINDEX &I)}{element as an l-value: the returned proxy supports \code{=}, \code{+=}, \code{-=} (the value is transformed by the sign/conjugation of the permutation before being stored), \code{*=} and \code{/=} (for an element stored as a conjugate the stored value is multiplied by the conjugated factor, so that the element itself is multiplied by the factor). Writes to elements with a repeated index of an antisymmetric group are ignored (a DEBUG build prints a warning once). Call \code{copyonwrite()} first if the tensor may be shared.}
\lafunc{LA_largeindex index(int *sign, const SUPERINDEX &I) const; LA_largeindex index(int *sign, const FLATINDEX &I) const}{position in \code{data} of the canonical element and the sign: 1, $-1$, 2 (conjugate), $-2$ (negative conjugate), or 0 and position $-1$ for an element which vanishes by antisymmetry. Index ranges are checked in DEBUG builds.}
\lafunc{LA_largeindex vindex(int *sign, LA_index i1, va_list args) const}{the same for a \code{va_list}; ends \code{args}.}
\lafunc{SUPERINDEX inverse_index(LA_largeindex s) const}{canonical indices of the element stored at position \code{s} (not efficient).}
\end{lareference}
\subsection{Looping over all elements}
\begin{lareference}
\lafunc{void loopover(void (*callback)(const SUPERINDEX &, T *), bool skipzeros=false)}{call \code{callback} for every stored element with its canonical indices and a pointer into \code{data}. With \code{skipzeros} the index value 0 is skipped in all indices (useful e.g.\ for indices running $\dots,-2,-1,1,2,\dots$ with a negative offset); the pointer passed to the callback always points to the element of the passed indices (with \code{skipzeros} it is computed from the indices).}
\lafunc{void constloopover(void (*callback)(const SUPERINDEX &, const T *), bool skipzeros=false) const}{the same for reading.}
\lafunc{void grouploopover(void (*callback)(const GROUPINDEX &, T *)); void constgrouploopover(void (*callback)(const GROUPINDEX &, const T *)) const}{loop over all stored elements with the position inside each group's compressed storage instead of the individual indices.}
\lafunc{INDEXMATRIX indexmatrix() const}{matrix whose row $s$ is the flat index of \code{data[s]}; allows a simple \code{for} loop over all elements instead of a callback.}
\end{lareference}
\subsection{Index bookkeeping}
\begin{lareference}
\lafunc{int myflatposition(int group, int index) const; int myflatposition(const INDEX &i) const}{position of an index in the flat index list.}
\lafunc{INDEX myindexposition(int flatindex) const}{inverse of \code{myflatposition}.}
\lafunc{int flatposition(int group, int index, const NRVec<INDEXGROUP> &shape); int flatposition(const INDEX &i, const NRVec<INDEXGROUP> &shape); INDEX indexposition(int flatindex, const NRVec<INDEXGROUP> &shape)}{free versions for a given shape.}
\lafunc{LA_largeindex subindex(int *sign, const INDEXGROUP &g, const NRVec<LA_index> &I)}{position of the index tuple \code{I} inside the storage of one group, with sign as for \code{index()}.}
\lafunc{NRVec<LA_index> inverse_subindex(const INDEXGROUP &g, LA_largeindex s)}{canonical index tuple of position \code{s} in a group.}
\lafunc{FLATINDEX superindex2flat(const SUPERINDEX &I)}{concatenate the groups of a superindex.}
\lafunc{bool zero_in_index(const FLATINDEX &); bool zero_in_index(const SUPERINDEX &); bool zero_in_index(const INDEXMATRIX &, const LA_largeindex row)}{does any index equal 0 (for index ranges with a negative offset where 0 is excluded)?}
\lafunc{bool shares_index(const FLATINDEX &I, const FLATINDEX &J)}{do \code{I} and \code{J} have a common index value?}
\end{lareference}
\section{Arithmetic}\label{sec:tensor:arithmetic}
Binary operations require identical shapes (\code{laerror} otherwise) and, if both tensors are named,
identical names.
\begin{lareference}
\lafunc{Tensor& operator*=(const T &a); Tensor operator*(const T &a) const; Tensor& operator/=(const T &a); Tensor operator/(const T &a) const}{scaling.}
\lafunc{Tensor& operator+=(const Tensor &rhs); Tensor& operator-=(const Tensor &rhs); Tensor operator+(const Tensor &rhs) const; Tensor operator-(const Tensor &rhs) const}{elementwise sum and difference of the stored data.}
\lafunc{Tensor& axpy(const T alpha, const Tensor &rhs)}{\code{*this += alpha*rhs}.}
\lafunc{Tensor operator-() const}{negation; the result has no index names.}
\lafunc{Tensor operator*(const Tensor &rhs) const}{outer product; the indices of \code{rhs} become the less significant (leftmost) ones: \code{(a*b)(J,I)=a(I)*b(J)}. Names are concatenated, so either both or none of the factors must be named.}
\lafunc{T dot(const Tensor &rhs) const}{full contraction $\sum_I \overline{a_I} b_I$ (complex conjugation of \code{*this}); antisymmetric groups are accounted for by the factor $m!$, symmetric and (anti)hermitean groups are not supported (\code{laerror}).}
\lafunc{typename LA_traits<T>::normtype norm() const}{Frobenius norm of the \emph{stored} data (redundant elements are not counted).}
\lafunc{Tensor& conjugateme(); Tensor conjugate() const}{complex conjugation.}
\lafunc{void clear()}{set all elements to zero.}
\lafunc{void randomize(const typename LA_traits<T>::normtype &x)}{random elements, followed by \code{enforce_hermiticity()}.}
\lafunc{void enforce_hermiticity()}{zero the imaginary (hermitean) or real (antihermitean) part of elements with repeated indices in (anti)hermitean groups.}
\lafunc{bool fulfills_hermiticity() const}{check the above condition.}
\end{lareference}
\section{Contractions}\label{sec:tensor:contractions}
Contractions are performed by unwinding the contracted indices of both tensors to the leftmost positions
(expanding them to their full range, \code{unwind_index()}), followed by one matrix multiplication
(\code{cblas_dgemm}/\code{cblas_zgemm}).
The index order of the result is: the remaining indices of the second tensor (\code{rhs}, \code{rhs2}) as
the less significant ones, followed by the remaining indices of the first one (\code{*this} or \code{rhs1});
each remaining group keeps its symmetry (a group from which an index was contracted keeps its symmetry
with one index less). If both tensors are named, the names of the contracted indices must agree and the
result is named accordingly.
In the \code{add...} versions the result is accumulated into \code{*this}:
$\mathtt{*this} \leftarrow \beta\,\mathtt{*this} + \alpha\,\mathrm{contraction}$; with \code{doresize=true}
\code{*this} is first resized to the result shape (this requires \code{beta==0}), otherwise its shape (and
names, if named) must match. \code{conjugate1} conjugates the first tensor, \code{conjugate}/\code{conjugate2}
the second one.
\begin{lareference}
\lafunc{void addcontraction(const Tensor &rhs1, int group, int index, const Tensor &rhs2, int rhsgroup, int rhsindex, T alpha=1, T beta=1, bool doresize=false, bool conjugate1=false, bool conjugate=false)}{contract one index of \code{rhs1} with one index of \code{rhs2}.}
\lafunc{void addcontraction(const Tensor &rhs1, const INDEX &I1, const Tensor &rhs2, const INDEX &I2, ...); void addcontraction(const Tensor &rhs1, const Tensor &rhs2, const INDEXNAME &iname, ...)}{the same with \code{INDEX} positions or with the common index name (same trailing arguments).}
\lafunc{Tensor contraction(int group, int index, const Tensor &rhs, int rhsgroup, int rhsindex, T alpha=1, bool conjugate1=false, bool conjugate=false) const; Tensor contraction(const INDEX &I, const Tensor &rhs, const INDEX &RHSI, ...) const; Tensor contraction(const Tensor &rhs, const INDEXNAME &iname, ...) const}{return $\alpha\,$\code{*this}$\cdot$\code{rhs} contracted over one index.}
\lafunc{void addcontractions(const Tensor &rhs1, const INDEXLIST &il1, const Tensor &rhs2, const INDEXLIST &il2, T alpha=1, T beta=1, bool doresize=false, bool conjugate1=false, bool conjugate2=false); void addcontractions(const Tensor &rhs1, const Tensor &rhs2, const NRVec<INDEXNAME> &names, ...)}{contract the indices \code{il1[i]} with \code{il2[i]} simultaneously in a single matrix multiplication. Finding an optimal contraction order is the responsibility of the user (contracting one index at a time may be cheaper). For empty lists the outer product is added.}
\lafunc{Tensor contractions(const INDEXLIST &il1, const Tensor &rhs2, const INDEXLIST &il2, T alpha=1, bool conjugate1=false, bool conjugate2=false) const; Tensor contractions(const Tensor &rhs2, const NRVec<INDEXNAME> &names, ...) const}{returning versions.}
\lafunc{void addgroupcontraction(const Tensor &rhs1, int group, const Tensor &rhs2, int rhsgroup, T alpha=1, T beta=1, bool doresize=false, bool conjugate1=false, bool conjugate=false); Tensor groupcontraction(int group, const Tensor &rhs, int rhsgroup, T alpha=1, bool conjugate1=false, bool conjugate=false) const}{contract over all indices of a whole group without unwinding them; the groups must have equal shape and be non-symmetric or antisymmetric (for antisymmetric groups the factor $m!$ is included, so the result equals the contraction of the flattened tensors).}
\lafunc{Tensor innercontraction(const INDEXLIST &il1, const INDEXLIST &il2) const; Tensor innercontraction(const INDEX &i1, const INDEX &i2) const; Tensor innercontraction(const NRVec<INDEXNAME> &nl1, const NRVec<INDEXNAME> &nl2) const; Tensor innercontraction(const INDEXNAME &n1, const INDEXNAME &n2) const}{trace(s) inside one tensor: indices \code{il1[i]} and \code{il2[i]} are set equal and summed over; the result is a scalar tensor if no index remains.}
\lafunc{void add_permuted_contractions(const char *antisymmetrizer, const Tensor &rhs1, const Tensor &rhs2, T alpha=1, T beta=1, bool conjugate1=false, bool conjugate2=false)}{for uniquely named tensors: contract all indices whose names occur in both \code{rhs1} and \code{rhs2}, apply the antisymmetrizer given as a string, permute the result to the index order of \code{*this} and accumulate. The string uses the notation of Kucharski and Bartlett: space-separated factors, each a list of index names where \code{/} separates classes and \code{,} separates names within a class; e.g.\ \code{"k/l,m"} is $P(k/lm)=1-P_{kl}-P_{km}$. Indices not mentioned are not permuted; \code{""} means just contraction and reordering. See \code{general_antisymmetrizer} in chapter~\ref{ch:permutation}.}
\end{lareference}
\section{Restructuring}\label{sec:tensor:restructuring}
Permutations are \code{NRPerm<int>} (chapter~\ref{ch:permutation}) and act on group numbers $1\dots n$.
Names are permuted together with the indices unless stated otherwise.
\begin{lareference}
\lafunc{Tensor permute_index_groups(const NRPerm<int> &p) const}{reorder the data permuting whole index groups: group $i$ of the result is group $p_i$ of the source (both counted from 1).}
\lafunc{Tensor permute_index_groups(const NRVec<INDEXNAME> &names) const}{the same with the permutation given by the requested order of the groups' first index names (for a flat tensor: of all names).}
\lafunc{Tensor unwind_index_group(int group) const}{move a group to the leftmost (least significant) position, the others keep their order.}
\lafunc{Tensor unwind_index(int group, int index) const; Tensor unwind_index(const INDEX &I) const; Tensor unwind_index(const INDEXNAME &N) const}{take one index out of its group and make it a separate leftmost non-symmetric group, expanded to its full range; the rest of the group keeps its symmetry. Without data reorganization if the index is already first in a non-symmetric group 0.}
\lafunc{Tensor unwind_indices(const INDEXLIST &il) const; Tensor unwind_indices(const NRVec<INDEXNAME> &names) const}{the same for several indices, which become groups $0,1,\dots$ in the listed order.}
\lafunc{Tensor flatten(int group= -1) const}{expand the given group (all groups for $-1$) into single non-symmetric indices, keeping the flat index order and names.}
\lafunc{Tensor merge_indices(const INDEXLIST &il, int symmetry=0) const; Tensor merge_indices(const NRVec<INDEXNAME> &nl, int symmetry=0) const}{opposite of \code{flatten}: the listed indices (equal range and offset) form a new leftmost group of the given symmetry; for a nonzero symmetry the data are (anti)symmetrized, i.e.\ averaged over the permutations with the appropriate signs.}
\lafunc{Tensor merge_index_groups(const NRVec<int> &groups) const}{permute the listed non-symmetric groups to the front (in the given order, the remaining groups follow in their original order) and merge them into one group; group numbers from 0, repeated numbers raise an error.}
\lafunc{void split_index_group(int group)}{in place, formal (no data movement): split a non-symmetric group into single-index groups.}
\lafunc{void split_index_group1(int group)}{in place, formal: split off the first index of a non-symmetric group.}
\lafunc{void merge_adjacent_index_groups(int groupfrom, int groupto)}{in place, formal: merge consecutive non-symmetric groups of equal range and offset into one.}
\lafunc{void permute_inside_group(int group, const NRPerm<int> &p, bool inverse=false)}{for a group with nonzero symmetry: apply the sign/conjugation of the permutation parity to the data and permute the corresponding names.}
\lafunc{Tensor subtensor1(int i) const}{the slice for the value \code{i} of the last (most significant) index, which must form a group of its own.}
\end{lareference}
\section{Permutation algebra, decompositions and transformations}\label{sec:tensor:other}
\begin{lareference}
\lafunc{void apply_permutation_algebra(const Tensor &rhs, const PermutationAlgebra<int,T> &pa, bool inverse=false, T alpha=1, T beta=0, bool conjugate_by_parity=false)}{$\mathtt{*this} \leftarrow \beta\,\mathtt{*this}$, then for all stored elements $I$ of \code{*this}: $\mathtt{this}(I) \mathrel{+}= \alpha \sum_P c_P\, \mathtt{rhs}(P(I))$, with $P$ acting on the flat index list (inverse permutations if \code{inverse}); with \code{conjugate_by_parity} odd permutations take the complex conjugate. Can represent general (anti)symmetrizers, e.g.\ \code{general_antisymmetrizer} (chapter~\ref{ch:permutation}). \code{*this} may have a symmetry which \code{rhs} lacks. The names of \code{*this} are taken from (or checked against) the names of \code{rhs} permuted by the first permutation. Element-by-element, not optimally efficient.}
\lafunc{void apply_permutation_algebra(const NRVec<Tensor> &rhsvec, const PermutationAlgebra<int,T> &pa, bool inverse=false, T alpha=1, T beta=0)}{the same with \code{rhs} being the outer product of the tensors in \code{rhsvec}, which is not formed explicitly (e.g.\ Grassmann products building a density matrix from cumulants); rather inefficient.}
\lafunc{NRVec<NRMat<T> > Tucker(typename LA_traits<T>::normtype thr=1e-12, bool inverseorder=false)}{HOSVD/Tucker decomposition: \code{*this} is replaced by the flat core tensor and the factor matrices (one per index, columns orthonormal) are returned; singular values below \code{thr} are dropped, which reduces the corresponding range of the core.}
\lafunc{Tensor inverseTucker(const NRVec<NRMat<T> > &x, bool inverseorder=false) const}{rebuild the tensor from the core (\code{*this}) and the matrices returned by \code{Tucker} with the same \code{inverseorder}.}
\lafunc{Tensor linear_transform(const NRVec<NRMat<T> > &x) const}{transform every index of group $g$ by the matrix \code{x[g]} (\code{x[g].ncols()} must equal the range; the new range is \code{x[g].nrows()}), preserving the group symmetries: $t'_{\dots a \dots} = \sum_i x_{ai}\, t_{\dots i\dots}$.}
\lafunc{Tensor linear_transform(const int g, const Tensor<T> &x) const; Tensor linear_transform(const int g, const NRMat<T> &x) const}{transform only group \code{g}, keeping its position and symmetry; \code{x} must be a flat rank-2 tensor (useful for raising/lowering indices) or a matrix.}
\end{lareference}
For the \code{Tensor} argument the contraction runs over its index 0, i.e.\ $t'_{\dots a\dots}=\sum_i x(i,a)\,t_{\dots i\dots}$ in tensor
indexing, which is the same as the matrix convention above for \code{x} built by \code{Tensor<T>(matrix,true)}.
\section{Names and shape queries}\label{sec:tensor:names}
\begin{lareference}
\lafunc{bool is_named() const}{are names set? (\code{laerror} if their number differs from the rank).}
\lafunc{bool is_uniquely_named() const}{named without repetitions.}
\lafunc{void defaultnames(const char *basename="i")}{set names \code{i000}, \code{i001}, \dots}
\lafunc{int findflatindex(const INDEXNAME nam) const}{flat position of the named index, $-1$ if not found (\code{laerror} if unnamed).}
\lafunc{INDEX findindex(const INDEXNAME nam) const; NRVec<INDEX> findindexlist(const NRVec<INDEXNAME> &names) const}{position(s) of named indices; \code{laerror} if not found.}
\lafunc{void renameindex(const INDEXNAME namfrom, const INDEXNAME nameto)}{rename an index (see the copy-on-write remark above).}
\lafunc{int rank() const; LA_largeindex size() const}{number of indices; number of stored elements.}
\lafunc{int calcrank(); LA_largeindex calcsize()}{recompute \code{myrank}, resp.\ \code{groupsizes}, \code{cumsizes} from \code{shape} and return the rank, resp.\ size.}
\lafunc{void canonicalize_shape()}{set symmetry 0 for single-index groups; check the symmetry codes.}
\lafunc{bool is_flat() const; bool is_compressed() const; bool has_symmetry() const; bool has_hermiticity() const}{all groups have one index; some multi-index group has nonzero symmetry; some group has nonzero symmetry; complex \code{T} and some group is (anti)hermitean.}
\end{lareference}
\section{Input and output}\label{sec:tensor:io}
\begin{lareference}
\lafunc{void put(int fd, bool with_names=false) const; void get(int fd, bool with_names=false)}{binary I/O of shape, auxiliary arrays, data and optionally names (section~\ref{sec:concepts:io}); with \code{with_names} the names are always written (an empty list if the tensor has none), so \code{put} and \code{get} must use the same \code{with_names}.}
\lafunc{template<typename T> std::ostream & operator<<(std::ostream &s, const Tensor<T> &x)}{text output: the shape, the names, then one line per stored element with its flat indices and value.}
\lafunc{template<typename T> std::istream & operator>>(std::istream &s, Tensor<T> &x)}{reads the format written by \code{<<}.}
\lafunc{std::ostream & operator<<(std::ostream &s, const INDEXGROUP &x); std::istream & operator>>(std::istream &s, INDEXGROUP &x)}{\code{number symmetry offset range}.}
\lafunc{std::ostream & operator<<(std::ostream &s, const INDEXNAME &x); std::istream & operator>>(std::istream &s, INDEXNAME &x)}{the name as a word.}
\lafunc{std::ostream & operator<<(std::ostream &s, const INDEX &x); std::istream & operator>>(std::istream &s, INDEX &x)}{\code{group index}.}
\end{lareference}
\section{Helpers in the header}
\begin{lareference}
\lafunc{template<typename T> T signeddata(const int sgn, const T &data)}{apply a sign code ($\pm1$, $\pm2$ = with conjugation, 0 gives 0) to a value.}
\lafunc{template<typename T> class Signedpointer}{proxy returned by \code{lhs()}, see section~\ref{sec:tensor:access}.}
\end{lareference}
\section{Examples}\label{sec:tensor:examples}
An antisymmetric rank-3 tensor:
\begin{lstlisting}
int n=4;
INDEXGROUP g;
g.number=3; g.symmetry= -1; g.offset=0; g.range=n;
Tensor<double> t(g); // stores n(n-1)(n-2)/6 elements
t.clear();
t.lhs(0,1,2) = 1.; // implies t(1,0,2)=-1 etc.
t.lhs(2,1,3) = 5.; // stored as t(1,2,3) = -5
const Tensor<double> &tc=t;
cout << tc(1,2,3) << " " << tc(3,2,1) << " " << tc(1,1,2) << endl; // -5 5 0
Tensor<double> tf = t.flatten(); // the same tensor stored in full, n^3 elements
\end{lstlisting}
Contractions of named tensors:
\begin{lstlisting}
INDEXGROUP s; s.number=1; s.symmetry=0; s.offset=0; s.range=n;
NRVec<INDEXGROUP> shape(4); for(int i=0; i<4; ++i) shape[i]=s;
Tensor<double> x(shape), y(shape);
x.randomize(1.); y.randomize(1.);
INDEXNAME xn[4]={"i","j","k","l"}; x.names=xn;
INDEXNAME yn[4]={"m","n","j","i"}; y.names=yn;
INDEXNAME cn[2]={"i","j"};
// z(m,n,k,l) = sum_ij x(i,j,k,l) y(m,n,j,i); remaining indices of y come first
Tensor<double> z = x.contractions(y,NRVec<INDEXNAME>(cn));
// result in a prescribed index order, antisymmetrized in k,l:
// w(k,l,m,n) = z(m,n,k,l) - z(m,n,l,k)
Tensor<double> w(shape);
INDEXNAME wn[4]={"k","l","m","n"}; w.names=wn;
w.add_permuted_contractions("k/l",x,y,1.,0.);
\end{lstlisting}
Tucker decomposition:
\begin{lstlisting}
NRVec<INDEXGROUP> sh(3);
for(int i=0; i<3; ++i) {sh[i].number=1; sh[i].symmetry=0; sh[i].offset=0; sh[i].range=n+i;}
Tensor<double> a(sh); a.randomize(1.);
Tensor<double> a0(a); a0.copyonwrite(); // keep the original
NRVec<NRMat<double> > u = a.Tucker(1e-12); // a now holds the core tensor
Tensor<double> b = a.inverseTucker(u); // b == a0
Tensor<double> c = a.linear_transform(u); // the same
\end{lstlisting}
Conversion from a dense four-index object (chapter~\ref{ch:fourindex}):
\begin{lstlisting}
fourindex_dense<nosymmetry,double,int> f(n);
f.randomize(1.);
Tensor<double> t4 = fourindex2tensor(f); // f(i,j,k,l) == t4(k,l,i,j), indices from 1
fourindex_dense<nosymmetry,double,int> ff;
tensor2fourindex(t4,ff);
\end{lstlisting}
\section{Pitfalls}\label{sec:tensor:pitfalls}
\begin{itemize}
\item The leftmost index is the fastest; a tensor built from a matrix is its transpose in index notation:
\code{Tensor<T>(x)(i,j)==x(j,i)}.
\item \code{norm()}, \code{operator+=} etc.\ act on the stored data only; the norm of a compressed tensor
differs from the norm of its flattened form.
\item Results of contractions have the remaining indices of the \emph{second} tensor first.
\item \code{lhs()} writes require an unshared tensor: call \code{copyonwrite()} after copying.
There is no \code{operator|=}.
\item Default-constructed \code{INDEXGROUP}s are uninitialized; set all members.
\item Operations based on \code{loopover()} use static global variables to pass data to the callbacks;
they are neither reentrant nor thread safe.
\item Index names longer than 7 characters are not NUL-terminated.
\item An outer product of a named and an unnamed tensor raises an error (inconsistent number of names).
\end{itemize}
+346
View File
@@ -0,0 +1,346 @@
\chapter{Utilities}\label{ch:utilities}
This chapter documents the auxiliary parts of \LA{} which are not linear algebra classes: a scratch-file storage of
fixed-size records used by the out-of-core variants of the iterative solvers, the random number generators used by all
\code{randomize()} functions, a few special functions and combinatorial numbers, small self-contained algorithms for
embedded use, the reference of the error handling functions, the single include file \header{la.h} and the version
string, elementary number theory and graph algorithms. The small classes Vec3/Mat3, Quaternion and bitvector are described in
chapters~\ref{ch:vecmat3}, \ref{ch:quaternion} and \ref{ch:bitvector}.
%=====================================================================
\section{Scratch-file storage: AuxStorage}\label{sec:utilities:auxstorage}
\laheader{auxstorage.h}
\subsection{Purpose and storage}
\code{AuxStorage<T>} stores numbered records of equal length in a binary file, using unformatted \code{read}/\code{write}
of the raw element array. It is used by \code{davidson}, \code{lanczos}, \code{gmres} and \code{DIIS}
(chapter~\ref{ch:iterative}) to keep the subspace vectors on disk when the in-core option is not selected, and to write
eigenvectors to a named file. The record length (in bytes) is fixed by the first \code{put()}; all later records must have
the same size. Record \code{pos} starts at byte offset \code{pos*recl}, so records may be written in any order; reading
a never written record lying before the end of the file gives zeros (a hole of the file), reading past the end of the file
calls \code{laerror}.
The class itself holds only a file descriptor and the file name; it is \emph{not} reference counted. The objects stored are
ordinary \LA{} objects: \code{get()} calls \code{x.copyonwrite()} before reading into \code{x}, \code{put()} only reads
its argument.
The comment in the header gives the limits of the design:
\begin{itemize}
\item \code{T} must be plain data (\code{double}, \code{std::complex<double>}, \dots); it will not work if \code{T} is a
class with dynamically allocated components (e.g.\ \code{NRVec<NRVec<double> >} or \code{NRVec<NRMat<double> >}),
since only the bytes of the top-level array are written;
\item it cannot be implemented for \code{SparseMat}, which lacks a fixed record length;
\item for more complex I/O the \code{put()} and \code{get()} methods of the individual classes are to be used
(section~\ref{sec:concepts:io}).
\end{itemize}
\subsection{Reference}
\begin{lareference}
\lafunc{template<typename T> class AuxStorage}{Scratch file of records whose elements are of type \code{T}.}
\lafunc{AuxStorage(void)}{Creates and opens a new unique temporary file \header{AUX_XXXXXX} in \code{\$TMPDIR} if it is set,
otherwise in the current working directory, by \code{mkostemp()} (atomic creation, with \code{O_LARGEFILE}), and unlinks it at
once: the open descriptor keeps it usable, and its disk space is released when it is closed, also after a crash.
\code{laerror} if the file cannot be created.}
\lafunc{AuxStorage(const char *name)}{Opens the file \code{name} for reading and writing; an existing file of this name is
first deleted (\code{unlink}), so the file always starts empty. The file is \emph{not} deleted by the destructor (used to keep
eigenvectors). \code{laerror} if it cannot be opened or if the name has 32 or more characters.}
\lafunc{~AuxStorage(void)}{Closes the file; deletes it if it was created by the default constructor.}
\lafunc{AuxStorage(const AuxStorage &) = delete; AuxStorage &operator=(const AuxStorage &) = delete}{Not copyable (a copy would close
the file twice); use pointers, as the iterative solvers do.}
\lafunc{void put(const NRVec<T> &x, const int pos)}{Writes the elements of \code{x} as record \code{pos} (counted from 0).
The first \code{put} defines the record length; \code{laerror} if a later object has a different size in bytes.}
\lafunc{void get(NRVec<T> &x, const int pos) const}{Reads record \code{pos} into \code{x}, which must be already allocated
with the right size. Calls \code{x.copyonwrite()}. \code{laerror} if nothing has been written yet, if the size of \code{x} in
bytes differs from the record length, or if the record cannot be read in full.}
\lafunc{void put(const NRMat<T> &x, const int pos); void get(NRMat<T> &x, const int pos) const}{The same for a dense
matrix (record of \code{nrows()*ncols()} elements in the storage order of \code{NRMat}).}
\lafunc{void put(const NRSMat<T> &x, const int pos); void get(NRSMat<T> &x, const int pos) const}{The same for a packed
symmetric matrix (record of $n(n+1)/2$ elements).}
\end{lareference}
Only the byte size is compared, so a record written as a vector can be read into a matrix of the same number of elements.
\subsection{Example}
\begin{lstlisting}
AuxStorage<double> st; //temporary file AUX_xxxxxx in $TMPDIR or the working directory
NRVec<double> v(1.,100), w(100);
st.put(v,3); //record 3, record length 100 doubles
st.get(w,3); //w must be allocated with 100 elements
\end{lstlisting}
\subsection{Pitfalls}
\begin{itemize}
\item The target of \code{get()} must be preallocated with exactly the record size (otherwise \code{laerror}).
\item The temporary file is placed in \code{\$TMPDIR}, or in the current working directory if \code{TMPDIR} is not set; it is
not visible in the directory listing (unlinked), but occupies disk space there while the object exists.
\item The file position is shared: \code{get()} and \code{put()} use \code{lseek64} followed by \code{read}/\code{write}, so
one object must not be used concurrently from several threads.
\item \code{lseek64}, \code{off64_t}, \code{O_LARGEFILE} and \code{mkostemp} are Linux (glibc) specific.
\item A short write is not detected (only a negative return value of \code{write} is checked).
\end{itemize}
%=====================================================================
\section{Random numbers}\label{sec:utilities:random}
\laheader{la_random.h, la_random.cc}
All random-number consumers of the library (\code{randomize()} of \code{NRVec}, \code{NRMat}, \code{NRSMat}, \code{Vec3},
\code{Mat3}, \code{Quaternion}, \code{bitvector}, random permutations, \dots) obtain random numbers through four macros,
so that the generator can be replaced without changing the library. \header{la_random.h} is included by
\header{la_traits.h}, i.e.\ by every \LA{} header.
\begin{lareference}
\lafunc{double randdouble()}{Uniform in $[0,1)$: \code{random()/(1.+RAND_MAX)}.}
\lafunc{double randdoublesigned()}{Uniform in $[-1,1)$: \code{2.*random()/(1.+RAND_MAX)-1.}.}
\lafunc{unsigned int randint32()}{32 random bits from two calls of \code{random()} (which gives 31 bits with glibc).}
\lafunc{uint64_t randint64()}{64 random bits from three calls of \code{random()} ($31+31+2$ bits).}
\lafunc{RANDDOUBLE, RANDDOUBLESIGNED, RANDINT32, RANDINT64}{Macros used at all call sites; unless defined before the
inclusion of \header{la_random.h} they expand to \code{LA::randdouble}, \code{LA::randdoublesigned}, \code{LA::randint32},
\code{LA::randint64}.}
\lafunc{WEAK_SYMBOL}{Expands to \code{__attribute__((weak))} with GNU compilers, empty otherwise.}
\end{lareference}
\subsection{Replacing the generator}
The four functions are defined in \header{la_random.cc} as \emph{weak symbols}. A program can replace any of them by its own
(strong) definition with the same signature in namespace \code{LA}; the linker then uses the user's function also in the
calls made from inside the precompiled library, and the remaining default functions stay available:
\begin{lstlisting}
#include "la.h"
namespace LA {
double randdoublesigned() {return my_generator.uniform(-1.,1.);} //replaces the library's weak definition
}
\end{lstlisting}
Defining one of the macros (e.g.\ \code{-DRANDDOUBLE=myrand}) affects only code compiled by the user (inline and
template code in the headers), not the calls in the compiled library; the weak-symbol replacement is therefore the
mechanism to use for a consistent change.
\subsection{Seeding}
The default functions use the C library generator \code{random()}, so they are seeded by \code{srandom(seed)}; with glibc
\code{srand()} seeds the same generator (as used in \header{testoptimize.cc} and \header{t.cc}). Without seeding the sequence
is the same in every run. A seed from the system can be taken e.g.\ from \header{/dev/random}, as in \header{t.cc}:
\begin{lstlisting}
int seed;
int f=open("/dev/random",O_RDONLY);
if(sizeof(int)!=read(f,&seed,sizeof(int))) laerror("cannot read /dev/random");
close(f);
srand(seed);
\end{lstlisting}
The default generators are not thread safe in the sense of reproducibility (they share the global state of
\code{random()}); a thread-safe or higher-quality generator is to be supplied by replacement.
%=====================================================================
\section{Special functions and combinatorial numbers}\label{sec:utilities:miscfunc}
\laheader{miscfunc.h, miscfunc.cc}
Plain functions in namespace \code{LA} for \code{double} and 64-bit unsigned integers. They keep internal static tables
(filled on demand), so they are not thread safe on the first calls.
\begin{lareference}
\lafunc{const double c_euler}{Euler--Mascheroni constant $\gamma=0.5772156649\dots$}
\lafunc{unsigned long long factorial(const int n)}{$n!$ from a table, $0\le n\le 20$; \code{laerror} for $n<0$ and for
$n>20$ (overflow of 64 bits). Exact; used where integer arithmetic is needed (permutation ranks and counts, \code{binom}).}
\lafunc{double double_factorial(const int n)}{$n!$ (not the double factorial $n!!$) in \code{double} precision,
$0\le n\le 170$: \code{factorial(n)} for $n\le20$, beyond from a table extended on demand. \code{laerror} for $n<0$ and
$n>170$. Used by \code{bernoulli_number}, \code{zeta} and \code{gamma}.}
\lafunc{double factorialln(const int n)}{$\ln n!$ for any $n\ge0$: tabulated up to $n=30$, accumulated (and cached) up to
$n=180$, Stirling series with Bernoulli-number corrections beyond. \code{laerror} for $n<0$.}
\lafunc{unsigned long long binom(int n, int k)}{Binomial coefficient $\binom nk$; 0 for $k<0$ or $k>n$. With $k$
replaced by $\min(k,n-k)$, computed exactly by the recurrence $\binom{n-k+i}{i}=\binom{n-k+i-1}{i-1}(n-k+i)/i$, dividing
by the common factor before each multiplication, so that no intermediate exceeds the result; cached for $n\le100$.
\code{laerror} if the result does not fit into 64 bits (all $\binom nk$ fit for $n\le67$).}
\lafunc{unsigned long long longpow(unsigned long long x, int i)}{$x^i$ by binary powering (modulo $2^{64}$, overflow not
detected); returns 0 for $i<0$.}
\lafunc{double zeta(double x)}{Riemann zeta function for real $x\ne1$. Integer arguments: exact values from Bernoulli
numbers for $x\le0$ and $x=2,4,6,8$; otherwise (also for other integers): the defining sum for $x\ge9.1$, the
Euler--Maclaurin summation formula with $N=10$ and ten Bernoulli terms for $-1.02\le x<9.1$ (including the neighbourhood of
the pole, whose term $1/(x-1)$ is exact), and the reflection formula $\zeta(x)=2(2\pi)^{x-1}\sin(\pi x/2)\Gamma(1-x)\zeta(1-x)$
for $x<-1.02$. Relative accuracy about $10^{-13}$ or better (except close to the trivial zeros). \code{laerror} for $x=1$.}
\lafunc{double gamma(double x)}{$\Gamma(x)$ for $x\le171$: $(x-1)!$ by \code{double_factorial} for integers, otherwise
upward recursion to $x\ge8$ and the Stirling series. \code{laerror} for $x>171$ and for non-positive integers.}
\lafunc{double gammln(double x)}{$\ln|\Gamma(x)|$, for integers via \code{factorialln}; \code{laerror} for non-positive
integers.}
\lafunc{double bernoulli_number(int n)}{Bernoulli number $B_n$ with $B_1=-\frac12$, $B_{2k+1}=0$ for $k\ge1$; $B_{2k}$ from
a table up to $B_{10}$, otherwise from $\zeta(2k)$ by $B_{2k}=(-1)^{k+1}\,2\,(2k)!\,\zeta(2k)/(2\pi)^{2k}$ (cached).
\code{laerror} for $n<0$.}
\lafunc{unsigned long long simplicial(int d, int n)}{$d$-dimensional simplicial (figurate) number
$s(d,n)=\binom{n+d-1}{d}$, i.e.\ the number of non-decreasing $d$-tuples of indices from $n$ values (used for the indexing of
symmetric tensors, chapter~\ref{ch:tensor}). $s(d,n)=0$ for $n\le0$, $1$ for $d=0$ or $n=1$, $n$ for $d=1$. Computed by the
recurrence $s(d,n)=s(d,n-1)+s(d-1,n)$ into a table growing on demand; \code{laerror} for $d\ge16$ or $n\ge1024$.}
\lafunc{int inverse_simplicial(int d, unsigned long long s)}{Largest $n$ with \code{simplicial(d,n)}${}\le s$ (Newton
iteration on the polynomial $s(d,n)$ followed by an integer correction); 0 for $s=0$. \code{laerror} for $d\ge16$.}
\end{lareference}
%=====================================================================
\section{Simple algorithms for embedded use}\label{sec:utilities:simple}
\laheader{simple.h, simple.cc}
\header{simple.h} contains small algorithms on C arrays of compile-time size, independent of BLAS, LAPACK and of the
rest of \LA{}. It is intended for embedded computers and can be compiled separately; it is installed with the library but
\emph{not} included by \header{la.h}. Everything is in namespace \code{LA_Simple}. With \code{AVOID_STDSTREAM} defined,
\header{iostream} is not included and the stream operator is omitted. \header{simple.cc} is currently empty. The element
type \code{T} is a real floating-point type (\code{abs} and comparisons are used for pivoting).
\begin{lareference}
\lafunc{template<typename T, int n, int m> T simple_gaussj(T (&a)[n][n], T (&b)[m][n])}{Gauss--Jordan elimination with
full pivoting. On input \code{a} is the $n\times n$ matrix $A$ and \code{b} contains $m$ right-hand sides as its
\emph{rows} \code{b[l][0..n-1]}. On return \code{a} contains $A^{-1}$, the rows of \code{b} the solutions, and the
function returns $\det A$. For a singular matrix it returns 0 and \code{a}, \code{b} are left partially transformed.
\code{T (&a)[n][n]} binds e.g.\ to \code{Mat3<double>::elements()}.}
\lafunc{template<typename T, int n> class simple_linfit}{Linear least-squares fit $y\approx\sum_{i=0}^{n-1}c_if_i(x)$
by accumulating the normal equations; the data points are not stored. Public members: \code{T fitmat[n][n]},
\code{T rhsmat[1][n]} (accumulated $\sum f_if_j$, $\sum f_iy$), \code{T fitcoef[n]} (result $c_i$), \code{int npoints}.}
\lafunc{simple_linfit()}{Empty fit, coefficients zero.}
\lafunc{void clear(bool keepresults=false)}{Discard the accumulated data (and the coefficients unless \code{keepresults}).}
\lafunc{void input(const T (&funcs)[n], const T y)}{Add a data point given by the values $f_i(x)$ and $y$.}
\lafunc{T solve(const T preserve=0)}{Solves the normal equations by \code{simple_gaussj}, stores the coefficients in
\code{fitcoef} and returns the determinant of the normal matrix (0 if singular). If fewer than $n$ points have been input it
returns 0 and leaves \code{fitcoef} unchanged. With \code{preserve==0} the accumulated data are cleared afterwards (the next
fit starts from scratch); with \code{preserve!=0} they are kept and, if \code{preserve!=1}, multiplied by \code{preserve},
which gives an exponentially decaying weight of old points in a running fit (\code{npoints} is not reset then).}
\lafunc{const T &operator[](int i) const}{Coefficient $c_i$.}
\lafunc{std::ostream& operator<<(std::ostream &o, const simple_linfit<T,n> &f)}{Prints the coefficients separated by spaces.}
\end{lareference}
Example (from \header{t.cc}), fit of a parabola:
\begin{lstlisting}
simple_linfit<double,3> fit;
double funcs[3];
for(int i=0; i<100; ++i)
{
double x = 10*RANDDOUBLESIGNED();
funcs[0]=1; funcs[1]=x; funcs[2]=x*x;
fit.input(funcs, 2*x*x-3*x+1);
}
double det = fit.solve();
cout <<"det= "<<det<<" fit "<<fit<<endl; //1 -3 2
\end{lstlisting}
\lawarning{\header{simple.h} defines and afterwards undefines a macro \texttt{SWAP}; a user macro of this name defined before
the inclusion is lost.}
%=====================================================================
\section{Error handling reference}\label{sec:utilities:errors}
\laheader{laerror.h, laerror.cc}
The concept (errors are reported by \code{laerror}, which throws an exception after printing diagnostics; checks enabled by
\code{DEBUG}) is described in section~\ref{sec:concepts:errors}. All entities are in namespace \code{LA}.
\begin{lareference}
\lafunc{laerror(const char *message)}{Macro expanding to \code{LA::laerror2(message, __PRETTY_FUNCTION__)} with GNU
compilers and \code{LA::laerror2(message, __func__)} otherwise. Usable in user code, also outside namespace \code{LA}.}
\lafunc{void laerror2(const char *message, const char *function)}{Flushes and prints a newline on \code{std::cout} and
\code{std::cerr}, then on both streams \code{LA:ERROR - message} and \code{Called from function} (``unspecified'' if
\code{message} is \code{NULL}); the system error message by \code{perror} if \code{errno} is nonzero; the command line (from
\header{/proc/self/cmdline}); user, host name and working directory (via \code{system()}); a listing of the working
directory if \code{LA_error_list_files} is true. Finally it throws \code{LAerror(message)}.}
\lafunc{class LAerror : public std::exception}{Exception thrown by \code{laerror2}. Public member \code{std::string msg}, a copy
of the message (so it stays valid also for messages built in local buffers, e.g.\ by the BLAS error handlers); constructor
\code{LAerror(const char *s)} (\code{NULL} gives ``unspecified''); \code{what()} returns the message, so an uncaught error or a
handler catching \code{std::exception} shows it.}
\lafunc{std::ostream & operator<<(std::ostream &s, const LAerror &x)}{Prints \code{x.msg}.}
\lafunc{bool LA_error_list_files}{Default \code{false}; if set, \code{laerror2} also runs \code{ls -l -t .}.}
\lafunc{bool _LA_count_check}{Default \code{true}. In \code{DEBUG} builds the non-const \code{operator[]} and
\code{operator()} of \code{NRVec}, \code{NRMat} and \code{NRSMat} call \code{laerror} if the reference count is larger
than 1 (section~\ref{sec:concepts:refcount}) only while this variable is true; setting it to false switches this check off
(the check is in inline header code, so it affects code compiled with \code{DEBUG}).}
\lafunc{const bool _LA_warn_empty_copyonwrite}{Compile-time \code{false} in the library; if it were true,
\code{copyonwrite()} of an empty object would print a warning. Not user-adjustable.}
\lafunc{int traceback(int flags)}{Prints a stack trace of the running process by forking \header{/usr/bin/gdb} in batch
mode attached to the process (allowed by \code{prctl(PR_SET_PTRACER)}); output goes to \code{stderr}. \code{flags} is
currently unused. Linux specific; requires gdb. C linkage.}
\lafunc{void sigtraceback(int sig, int flags)}{Installs a one-shot handler for signal \code{sig} which flushes
\code{stdout}, calls \code{traceback(flags)} and re-raises the signal (default action, e.g.\ core dump); if the handler is
entered again it calls \code{_exit(1)}. C linkage.}
\lafunc{int endianity()}{Index of the byte holding the least significant byte of an \code{int}: 0 on little-endian machines,
nonzero (\code{sizeof(int)-1}) on big-endian ones. Used e.g.\ by \code{bitvector} to refuse big-endian platforms.}
\end{lareference}
Errors detected by BLAS and LAPACK (\code{xerbla_}), ATLAS (\code{ATL_xerbla}) and CBLAS (\code{cblas_xerbla},
\code{cblas_errprn}) are redirected to \code{laerror} by replacement handlers defined in \header{laerror.cc}. The CBLAS ones
are omitted when the library is configured without CBLAS (\code{NONCBLAS}), \code{cblas_xerbla} also when
\code{AVOID_DUPLICATE_CBLAS_XERBLA} is defined (for CBLAS implementations providing their own).
Typical use at the start of \code{main()} (\header{t.cc}):
\begin{lstlisting}
sigtraceback(SIGSEGV,1);
sigtraceback(SIGABRT,1);
sigtraceback(SIGBUS,1);
sigtraceback(SIGFPE,1);
\end{lstlisting}
%=====================================================================
\section{The single include file la.h}\label{sec:utilities:la}
\laheader{la.h}
\header{la.h} is meant as the single include file for the end user. It includes the headers of all classes and modules
(traits, error handling, \code{AuxStorage}, vectors, dense, symmetric and sparse matrices, the non-class functions,
iterative solvers, \code{DIIS}, matrix exponential, four-index transformation, permutations, polynomials, continued
fractions, tensors, optimization, numbers, bitvector, Vec3/Mat3, quaternions, \header{miscfunc.h} and the version
declaration), and \header{la_random.h} indirectly. It does \emph{not} include \header{simple.h}, \header{graph.h},
\header{reg.h} and \header{regsurf.h}, which have to be included explicitly when needed.
\header{la.h} puts \code{using namespace LA;} into the global namespace and defines the abbreviations
\begin{lstlisting}
typedef NRMat<int> NRIMat; typedef NRVec<int> NRIVec;
typedef NRMat<double> NRDMat; typedef NRVec<double> NRDVec;
typedef NRMat<std::complex<double> > NRCMat;
typedef NRVec<std::complex<double> > NRCVec;
\end{lstlisting}
Code which wants to avoid the global \code{using} directive can include the individual headers instead.
%=====================================================================
\section{Version information}\label{sec:utilities:version}
The library contains the string \code{extern const char LA::version[]} (declared in \header{LA_version.h}, included by
\header{la.h}). Its definition \header{version.cc} is regenerated at every \code{make} by the script
\header{get_git_version} and contains the output of \code{git describe}, the HEAD commit hash and date, the compilation date,
host and user name. A program can print it to record which library build produced its results:
\begin{lstlisting}
cout <<"LA version = "<<LA::version<<endl;
\end{lstlisting}
%=====================================================================
\section{Number-theoretic utilities}\label{sec:utilities:numbers}
\laheader{numbers.h, numbers.cc}
Elementary number theory with naive algorithms, suitable for small numbers (trial division).
All functions are templates in the integer type \code{N} but are instantiated only for
\code{uint64_t} (the inline ones \code{isprime}, \code{isoddprime}, \code{eulerphi(const N&)}
therefore also link only for \code{uint64_t}).
\begin{lareference}
\lafunc{N primefactor(const N &x, const N &last=0)}{Smallest prime factor of $x$ which is $\ge$\code{last} (assuming $x$ has no smaller odd factors); returns $x$ for $x\le2$ and for primes.}
\lafunc{bool isprime(const N &x); bool isoddprime(const N &x)}{$x$ prime; $x$ odd prime.}
\lafunc{class FACTORIZATION<N> : public std::list<std::pair<N,N>>}{List of (prime, exponent) pairs in increasing order.}
\lafunc{FACTORIZATION<N> factorization(const N &x)}{Prime factorization (empty for $x\le1$).}
\lafunc{N nextprime(N x)}{Smallest prime $>x$ (2 for $x<2$).}
\lafunc{std::ostream &operator<<(std::ostream &s, const FACTORIZATION<N> &x)}{Prints \code{(p,e) (p,e) ...}.}
\lafunc{N eulerphi(const FACTORIZATION<N> &f); N eulerphi(const N &x)}{Euler's totient function.}
\lafunc{N pow(const N &x, N i)}{$x^i$ by repeated squaring (overflow not checked).}
\lafunc{N multmod(N x, N y, const N &m); N powmod(const N &x, N i, const N &m)}{$xy \bmod m$ by shift-and-add (avoids overflow of $xy$ up to about $m<2^{63}$), $x^i\bmod m$.}
\end{lareference}
Example: \code{uint64_t n=360; cout << factorization(n) << eulerphi(n);} prints
\code{(2,3) (3,2) (5,1) 96}.
%=====================================================================
\section{Graph algorithms}\label{sec:utilities:graph}
\laheader{graph.h, graph.cc}
A small collection of graph algorithms. There is no graph class: the functions are templates in
the adjacency matrix type \code{G}, which must provide \code{nrows()}, an element access
\code{operator()(i,j)} convertible to \code{bool}, a copy constructor/assignment and
\code{submatrix(const NRVec<int>&)} returning a \code{G}. They are instantiated only for
\code{G = NRSMat<char>} (undirected graphs, chapter~\ref{ch:smat}). Vertices are numbered from 0;
diagonal elements are ignored.
\begin{lareference}
\lafunc{NRVec<int> neighbors(const G &adjacency, const int v); NRVec<int> nonneighbors(const G &adjacency, const int v)}{Sorted lists of vertices adjacent / not adjacent to $v$ (excluding $v$).}
\lafunc{NRVec<int> findclique(const G &adjacency, const int v=0)}{A large clique found by the recursive Ramsey heuristic of Boppana and Halld\'orsson \cite{utilities:boppana1992} started at vertex $v$; returns the sorted vertex list (empty for an empty graph). Exponential worst case in recursion; intended for small graphs.}
\lafunc{NRVec<int> complement(const G &adjacency, const NRVec<int> &vertexlist)}{Vertices not contained in \code{vertexlist}.}
\lafunc{NRVec<int> cliquecover(const G &adjacency, int *ncliques=NULL)}{Greedy clique cover by repeated \code{findclique} on the remaining subgraph; returns for each vertex the number of its clique (counted from 1) and optionally the number of cliques.}
\end{lareference}
\begin{lstlisting}
NRSMat<char> adj;
cin >> adj;
NRVec<int> clique = findclique(adj);
NRVec<int> cover = cliquecover(adj);
NRPerm<int> p(cover.size());
cover.sort(0,p,true); //permutation grouping the cliques
NRSMat<char> adjperm = adj.permuted(p); //block structure
\end{lstlisting}
+393
View File
@@ -0,0 +1,393 @@
\chapter{Vectors: NRVec}\label{ch:vec}
\laheader{vec.h, vec.cc}
\section{Purpose and main idea}\label{sec:vec:purpose}
\code{NRVec<T>} is the basic dense vector class template of \LA{}: a contiguous array of \code{nn} elements of type \code{T},
indexed from zero, with reference-counted storage (copy on demand). It is used directly as the vector type of linear algebra
(BLAS-1 and BLAS-2 operations for \code{double} and \code{std::complex<double>}), as a general resizable container of arbitrary
elements (including nested \LA{} objects such as \code{NRVec<NRVec<int> >}), and as the storage backbone of other classes
(\code{NRMat<T>} and \code{NRSMat<T>} can be viewed as vectors without copying, see Sec.~\ref{sec:vec:construct}).
\code{NRVec_from1<T>} (Sec.~\ref{sec:vec:from1}) is a thin derived class with indexing from one.
The class is explicitly instantiated in \texttt{vec.cc} for the element types \code{double}, \code{char}, \code{short}, \code{int},
\code{long}, \code{long long}, their \code{unsigned} variants, and for \code{std::complex<double>}. Members defined in
\texttt{vec.h} can be used with any other element type (user classes, nested \LA{} types, \code{float}, \dots); members defined
only in \texttt{vec.cc} (\code{NRVec(const NRMat&)}, \code{fprintf}, \code{fscanf}, unary \code{operator-}, \code{operator<},
\code{operator>}, \code{find}, \code{findthr}, \code{permuteme(const CyclePerm<int>&)}, \code{conjugateme}, \code{negateme},
\code{negateconjugateme}, \code{sum}) are available only for the instantiated types. Numerical members based on BLAS are intended
for floating-point element types only (real and complex), not for integers; at present they exist for \code{double} and
\code{std::complex<double>} (see the remarks in the reference below).
\section{Storage and class-specific concepts}\label{sec:vec:storage}
The object holds the size \code{nn}, a data pointer \code{v} and a pointer \code{count} to the reference counter
(plus a location tag in GPU builds, see Sec.~\ref{sec:concepts:gpu}). A default-constructed vector has \code{nn==0}, \code{v==0},
\code{count==0}.
\code{NRVec} follows the copy-on-demand scheme of Sec.~\ref{sec:concepts:refcount}: copy construction and \code{operator=} share
the data, \code{operator|=} makes a deep copy (Sec.~\ref{sec:concepts:assign}), member functions that modify the data call
\code{copyonwrite()} themselves, \code{resize()} detaches shared data, and an explicit \code{copyonwrite()} is needed only before
writing through \code{operator[]} or a raw pointer. In DEBUG builds the non-const \code{operator[]} raises \code{laerror} if the
reference count is larger than one and \code{_LA_count_check} is set. For element types that are not plain data
(\code{LA_traits<T>::is_plaindata()} false, e.g.\ nested vectors), \code{copyonwrite()} recursively calls \code{copyonwrite()}
on the elements, see Sec.~\ref{sec:concepts:traits}.
The element-level member \code{swap(int,int)} does not call \code{copyonwrite()} by design, like \code{operator[]}: call
\code{copyonwrite()} once before using it on a vector that may share its data (Sec.~\ref{sec:concepts:refcount}).
Elements are stored contiguously, so \code{(T*)x} or \code{&x[0]} can be passed to C/Fortran routines expecting an array.
The nested type \code{ROWTYPE} is \code{T}.
\section{Construction, assignment, conversion}\label{sec:vec:construct}
\begin{lareference}
\lafunc{NRVec()}{Empty vector (size 0, nothing allocated).}
\lafunc{explicit NRVec(const int n, const GPUID loc = undefined)}{Vector of \code{n} elements, \emph{not initialized} (for
plain data). \code{loc} is the memory location in GPU builds, ignored otherwise.}
\lafunc{NRVec(const T &a, const int n, const GPUID loc = undefined)}{Vector of \code{n} elements all equal to \code{a}. Note the
order of arguments: value first, size second.}
\lafunc{template<int SIZE> NRVec(const T (&a)[SIZE])}{Copy of a C array; also accepts a braced list, e.g.\
\code{NRVec<double> x({1.,2.,3.})}.}
\lafunc{NRVec(const T *a, const int n)}{Copy of \code{n} elements from memory at \code{a}.}
\lafunc{NRVec(T *a, const int n, bool skeleton)}{With \code{skeleton==false} same as the previous one. With \code{skeleton==true}
the vector \emph{wraps} the external memory \code{a} without copying; the reference count is set to 2 so that the memory is never
freed by \LA{}. Consequently any modifying member function makes a private copy first (the external memory is \emph{not}
modified), and DEBUG-mode \code{operator[]} for writing raises an error. Use it for read-only views of foreign data.}
\lafunc{NRVec(const Vec3<T> &rhs)}{Copy of the three components of a \code{Vec3} (from \texttt{vecmat3.h}, see
Chapter~\ref{ch:vecmat3}).}
\lafunc{NRVec(const NRVec &rhs)}{Copy constructor, shares data.}
\lafunc{explicit NRVec(const std::list<T> l)}{Copy of the elements of a \code{std::list} (the list is passed by value).}
\lafunc{NRVec(const typename LA_traits_complex<T>::NRVec_Noncomplex_type &rhs, bool imagpart=false)}{Complex vector from a real
one: \code{rhs} becomes the real part (or the imaginary part if \code{imagpart}), the other part is zero. Implemented only for
\code{NRVec<std::complex<double> >} from \code{NRVec<double>}.}
\lafunc{explicit NRVec(const NRSMat<T> &S)}{View of the packed storage of a symmetric matrix of order $n$ as a vector of
$n(n+1)/2$ elements. \emph{Shares} the data with \code{S} (no copy).}
\lafunc{explicit NRVec(const NRMat<T> &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 SparseMat<T> &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<double>}.}
\lafunc{~NRVec()}{Decrements the reference count, frees the data when it reaches zero.}
\lafunc{NRVec& operator=(const NRVec &rhs)}{Shallow assignment (shares data), see Sec.~\ref{sec:concepts:assign}.}
\lafunc{NRVec& operator|=(const NRVec &rhs)}{Deep copy; \code{rhs} must be allocated (checked in DEBUG).}
\lafunc{NRVec& operator=(const T &a)}{Sets all elements to \code{a} (keeps the size); calls copyonwrite.}
\lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{If the data are shared, allocate a private copy.
\code{detachonly=true} allocates private storage without copying the contents (they are undefined afterwards).
With \code{deep} and non-plain-data \code{T}, elements are made unique recursively even if this object is not shared.
Calling it on an empty vector is harmless (prints a warning if \code{_LA_warn_empty_copyonwrite} is set).}
\lafunc{int getcount() const}{Current reference count (0 for an empty vector).}
\lafunc{void resize(const int n, const bool preserve=false, const bool preserve_at_end=false)}{Change size; detaches from shared data.
Without \code{preserve} the new contents are undefined (no-op if the size does not change and the data are not shared).
With \code{preserve} the first $\min(n_\text{old},n)$ elements are kept and new elements are zeroed (by \code{memset});
with \code{preserve_at_end} the last elements are kept and aligned to the end, the new ones being at the beginning.
\code{resize(0)} deallocates.}
\lafunc{void dealloc(void)}{Same as \code{resize(0)}.}
\lafunc{void clear()}{Set all elements to zero (\code{LA_traits<T>::clear}); detaches shared plain data.}
\lafunc{void swap(NRVec &rhs)}{Exchange contents (size, data, counter) of two vectors in $O(1)$.}
\lafunc{template<typename U> NRVec<std::complex<U> > complexify(const NRVec<U> &rhs)}{(free function) Complex vector with
real part \code{rhs}. CPU only (NOT\_GPU) in the generic version; specialized for \code{double} via BLAS.}
\lafunc{template<typename T, typename S> void NRVec_convert(NRVec<T> &a, const NRVec<S> &b)}{(free function) Resize \code{a} and
set \code{a[i] = (T)b[i]}.}
\end{lareference}
\section{Element access}\label{sec:vec:access}
\begin{lareference}
\lafunc{T & operator[](const int i); const T & operator[](const int i) const}{Element $i$, $0\le i<n$. DEBUG builds check the
range, allocation, and (non-const version) the reference count, see Sec.~\ref{sec:vec:storage}.}
\lafunc{operator T*(); operator const T*() const}{Pointer to the data (DEBUG: error if unallocated). Call \code{copyonwrite()}
before writing through the pointer.}
\lafunc{int size() const}{Number of elements.}
\lafunc{const NRVec subvector(const int from, const int to) const}{Copy of elements \code{from}\dots\code{to} (inclusive). If this is
the whole vector, a shallow copy is returned. Plain data only.}
\lafunc{const NRVec subvector(const NRVec<int> &selection) const}{Gather into a new vector: \code{r[i] = x[selection[i]]}.
Index range is always checked.}
\lafunc{void subvector_from(const NRVec<int> &selection, const NRVec<T> &rhs)}{Gather into \code{*this}:
\code{x[i] = rhs[selection[i]]}; \code{x.size()} must equal \code{selection.size()} (otherwise \code{laerror}); calls copyonwrite.}
\lafunc{void storesubvector(const int from, const NRVec &rhs)}{Copy \code{rhs} into positions \code{from}\dots\code{from+rhs.size()-1};
plain data only; calls copyonwrite.}
\lafunc{void storesubvector(const NRVec<int> &selection, const NRVec &rhs)}{Scatter into \code{*this}:
\code{x[selection[i]] = rhs[i]}; sizes of \code{selection} and \code{rhs} must agree; calls copyonwrite.}
\lafunc{void storesubvector_to(const NRVec<int> &selection, NRVec &rhs) const}{Scatter \code{*this} into \code{rhs}:
\code{rhs[selection[i]] = x[i]}; \code{x.size()} must equal \code{selection.size()}; calls \code{rhs.copyonwrite()}.}
\lafunc{NRVec reverse() const}{Copy with reversed order of elements.}
\lafunc{NRVec concat(const NRVec &rhs) const; NRVec concatscaled(const NRVec &rhs, const T alpha=1) const}{New vector
$(x, \mathtt{rhs})$, resp.\ $(x, \alpha\,\mathtt{rhs})$. If one of the vectors is empty, the other one is returned (shared; for
\code{concatscaled} with an empty \code{*this} and $\alpha\neq1$ a scaled copy of \code{rhs}).}
\lafunc{void concatme(const NRVec &rhs); void concatmescaled(const NRVec &rhs, const T alpha=1)}{Append \code{rhs}
(resp.\ $\alpha\,\mathtt{rhs}$) to \code{*this} in place (via \code{resize} with preservation).}
\lafunc{void append(const T &a); void prepend(const T &a)}{Add one element at the end / beginning. Each call reallocates;
inefficient when repeated.}
\lafunc{const int find(const T &val) const}{Index of the first element equal to \code{val}, $-1$ if none.}
\lafunc{const int findthr(const T &val, const typename LA_traits<T>::normtype &thr=0) const}{Index of the first element with
$|x_i-\mathtt{val}|<\mathtt{thr}$, $-1$ if none (note the strict inequality: the default \code{thr=0} never matches).}
\end{lareference}
\section{Arithmetic and BLAS-backed operations}\label{sec:vec:arith}
All compound operators call \code{copyonwrite()}. Dimensions of vector operands are checked only in DEBUG builds.
For \code{double} and \code{std::complex<double>} the operations marked BLAS are specialized to call CBLAS; the generic versions are
element-wise loops (CPU only).
\begin{lareference}
\lafunc{NRVec& operator+=(const NRVec &rhs); NRVec& operator-=(const NRVec &rhs)}{Element-wise sum/difference (BLAS axpy).}
\lafunc{NRVec& operator*=(const NRVec &rhs); NRVec& operator/=(const NRVec &rhs)}{Element-wise (Hadamard) product/quotient.}
\lafunc{const NRVec operator+(const NRVec &rhs) const; const NRVec operator-(const NRVec &rhs) const}{Sum/difference. There are
no binary element-wise \code{*} and \code{/} of two vectors; \code{x*y} is the scalar product.}
\lafunc{NRVec& operator+=(const T &a); NRVec& operator-=(const T &a)}{Add/subtract \code{a} to/from every element (BLAS).}
\lafunc{NRVec& operator*=(const T &a); NRVec& operator/=(const T &a)}{Scale every element (BLAS scal; division as
multiplication by $1/a$ for the specialized types).}
\lafunc{const NRVec operator+(const T &a) const; ... operator-, operator*, operator/}{\code{x+a}, \code{x-a}, \code{x*a},
\code{x/a} element-wise with a scalar.}
\lafunc{const NRVec operator+(const T &a, const NRVec &x); const NRVec operator*(const T &a, const NRVec &x)}{(free functions) Scalar on the
left: $a+x$ and $ax$. The noncommutative \code{a-x} and \code{a/x} are deliberately not available (declared \code{= delete},
a compile error); write \code{-x+a} explicitly.}
\lafunc{const NRVec operator-() const}{Negated copy.}
\lafunc{NRVec& negateme()}{$x\leftarrow -x$ in place.}
\lafunc{NRVec& conjugateme(); NRVec conjugate() const}{Complex conjugation in place / conjugated copy (no-op for real types).}
\lafunc{NRVec& negateconjugateme()}{$x\leftarrow -x^*$ in place (specialized for \code{std::complex<double>} and
\code{std::complex<float>}).}
\lafunc{const T operator*(const NRVec &rhs) const}{Scalar product $\sum_i x_i^*\, y_i$ (conjugating \code{*this} for complex types;
BLAS dot/dotc).}
\lafunc{const T dot(const T *a, const int stride = 1, bool conjugate=true) const}{$\sum_i \tilde x_i\, a_{i\cdot\mathrm{stride}}$,
where $\tilde x_i=x_i^*$ if \code{conjugate} and \code{T} is complex, else $x_i$; \code{a} must have sufficient length.}
\lafunc{const T dot(const NRVec &rhs, bool conjugate=true) const}{Same with a vector (no dimension check).}
\lafunc{void axpy(const T alpha, const NRVec &x)}{$\mathtt{this} \leftarrow \mathtt{this} + \alpha\,\mathtt{x}$ (BLAS axpy).
Only \code{double} and \code{std::complex<double>}.}
\lafunc{void axpy(const T alpha, const T *x, const int stride=1)}{Same with strided raw data; CPU only.
Only \code{double} and \code{std::complex<double>}.}
\lafunc{void gemv(const T beta, const NRMat<T> &a, const char trans, const T alpha, const NRVec &x)}{$y\leftarrow\alpha\,\mathrm{op}(A)x+\beta y$
with $y=$ \code{*this}; \code{trans} is \code{'n'} ($A$), \code{'t'} ($A\Tr$) or for complex \code{'c'} ($A\Hc$), case insensitive.
\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<T> &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 SparseMat<T> &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.}
\lafunc{void gemv(const Component_type beta, const NRMat_Noncomplex_type &a, const char trans, const Component_type alpha, const NRVec &x);
void gemv(const Component_type beta, const NRSMat_Noncomplex_type &a, const char trans, const Component_type alpha, const NRVec &x)}{(Types
from \code{LA_traits_complex<T>}.) Complex vector times \emph{real} dense or packed-symmetric matrix with real coefficients;
real and imaginary parts are processed separately.}
\lafunc{const NRVec operator*(const NRMat<T> &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<T> &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<T> &mat) const}{Row vector times sparse matrix; CPU only.}
\lafunc{const NRMat<T> otimes(const NRVec<T> &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<T> operator|(const NRVec<T> &rhs) const}{Same as \code{otimes(rhs,true)}, i.e.\ $x y\Hc$.}
\lafunc{const NRVec otimes2vec(const NRVec<T> &rhs, const bool conjugate = false, const T &scale = 1) const}{Outer product stored
as a vector of length $n_x n_y$ (row-major, index $i n_y + j$).}
\end{lareference}
The real-matrix \code{gemv}, \code{normalize}, \code{otimes} and \code{otimes2vec} exist only for \code{double} and
\code{std::complex<double>}; for the instantiated integer types (and complex integer types) they are defined to raise
\code{laerror}.
\section{Norms, reductions, extrema}\label{sec:vec:norms}
\begin{lareference}
\lafunc{const typename LA_traits<T>::normtype norm(int length= -1, int offset=0, int stride=1) const}{Euclidean norm
(BLAS nrm2) of \code{length} elements (default all) starting at \code{offset} with \code{stride}.
Only \code{double} and \code{std::complex<double>}.}
\lafunc{NRVec& normalize(typename LA_traits<T>::normtype* norm = 0)}{Divide by the Euclidean norm, optionally storing the norm in
\code{*norm}; DEBUG: error for a zero vector. Only \code{double} and \code{std::complex<double>}.}
\lafunc{const NRVec unitvector() const}{Normalized copy.}
\lafunc{const typename LA_traits<T>::normtype asum() const}{$\sum_i |x_i|$ (BLAS asum; for complex
$\sum_i |\mathrm{Re}\,x_i|+|\mathrm{Im}\,x_i|$). Only \code{double} and \code{std::complex<double>}.}
\lafunc{inline int iamax() const}{Zero-based index of the element of largest absolute value (BLAS i?amax; for complex the 1-norm
$|\mathrm{Re}|+|\mathrm{Im}|$). Only \code{double} and \code{std::complex<double>}. \code{laerror} for an empty vector.}
\lafunc{inline const T amax() const; inline const T amin() const}{The element (with sign) of largest / smallest absolute value
(complex: 1-norm). Only \code{double} and \code{std::complex<double>}. \code{laerror} for an empty vector.}
\lafunc{const T max() const; const T min() const}{Largest / smallest element (0 for an empty vector). Only real types
(instantiated for the integer types, \code{float}, \code{double}).}
\lafunc{const T sum() const}{$\sum_i x_i$.}
\lafunc{const T sumsqr() const}{$\sum_i x_i^2$, 0 for an empty vector. No conjugation for complex types; use \code{norm()} for $\sqrt{\sum_i|x_i|^2}$.}
\lafunc{const T prod() const}{$\prod_i x_i$, 1 for an empty vector.}
\lafunc{NRVec<typename LA_traits<T>::normtype> abs() const}{Vector of $|x_i|$.}
\lafunc{NRVec<typename LA_traits<T>::normtype> diffabs(const NRVec &rhs) const}{Vector of $|x_i|-|y_i|$.}
\end{lareference}
\section{Other member functions}\label{sec:vec:other}
\subsection*{Comparison}
\begin{lareference}
\lafunc{const bool operator==(const NRVec &rhs) const; const bool operator!=(const NRVec &rhs) const}{Equal size and elements.
For plain data the comparison is bytewise (\code{memcmp}), so e.g.\ \code{0.0} and \code{-0.0} differ.}
\lafunc{const bool operator<(const NRVec &rhs) const; ... operator>, operator<=, operator>=}{Lexicographic order using
\code{LA_traits<T>::smaller/bigger}; a proper prefix is smaller. Raises \code{laerror} for complex elements.}
\end{lareference}
\subsection*{Sorting and permutations}
\begin{lareference}
\lafunc{int sort(int direction = 0, int from = 0, int to = -1, int *perm = NULL, bool stable=false)}{Quicksort of elements
\code{from}\dots\code{to} (\code{-1}: last) in place, ascending for \code{direction==0}, descending otherwise; returns the
parity (0/1) of the applied permutation. If \code{perm} is given, \code{perm[from..to]} (initialized by the caller) is permuted
along with the elements. \code{stable} selects a stable variant. Calls copyonwrite.}
\lafunc{int sort(int direction, NRPerm<int> &perm, bool stable=false)}{Sort the whole vector; \code{perm} (of the same size)
is set to the identity and returned with \code{perm[i]} = original (1-based) position of the element now at position $i$ (1-based).}
\lafunc{void printsorted(std::ostream &s, int direction=1, bool absvalue=false, bool stable=false, typename LA_traits<T>::normtype thr=0, int topmax=0, const NRMat<int> *indexmap=NULL) const}{Print
lines ``\emph{index value}'' in sorted order (default descending) without modifying the vector. \code{absvalue}: sort by $|x_i|$;
\code{thr}: skip elements with $|x_i|<\mathrm{thr}$; \code{topmax}: print at most this many lines (0 = all); \code{indexmap}:
print \code{(*indexmap)[i][0]} and \code{(*indexmap)[i][1]} instead of the index $i$.}
\lafunc{bool bigger(int i, int j) const; bool smaller(int i, int j) const; void swap(int i, int j)}{Element comparison and
exchange used by the sorting templates (\texttt{qsort.h}). \code{swap(i,j)} is element-level access and, like \code{operator[]}, does not call
copyonwrite: call it before, outside the loop (Sec.~\ref{sec:vec:storage}).}
\lafunc{const NRVec permuted(const NRPerm<int> &p, const bool inverse=false) const}{Permuted copy: \code{r[p[i]-1] = x[i-1]}, or
with \code{inverse} \code{r[i-1] = x[p[i]-1]} ($p$ is 1-based, see Chapter~\ref{ch:permutation}).}
\lafunc{void permuteme(const NRPerm<int> &p, const bool inverse=false); void permuteme(const CyclePerm<int> &p)}{The same in place;
for a cycle $(c_1 \dots c_L)$ the element at $c_{k}$ moves to $c_{k+1}$ and the one at $c_L$ to $c_1$.}
\end{lareference}
\subsection*{Element-wise function application}
\begin{lareference}
\lafunc{NRVec& call_on_me(T (*_F)(const T &))}{$x_i\leftarrow F(x_i)$ for a plain function pointer. This non-template overload
is kept on purpose: when the name of an \emph{overloaded} function is passed, the fixed parameter type selects the right overload
(the template below cannot deduce its argument from an overload set). Note that the function must take \code{const T &}, so e.g.\
\code{std::sqrt} does not match it.}
\lafunc{template<typename F> NRVec& call_on_me(F _F)}{$x_i\leftarrow F(x_i)$ for any callable (functor, lambda, function with
another compatible signature). For a single non-overloaded function both overloads match and the non-template one is chosen.}
\lafunc{template<typename F> NRVec& call_on_me(F _F, const NRVec &rhs)}{Binary version $x_i\leftarrow F(x_i, \mathtt{rhs}_i)$; sizes
checked in DEBUG.}
\end{lareference}
All three are CPU only and call copyonwrite.
\subsection*{Random numbers}
\begin{lareference}
\lafunc{void randomize(const typename LA_traits<T>::normtype &x)}{Fill with uniform random numbers from $[-x,x)$ (real and
imaginary parts independently for complex), using \code{RANDDOUBLESIGNED} (Chapter~\ref{ch:utilities}). Only \code{double} and
\code{std::complex<double>}.}
\end{lareference}
\subsection*{Input/output}
See Sec.~\ref{sec:concepts:io} for the general conventions.
\begin{lareference}
\lafunc{void put(int fd, bool dimensions=1, bool transp=0, bool orca_format=false) const}{Binary output to file descriptor.
With \code{dimensions}: header \code{int n}, then \code{int 1} (padding), or with \code{orca_format} \code{int sizeof(T)};
followed by the elements (\code{LA_traits<T>::multiput}, recursive for nested types). \code{transp} is unused.}
\lafunc{void get(int fd, bool dimensions=1, bool transp=0, bool orca_format=false)}{Binary input matching \code{put}; with
\code{dimensions} the vector is resized, otherwise the current size is used (and copyonwrite called). \code{orca_format} checks
the element size.}
\lafunc{void fprintf(FILE *f, const char *format, const int modulo) const}{Formatted output via \code{lawritemat} as a
$1\times n$ matrix (Chapter~\ref{ch:nonclass}).}
\lafunc{void fscanf(FILE *f, const char *format)}{Read the dimension and then the elements with the given \code{scanf} format
(e.g.\ \texttt{"\%lf"} for \code{double}); resizes.}
\end{lareference}
\subsection*{Miscellaneous}
\begin{lareference}
\lafunc{GPUID getlocation() const; void moveto(const GPUID dest)}{Memory location (always \code{cpu} without \code{CUDALA}, then
\code{moveto} is a no-op); see Sec.~\ref{sec:concepts:gpu}.}
\lafunc{void setcoldim(int i); void simplify()}{Dummies for interface compatibility with other classes.}
\end{lareference}
\section{Related free functions}\label{sec:vec:free}
\begin{lareference}
\lafunc{std::ostream & operator<<(std::ostream &s, const NRVec<T> &x)}{Text output: the size on one line, then the elements
separated by spaces on the next line (converted to \code{LA_traits_io<T>::IOtype}, so e.g.\ \code{char} is printed as a number).}
\lafunc{std::istream & operator>>(std::istream &s, NRVec<T> &x)}{Reads the format written by \code{<<}; resizes \code{x}.}
\lafunc{template<typename T> void NRVec_simplify(NRVec<T> &x, const typename LA_traits<T>::normtype thr=0, bool alwayskeepfirst=false)}{For
vectors of ``coefficient $\times$ element'' terms (\code{T} must provide sorting traits, \code{operator==} comparing only the element,
and \code{LA_traits<T>::coefficient}, \code{abscoefficient}, \code{elementtype}): sort, merge equal elements by adding coefficients,
drop terms with zero coefficient or $|\text{coefficient}|<\mathrm{thr}$ (the first term is kept if \code{alwayskeepfirst}), and
shrink the vector. Public API; within the library it is presently used by \code{PermutationAlgebra::simplify()}.}
\lafunc{template<typename T> typename LA_traits<T>::normtype MYABS(const T &x)}{\code{std::abs} with pass-through specializations
for unsigned integer types; auxiliary.}
\end{lareference}
\code{complexify} and \code{NRVec_convert} are listed in Sec.~\ref{sec:vec:construct}. The header also defines constants
\code{CONE}, \code{CMONE}, \code{CZERO} (complex $1,-1,0$ for CBLAS calls) and the internal macros \code{NRVECMAT_OPER},
\code{NRVECMAT_OPER_NONCOMMUTATIVE}, \code{NRVECMAT_OPER2} generating the binary operators from the compound ones.
\section{Vectors indexed from one: NRVec\_from1}\label{sec:vec:from1}
\code{NRVec_from1<T>} is derived publicly from \code{NRVec<T>} and differs only in indexing: \code{operator[]} takes
$1\le i\le n$ (same DEBUG checks as \code{NRVec}), and \code{iamax()} returns a 1-based index. It is used e.g.\ for permutations
(Chapter~\ref{ch:permutation}). Everything else is inherited unchanged and \emph{remains zero-based} (e.g.\ \code{subvector},
\code{find}, \code{sort(direction,from,to,...)}, the raw pointer).
\begin{lareference}
\lafunc{NRVec_from1(); explicit NRVec_from1(const int n); NRVec_from1(const T &a, const int n); NRVec_from1(const T *a, const int n)}{As
for \code{NRVec}.}
\lafunc{template<int SIZE> NRVec_from1(const T (&a)[SIZE]); explicit NRVec_from1(const std::list<T> l)}{As for \code{NRVec}.}
\lafunc{NRVec_from1(const NRVec<T> &rhs)}{Implicit conversion from the base class (shares data).}
\lafunc{const T& operator[](const int i) const; T& operator[](const int i)}{Element $i$, 1-based.}
\lafunc{int iamax() const}{1-based index of the element of largest absolute value.}
\end{lareference}
\lawarning{\code{NRVec_from1} does not redeclare \code{operator=(const T&)}; the implicitly declared copy assignment hides it, so
\code{f = 0.;} does not compile for \code{NRVec_from1<double> f}. Use the \code{(value, size)} constructor or assign through a base
class reference.}
\section{Examples}\label{sec:vec:examples}
\begin{lstlisting}
#include "la.h"
using namespace LA;
double sq(const double &a) {return a*a;}
float sq(const float &a) {return a*a;}
int main()
{
NRVec<double> x({1.,2.,3.});
NRVec<double> y(x); // shares data with x
y *= 2.; // y gets its own copy, x unchanged
double d = x*y; // scalar product = 28
NRVec<double> z = x + y*0.5;
z.axpy(-1.,x); // z = x
std::cout << d << " " << z.norm() << std::endl;
NRVec<double> w(0.,5); // five zeros
NRVec<int> sel({4,0,2});
w.storesubvector(sel,x); // scatter: w[4]=x[0], w[0]=x[1], w[2]=x[2]
NRVec<double> g(3);
g.subvector_from(sel,w); // gather: g[i]=w[sel[i]], i.e. g == x
std::cout << w << g;
x.call_on_me(sq); // overloaded name: non-template overload
x.call_on_me([](const double &a){return a+1.;}); // lambda: template overload
x.call_on_me([](const double &a, const double &b){return a>b?a:b;}, y); // binary
x.copyonwrite(); // needed before writing via operator[]
x[0] = -1.;
NRVec_from1<double> f(1.,3);
f[3] = 5.; // last element
std::cout << f << f.iamax() << std::endl; // prints 3
NRVec<std::complex<double> > c(x,true); // purely imaginary
std::cout << c << complexify(x);
}
\end{lstlisting}
Sorting with the permutation returned:
\begin{lstlisting}
NRVec<double> v(12);
v.randomize(1.);
NRPerm<int> p(v.size());
NRVec<double> s(v);
s.sort(0,p); // ascending; s copied before sorting, v unchanged
// s[i-1] == v[p[i]-1] for i=1..n
v.printsorted(std::cout,1,true); // descending by absolute value
\end{lstlisting}
\section{Pitfalls}\label{sec:vec:pitfalls}
\begin{itemize}
\item \code{NRVec<double> x(n)} is not initialized; use \code{NRVec<double> x(0.,n)} or \code{x.clear()}.
The argument order of the filling constructor is \emph{(value, size)}.
\item After \code{y=x} the data are shared: call \code{y.copyonwrite()} before \code{y[i]=...}. In DEBUG builds reading an element
of a shared non-const vector via \code{operator[]} also triggers the error; read through a const reference.
\item \code{NRVec(const NRMat&)} and \code{NRVec(const NRSMat&)} share the data with the matrix (unless \code{MATPTR}); writing into
the vector after \code{copyonwrite()} does not affect the matrix, but the matrix and the vector are not independent before that.
\item A skeleton vector \code{NRVec(ptr,n,true)} never writes into the foreign memory through modifying member functions; they
silently make a copy.
\item \code{x*y} is the (conjugated) scalar product, not an element-wise product; use \code{x*=y} for that.
\item \code{a-x} and \code{a/x} with a scalar \code{a} on the left do not compile (deleted); write \code{-x+a}.
\item \code{resize()} without \code{preserve} leaves the contents undefined.
\item \code{norm}, \code{normalize}, \code{asum}, \code{amax}, \code{amin}, \code{iamax}, \code{axpy}, \code{randomize},
\code{gemv}, \code{otimes} are intended for floating-point types only and are presently available for \code{double} and
\code{std::complex<double>} (link error or \code{laerror} otherwise, by design for integer types); \code{max}/\code{min} only for real types.
\item \code{NRVec_from1} indexing is 1-based only in \code{operator[]} and \code{iamax()}.
\end{itemize}
+120
View File
@@ -0,0 +1,120 @@
\chapter{3D vectors and matrices: Vec3, Mat3}\label{ch:vecmat3}
\laheader{vecmat3.h, vecmat3.cc}
Fixed-size 3D vectors and matrices; plain data, no reference counting (unlike the classes of chapter~\ref{ch:vec} and \ref{ch:mat}).
\section{Purpose}
\code{Vec3<T>} and \code{Mat3<T>} are small fixed-size classes for real 3-dimensional
vectors and $3\times3$ matrices, intended for geometry, rotations and Euler angles. They are
compatible with the quaternion functions of chapter~\ref{ch:quaternion}
(rotation matrices, SO(3) parametrization). They contain only a C array and all operations
are hand-unrolled; no BLAS/LAPACK is used.
The files are written so that they can be compiled separately from the rest of \LA{}
(e.g.\ for microcontrollers). The following macros control this:
\begin{itemize}
\item \code{AVOID_STDSTREAM} -- do not include \code{<iostream>}, no stream operators;
\item \code{NO_NUMERIC_LIMITS} -- do not use \code{std::numeric_limits}; \code{DBL_EPSILON}
is then defined as the single-precision epsilon;
\item \code{QUAT_NO_RANDOM} -- omit \code{randomize()} (which needs \header{la_random.h});
\item \code{QUAT_NO_DOUBLE} -- instantiate only \code{float} (otherwise \code{float} and \code{double}).
\end{itemize}
The templates are explicitly instantiated in \header{vecmat3.cc} for \code{float} and \code{double} only.
The classes live in namespace \code{LA_Vecmat3}; the header ends with a global
\code{using namespace LA_Vecmat3;}, so the names are visible without qualification
after including \header{vecmat3.h} (or \header{la.h}, which includes it).
\code{LA_traits} specializations declare both classes plain data with a no-op
\code{copyonwrite}, so they can be used as elements of \code{NRVec}, \code{NRMat} etc.
\section{Storage}
\code{Vec3<T>} holds the public array \code{T q[3]}, \code{Mat3<T>} the public array
\code{T q[3][3]} in row-major (C) order, i.e.\ \code{q[i][j]} is row $i$, column $j$.
There is no reference counting; the compiler-generated copy constructor and assignment
copy the data. \code{elements()} returns a reference to the C array, conversion to \code{T*}
gives a pointer to the first element.
\section{Vec3 reference}
\begin{lareference}
\lafunc{Vec3(); Vec3(const T x, const T u=0, const T v=0)}{Uninitialized vector; vector $(x,u,v)$.}
\lafunc{Vec3(const T *x); Vec3(const T (&a)[3])}{Copy three elements from memory or from a C array (also allows brace initialization \code{Vec3<double> x({1,2,3})}).}
\lafunc{T (&elements())[3]; const T (&elements() const)[3]}{Reference to the underlying array.}
\lafunc{operator T*(); operator const T*() const}{Pointer to the data.}
\lafunc{T &operator[](const int i)}{Element access (no range check), const version available.}
\lafunc{void clear()}{Set to zero (memset, \code{T} must be plain data).}
\lafunc{Vec3 &operator*=(const T); Vec3 &operator/=(const T); Vec3 operator*(const T) const; Vec3 operator/(const T) const}{Scaling by a scalar.}
\lafunc{T sum() const; T asum() const; T sumsqr() const; T prod() const}{$\sum_i q_i$, $\sum_i |q_i|$, $\sum_i q_i^2$, $\prod_i q_i$.}
\lafunc{Vec3 operator-() const; Vec3 &operator+=(const Vec3 &); Vec3 &operator-=(const Vec3 &); Vec3 operator+(const Vec3 &) const; Vec3 operator-(const Vec3 &) const}{Vector algebra.}
\lafunc{Vec3 operator*(const Vec3 &rhs) const}{\emph{Vector (cross) product} $\mathbf{q}\times\mathbf{rhs}$.}
\lafunc{T dot(const Vec3 &rhs) const}{Scalar product.}
\lafunc{Vec3 elementwise_product(const Vec3 &rhs) const}{Componentwise product.}
\lafunc{T normsqr() const; T norm() const}{Squared and Euclidean norm.}
\lafunc{Vec3 &normalize()}{Divide by the norm (no check for zero).}
\lafunc{Vec3 &fast_normalize()}{For \code{float} uses the ``fast inverse square root'' bit trick \code{fast_sqrtinv(float)} (approximate); for other types identical to \code{normalize()}.}
\lafunc{Vec3 operator*(const Mat3<T> &rhs) const}{Row vector times matrix, $\mathbf{q}^{\mathsf T} M$.}
\lafunc{Vec3 timesT(const Mat3<T> &rhs) const}{$\mathbf{q}^{\mathsf T} M^{\mathsf T}$, i.e.\ $M\mathbf{q}$.}
\lafunc{Mat3<T> outer(const Vec3 &rhs) const}{Outer (tensor) product $\mathbf{q}\,\mathbf{rhs}^{\mathsf T}$.}
\lafunc{void addouter(Mat3<T> &m, const Vec3 &rhs, const T weight) const}{$m \mathrel{+}= w\,\mathbf{q}\,\mathbf{rhs}^{\mathsf T}$.}
\lafunc{void inertia(Mat3<T> &itensor, const T weight) const}{Adds the contribution of a point mass \code{weight} at position $\mathbf{q}$ to the inertia tensor: $I \mathrel{+}= w\,(r^2 \mathbf{1} - \mathbf{q}\mathbf{q}^{\mathsf T})$.}
\lafunc{void randomize(const T x)}{Elements uniformly random in $[-x,x)$ via \code{RANDDOUBLESIGNED} (chapter~\ref{ch:utilities}); for \code{float} and \code{double}.}
\lafunc{int fprintf(FILE *f, const char *format) const; int sprintf(char *f, const char *format) const}{C-style output, \code{format} must contain three conversions.}
\lafunc{int fscanf(FILE *f, const char *format); int sscanf(const char *f, const char *format)}{C-style input, \code{format} contains three conversions (e.g.\ \code{"\%lf \%lf \%lf"}); returns the number of items read.}
\end{lareference}
Free function: \code{T hypot3(const Vec3<T> &c, const Vec3<T> &d)} -- Euclidean distance $|\mathbf c-\mathbf d|$.
\section{Mat3 reference}
\begin{lareference}
\lafunc{Mat3(); Mat3(const T (&a)[3][3]); Mat3(const T *x)}{Uninitialized; from a C array (also brace initialization \code{Mat3<double> y({{1,2,3},{4,5,6},{7,8,9}})}); from 9 elements in row-major order.}
\lafunc{Mat3(const T x00, const T x01, const T x02, const T x10, const T x11, const T x12, const T x20, const T x21, const T x22)}{From nine elements, row by row.}
\lafunc{Mat3(const T x); Mat3 &operator=(const T &x)}{Scalar matrix $x\mathbf{1}$. A single \emph{integer} constructor argument is forbidden
(deleted), since through the conversion \code{NRMat(const Mat3&)} it would silently turn e.g.\ \code{NRMat<double> a(5)} into a $3\times3$ matrix;
write \code{Mat3<double>(5.)}.}
\lafunc{void indentity()}{Set to the unit matrix (sic, the name is misspelled in the code).}
\lafunc{T (&elements())[3][3]; operator T*()}{Reference to the array, pointer to the data (const versions exist).}
\lafunc{T *operator[](const int i); T &operator()(const int i, const int j)}{Row pointer (so \code{m[i][j]} works) and element access, const versions exist.}
\lafunc{void clear()}{Zero matrix.}
\lafunc{Mat3 &operator+=(const T); Mat3 &operator-=(const T); Mat3 operator+(const T) const; Mat3 operator-(const T) const}{Add/subtract a multiple of the unit matrix (diagonal only).}
\lafunc{Mat3 &operator*=(const T); Mat3 &operator/=(const T); Mat3 operator*(const T) const; Mat3 operator/(const T) const}{Scaling.}
\lafunc{void randomize(const T x, const bool symmetric=false)}{Random elements in $[-x,x)$, optionally symmetric; for \code{float} and \code{double}.}
\lafunc{Mat3 operator-() const; Mat3 &operator+=(const Mat3 &); Mat3 &operator-=(const Mat3 &); Mat3 operator+(const Mat3 &) const; Mat3 operator-(const Mat3 &) const}{Matrix algebra.}
\lafunc{Mat3 operator*(const Mat3 &rhs) const; Mat3 timesT(const Mat3 &rhs) const; Mat3 Ttimes(const Mat3 &rhs) const; Mat3 TtimesT(const Mat3 &rhs) const}{$AB$, $AB^{\mathsf T}$, $A^{\mathsf T}B$, $A^{\mathsf T}B^{\mathsf T}$.}
\lafunc{Vec3<T> operator*(const Vec3<T> &rhs) const; Vec3<T> Ttimes(const Vec3<T> &rhs) const}{$A\mathbf v$ and $A^{\mathsf T}\mathbf v$.}
\lafunc{T trace() const; T determinant() const}{Trace and determinant.}
\lafunc{void transposeme(); Mat3 transpose() const}{Transpose in place / return the transpose.}
\lafunc{Mat3 inverse(T *det = NULL) const}{Inverse by the adjugate formula; optionally returns the determinant. No check for singularity (division by zero determinant).}
\lafunc{Vec3<T> linear_solve(const Vec3<T> &rhs, T *det = NULL) const}{Solves $A\mathbf x=\mathbf{rhs}$ by Cramer's rule (alternative to \code{simple_gaussj}, section~\ref{sec:utilities:simple}); no singularity check.}
\lafunc{int fprintf(FILE *f, const char *format) const; int fscanf(FILE *f, const char *format)}{C-style I/O row by row, \code{format} contains three conversions.}
\lafunc{void symmetrize()}{Replace off-diagonal pairs by their average.}
\lafunc{bool eivec_sym(Vec3<T> &w, Mat3 &v, const bool sortdown=false) const}{Eigenvalues \code{w} and eigenvectors (\emph{columns} of \code{v}) of a real symmetric matrix by the cyclic Jacobi method (at most 50 sweeps); symmetry is not checked. Eigenvalues sorted ascending, or descending if \code{sortdown}. Returns \code{false} on success, \code{true} if not converged. (An analytic Cardano-based solver is present in the source in a disabled \texttt{\#if 0} block, abandoned for numerical instability.)}
\lafunc{T norm(const T scalar = 0) const}{Frobenius norm of $A - \mathrm{scalar}\cdot\mathbf 1$.}
\lafunc{void qrd(Mat3 &q, Mat3 &r)}{QR decomposition $A=QR$ by three Givens rotations; \emph{destroys} \code{*this} (non-const).}
\lafunc{void svd(Mat3 &u, Vec3<T> &w, Mat3 &v, bool proper_rotations=false) const}{Singular value decomposition $A = U\,\mathrm{diag}(w)\,V^{\mathsf T}$ following \cite{vecmat3:mcadams2011} (eigenvectors of $A^{\mathsf T}A$ by \code{eivec_sym}, then QR of $AV$). Singular values in descending order. If \code{proper_rotations} is false, a negative $w_2$ is made positive by changing the sign of the last column of $U$; if true, $U$ and $V$ are proper rotations ($\det U=\det V=+1$, the sign is moved into $w_2$, which may then be negative), as needed e.g.\ for the Kabsch alignment without mirroring.}
\lafunc{void diagmultl(const Vec3<T> &rhs); void diagmultr(const Vec3<T> &rhs)}{$A\leftarrow\mathrm{diag}(\mathbf{rhs})A$ and $A\leftarrow A\,\mathrm{diag}(\mathbf{rhs})$.}
\lafunc{Mat3 svdinverse(const T thr=1000*DBL_EPSILON) const}{Pseudoinverse via SVD; singular values below the \emph{absolute} threshold \code{thr} are treated as zero.}
\end{lareference}
\section{Free functions: I/O, Euler angles, projection}
\begin{lareference}
\lafunc{std::ostream &operator<<(std::ostream &s, const Vec3<T> &x); std::istream &operator>>(std::istream &s, Vec3<T> &x)}{Three numbers separated by spaces (no dimensions, no newline).}
\lafunc{std::ostream &operator<<(std::ostream &s, const Mat3<T> &x); std::istream &operator>>(std::istream &s, Mat3<T> &x)}{Nine numbers, one row per line on output.}
\lafunc{void euler2rotmat(const T *eul, Mat3<T> &a, const char *type, bool transpose=0, bool direction=0, bool reverse=0)}{Rotation matrix from three Euler angles \code{eul[0..2]}; see below.}
\lafunc{void rotmat2euler(T *eul, const Mat3<T> &a, const char *type, bool transpose=0, bool direction=0, bool reverse=0)}{Inverse of \code{euler2rotmat} with the same options.}
\lafunc{void perspective(T *proj_xy, const Vec3<T> &point, const Mat3<T> &rot_angle, const Vec3<T> &camera, const Vec3<T> &plane_to_camera)}{Perspective projection of a 3D point onto a 2D plane: $\mathbf d = R\,\mathbf{point}-\mathbf{camera}$, $\mathrm{proj}_i = (e_z/d_z)\, d_i + e_i$, $i=x,y$, where $\mathbf e$=\code{plane_to_camera}.}
\end{lareference}
\paragraph{Euler angle conventions.}
\code{type} is a three-character string naming the rotation axes: one of the six proper
Euler sequences \code{"xzx" "xyx" "yxy" "yzy" "zyz" "zxz"} or the six Tait--Bryan sequences
\code{"xzy" "xyz" "yxz" "yzx" "zyx" "zxy"}. With default options the result is the product of
active elementary rotations
\[
a = R_{\mathrm{type[0]}}(\mathrm{eul}_0)\,R_{\mathrm{type[1]}}(\mathrm{eul}_1)\,R_{\mathrm{type[2]}}(\mathrm{eul}_2),
\]
following the table in the Wikipedia article ``Euler angles''. The options modify this:
\code{transpose} returns $a\Tr$; \code{direction} changes the sign of all angles;
\code{reverse} swaps the roles of \code{eul[0]} and \code{eul[2]}. In \code{rotmat2euler},
the middle angle is returned in $[0,\pi]$ for proper Euler sequences and in $[-\pi/2,\pi/2]$ for
Tait--Bryan ones; the degenerate (gimbal lock) case is not treated specially. An unknown
\code{type} leaves the output untouched without an error. The macro \code{Euler_case(a,b,c)}
maps the three axis characters to an integer used in the internal switch.