diff --git a/doc/bib/iterative.tex b/doc/bib/iterative.tex new file mode 100644 index 0000000..a78daed --- /dev/null +++ b/doc/bib/iterative.tex @@ -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). diff --git a/doc/bib/optimize.tex b/doc/bib/optimize.tex new file mode 100644 index 0000000..538cb12 --- /dev/null +++ b/doc/bib/optimize.tex @@ -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). diff --git a/doc/bib/permutation.tex b/doc/bib/permutation.tex new file mode 100644 index 0000000..015b959 --- /dev/null +++ b/doc/bib/permutation.tex @@ -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. diff --git a/doc/bib/polynomial.tex b/doc/bib/polynomial.tex new file mode 100644 index 0000000..7f40640 --- /dev/null +++ b/doc/bib/polynomial.tex @@ -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). diff --git a/doc/bib/quaternion.tex b/doc/bib/quaternion.tex new file mode 100644 index 0000000..78b20ce --- /dev/null +++ b/doc/bib/quaternion.tex @@ -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). diff --git a/doc/bib/utilities.tex b/doc/bib/utilities.tex new file mode 100644 index 0000000..c27d2aa --- /dev/null +++ b/doc/bib/utilities.tex @@ -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). diff --git a/doc/bib/vecmat3.tex b/doc/bib/vecmat3.tex new file mode 100644 index 0000000..544e615 --- /dev/null +++ b/doc/bib/vecmat3.tex @@ -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. diff --git a/doc/biblio.tex b/doc/biblio.tex new file mode 100644 index 0000000..d003004 --- /dev/null +++ b/doc/biblio.tex @@ -0,0 +1,21 @@ +%bibliography; the items of each chapter are kept in bib/.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} diff --git a/doc/bitvector.tex b/doc/bitvector.tex new file mode 100644 index 0000000..7037fbd --- /dev/null +++ b/doc/bitvector.tex @@ -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}, 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 &r); void bitvector_compress(bitvector &r, const NRVec &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} 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.} diff --git a/doc/building.tex b/doc/building.tex new file mode 100644 index 0000000..d22e61d --- /dev/null +++ b/doc/building.tex @@ -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 LDFLAGS=-L +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=} & 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). diff --git a/doc/concepts.tex b/doc/concepts.tex new file mode 100644 index 0000000..6cb6ee7 --- /dev/null +++ b/doc/concepts.tex @@ -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 a(1.,1000); //1000 elements equal to 1 +NRVec b(a); //no copy of the data, a.getcount()==2 +NRVec 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 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 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 &x) //non-const, x may share data with another vector +{ +const NRVec &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 a(n,n); +... +NRMat 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}, 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} and for \code{NRMat >}} +\lafunc{typedef ... realtype; typedef ... complextype}{the corresponding real and complex type, e.g.\ \code{NRVec} and \code{NRVec >}} +\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} distinguishes scalars from classes, \code{LA_traits_complex} +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} 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} +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} +(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 >} (a list of matrices) or +\code{NRMat >}. 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}). diff --git a/doc/fourindex.tex b/doc/fourindex.tex new file mode 100644 index 0000000..06e8b36 --- /dev/null +++ b/doc/fourindex.tex @@ -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}: 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}: the same list stored in a binary file, read and written in large + page-aligned blocks; +\item \code{fourindex_dense}: \emph{dense} in-core storage for a given symmetry type \code{S}, + derived from \code{NRMat} or \code{NRSMat} (``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}, \code{fourindex_ext} and +\code{fourindex_dense} 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}. + +\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 void symmetry_faktor(const fourindexsymtype symmetry, const union packed_index &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 struct indiv_index {I i; I j; I k; I l;}}{the four indices (packed with GNU C).} +\lafunc{template union packed_index {I packed[4]; struct indiv_index indiv;}}{the indices accessible by name or as an array.} +\lafunc{template struct matel4 {T elem; matel4 *next; union packed_index index;}}{list element of \code{fourindex}; also the type returned by the permutation iterators.} +\lafunc{template struct matel4stored {T elem; union packed_index 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} records (machine dependent, no header +unless written by \code{fourindex::put(fd,true)}). + +\section{In-core sparse storage: \texttt{fourindex}}\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 *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 &index, const T elem); void add(const I (&index)[4], const T elem); void add(const matel4 &rhs); void add(const matel4stored &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 &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} (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} 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 std::ostream& operator<<(std::ostream &s, const fourindex &x)}{text output: the dimension, lines \code{i j k l value}, and a final line with four terminators and 0.} +\lafunc{template std::istream& operator>>(std::istream &s, fourindex &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}; 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}}\label{sec:fourindex:ext} + +\code{fourindex_ext} accesses a file of \code{matel4stored} 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 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}; 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 std::ostream& operator<<(std::ostream &s, const fourindex_ext &x)}{text output of the whole file in the format of \code{fourindex}.} +\lafunc{template std::istream& operator>>(std::istream &s, fourindex_ext &x)}{read the text format and append the elements to the file (with \code{flush()}).} +\end{lareference} + +\section{Dense storage: \texttt{fourindex\_dense}}\label{sec:fourindex:dense} + +\begin{lstlisting} +template class fourindex_dense; // only specializations exist +\end{lstlisting} +The dense classes are derived publicly from \code{NRMat} or \code{NRSMat}, 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} and +\code{fourindex_ext} (named \code{DUMMY} in some places). \code{LA_traits >} 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} 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 &rhs)}{reinterpret a matrix (shared data); also an implicit conversion from the base class.} +\lafunc{fourindex_dense(const fourindex &rhs); fourindex_dense(const fourindex_ext &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} 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} 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} in place of +\code{NRMat}. + +\subsection{nosymmetry} +Base \code{NRMat} 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 &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} 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 &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} 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 &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} 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 &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} 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 &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 &rhs); fourindex_dense(const fourindex_ext &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 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 D(1.,n); +NRMat J(0.,n,n); +for(fourindex::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::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 fe(fd,twoelectronrealmullikan,n); +fourindex_dense fdense(fe); +const fourindex_dense &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 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::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} diff --git a/doc/introduction.tex b/doc/introduction.tex new file mode 100644 index 0000000..2b254a7 --- /dev/null +++ b/doc/introduction.tex @@ -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} (vector), \code{NRMat} (dense matrix) and \code{NRSMat} (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 >}; 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}, \code{NRVec_from1} (chapter~\ref{ch:vec}) \\ +\header{mat.h} & \code{NRMat}, \code{NRMat_from1} (chapter~\ref{ch:mat}) \\ +\header{smat.h} & \code{NRSMat}, \code{NRSMat_from1} (chapter~\ref{ch:smat}) \\ +\header{sparsemat.h}, \header{sparsesmat.h} & sparse matrices \code{SparseMat}, \code{SparseSMat} (chapter~\ref{ch:sparse}) \\ +\header{csrmat.h} & compressed row storage sparse matrix (unfinished) \\ +\header{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} 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 h(n); +h.randomize(1.); //symmetric matrix with random elements +NRSMat hsave; +hsave |= h; //deep copy, diagonalize() destroys its argument +NRVec e(n); +NRMat v(n,n); +diagonalize(h,e,&v); //eigenvalues e (ascending), eigenvectors in columns of v +NRMat r = hsave*v; //residual H V - V diag(e) +r.copyonwrite(); //always before writing via operator() or [] +for(int i=0; i d; + double c; +public: + Chain(int n, double cc) : d(n), c(cc) {for(int i=0; i &r, const bool divide=false, bool cache=false) const + { + if(divide) {r.copyonwrite(); for(int i=0; i &r, const char trans, const double alpha, const NRVec &x) const + { + int n=d.size(); + if(beta==0) r=0.; else r*=beta; + r.copyonwrite(); //before writing r[i] + for(int i=0; i0) t+=c*x[i-1]; + if(i e(nroots); +NRVec *v = new NRVec[nroots]; +davidson(h,e,v,NULL,nroots,false,1e-8); //vectors in memory +std::cout << "eigenvalues "< hd = explicit_matrix(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 a(n,n); ... //non-symmetric, diagonally dominant +NRVec b(n), x(n); ... +bool ok=gmres(a,b,x,true,1e-10,50,false); //x: solution +NRSMat 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 > diis(4); +NRVec x(0.,n); +for(int it=0; it xn=f(x); + NRVec 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(...)}. +\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 >}) and call \code{setup()}. +\end{itemize} diff --git a/doc/lamacros.tex b/doc/lamacros.tex new file mode 100644 index 0000000..e9a0e85 --- /dev/null +++ b/doc/lamacros.tex @@ -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}; 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} diff --git a/doc/manual.pdf b/doc/manual.pdf new file mode 100644 index 0000000..4ec4438 Binary files /dev/null and b/doc/manual.pdf differ diff --git a/doc/manual.tex b/doc/manual.tex new file mode 100644 index 0000000..d6f78c2 --- /dev/null +++ b/doc/manual.tex @@ -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} diff --git a/doc/mat.tex b/doc/mat.tex new file mode 100644 index 0000000..3462cfd --- /dev/null +++ b/doc/mat.tex @@ -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} 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} (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} for +\code{double}, \code{std::complex}, and the integer types \code{char}, \code{short}, \code{int}, +\code{long}, \code{long long} and their unsigned variants. Many numerical members are only +specialized (BLAS-backed) for \code{double} and \code{complex} 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 >}) 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} \\ +\texttt{operator*(const NRMat\&)} & \texttt{double}, \texttt{complex}, \texttt{int} \\ +\texttt{diagonalof}, \texttt{orthonormalize}, \texttt{strassen}, \texttt{gemm} with \texttt{SparseMat} & \texttt{double} only \\ +\texttt{nonhermiticity} & \texttt{complex} only \\ +everything else & all instantiated types \\ +\bottomrule +\end{tabular} +\caption{Availability of \texttt{NRMat} 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&, n, m, offset)} and \code{NRVec(const NRMat&)} 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 NRMat(const T (&a)[R][C])}{From a two-dimensional C array; also allows brace initialization \code{NRMat x({{1,2,3},{4,5,6}});}.} +\lafunc{NRMat(const Mat3 &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::NRMat_Noncomplex_type &rhs, bool imagpart = false)}{Complexifying (non-explicit) constructor, defined for \code{T = complex}: \code{rhs} (\code{NRMat}) becomes the real part (or the imaginary part if \code{imagpart}), the other part is zero.} +\lafunc{explicit NRMat(const NRMat > &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 &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 &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 &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 &p, const bool direction)}{Weight times permutation matrix.} +\lafunc{explicit NRMat(const PermutationAlgebra &p, const bool direction, const int nforce = 0)}{Sum of weighted permutation matrices of the elements of a permutation algebra; size taken from the first element unless \code{nforce>0}. (The header remarks that the permutation-matrix representation of $S_n$ is reducible to only the irreps $[n]$ and $[n-1,1]$, so e.g.\ Young projectors cannot be represented this way.) See chapter~\ref{ch:permutation}.} +\lafunc{explicit NRMat(const SparseMat &rhs); explicit NRMat(const SparseSMat &rhs); explicit NRMat(const SemiSparseMat &rhs)}{Dense matrix from sparse ones (chapter~\ref{ch:sparse}).} +\lafunc{explicit NRMat(const CSRMat &rhs)}{Not supported: \code{CSRMat} is unfinished, the declaration is kept for possible future work (no definition, a link error if used).} +\lafunc{~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 &rhs); NRMat & operator-=(const NRSMat &rhs); inline const NRMat operator+(const NRSMat &rhs) const; inline const NRMat operator-(const NRSMat &rhs) const}{Addition/subtraction of a packed symmetric matrix (square $A$).} +\lafunc{NRMat & operator^=(const NRMat &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} \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 &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 &rhs) const}{$AS$ with packed symmetric (real) or hermitian (complex) $S$ (BLAS \code{dspmv}/\code{zhpmv} per row).} +\lafunc{SparseSMat operator*(const SparseSMat &rhs) const}{Dense times sparse, the result is sparse (chapter~\ref{ch:sparse}).} +\lafunc{const NRVec operator*(const NRVec &rhs) const; const NRVec > operator*(const NRVec > &rhs) const}{Matrix--vector product via \code{NRVec::gemv} (the second form: real matrix times complex vector).} +\lafunc{void gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x) const; void gemv(const T beta, NRVec > &r, const char trans, const T alpha, const NRVec > &x) const}{$r\leftarrow\alpha\,\mathrm{op}(A)x+\beta r$; forwards to \code{r.gemv(beta,*this,trans,alpha,x)}, see chapter~\ref{ch:vec}.} +\lafunc{const T dot(const NRMat &rhs) const}{$\sum_{ij}A_{ij}^*B_{ij}$ (Frobenius inner product, conjugating \code{*this}).} +\lafunc{const NRSMat transposedtimes() const}{$A\Tr A$ ($A\Hc A$ for complex) as packed matrix.} +\lafunc{const NRSMat 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 &rhs); void diagmultr(const NRVec &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 &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 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 &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 column(const int j, int l = -1) const; void columnset(const NRVec &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 &rows, const NRVec &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 &rows, const NRVec &cols, const NRMat &rhs)}{Scatter: $A_{r_i c_j}\leftarrow R_{ij}$.} +\lafunc{const T* diagonalof(NRVec &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 &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} 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 &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 &p, const bool inverse = false) const; const NRMat permuted_both(const NRPerm &p, const NRPerm &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 &p); void permuteme_cols(const CyclePerm &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 &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::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 rsum() const; const NRVec 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::normtype nonsymmetry() const}{$\sum_{i>j}|A_{ij}-A_{ji}|^2$ (square of the norm, no square root).} +\lafunc{const typename LA_traits::normtype nonhermiticity() const}{\code{complex} only: $\sum_{i\ge j}|A_{ij}-A_{ji}^*|^2$.} +\lafunc{NRMat::normtype> abs() const}{Matrix of element-wise absolute values.} +\lafunc{NRMat::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 *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::normtype thr = 0) const}{Pseudo-inverse via SVD, singular values below \code{thr} are dropped.} +\lafunc{NRMat absadjoint(const typename LA_traits::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::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::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&)} and \code{gemm} with a \code{SparseMat} +factor listed above, \code{NRMat} declares +\begin{lareference} +\lafunc{NRMat & operator+=(const SparseMat &rhs); NRMat & operator-=(const SparseMat &rhs)}{Add/subtract the sparse matrix (defined in \code{sparsemat.h}; a symmetric \code{SparseMat} is unfolded, without conjugation).} +\end{lareference} + +\section{Related free functions}\label{sec:mat:free} +\begin{lareference} +\lafunc{template const NRMat operator+(const T &a, const NRMat &rhs); template const NRMat operator*(const T &a, const NRMat &rhs)}{$a\,\mathbf 1+A$ and $aA$.} +\lafunc{template const NRMat operator-(const T &a, const NRMat &rhs) = delete}{Deliberately not available (compile error); write \code{-A+a} for $a\,\mathbf 1-A$.} +\lafunc{template NRMat > complexify(const NRMat &rhs)}{Complex matrix with real part \code{rhs}.} +\lafunc{template NRMat > complexify(const NRMat &rhsr, const NRMat &rhsi)}{Complex matrix from real and imaginary parts.} +\lafunc{template void NRMat_convert(NRMat &a, const NRMat &b)}{Element type conversion: resizes \code{a} and assigns \code{(T)b(i,j)}.} +\lafunc{template std::ostream& operator<<(std::ostream &s, const NRMat &x)}{Text output: a line with the dimensions, then one line per row.} +\lafunc{template std::istream& operator>>(std::istream &s, NRMat &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} is derived publicly from \code{NRMat} 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}, 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 explicit NRMat_from1(const T (&a)[R][C])}{From a two-dimensional C array / brace initializer.} +\lafunc{NRMat_from1(const NRMat &rhs)}{Implicit conversion from the base class (shallow, shares data).} +\lafunc{explicit NRMat_from1(const NRSMat_from1 &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 a(4,3), b(3,5); +a.randomize(1.); b.randomize(1.); + +NRMat c = a*b; // dgemm +NRMat d(3,3); // result must have the right shape +d.gemm(0., a, 't', a, 'n', 1.); // d = a^T a +NRSMat s = a.transposedtimes();// the same, packed symmetric +cout << (d - NRMat(s)).norm() << endl; + +NRMat e = c; // shares data with c +e.copyonwrite(); // needed: we write through operator() +e(0,0) = 1.; +const NRMat &cc = c; // read-only access via const reference +cout << cc(0,0) << " " << e(0,0) << endl; + +NRMat u(3,3); u.randomize(1.); +u.orthonormalize(false); // orthonormal columns +cout << (u.transpose()*u - 1.).norm() << endl; // "- 1." subtracts the unit matrix + +NRMat blk = c.submatrix(1,2,0,4); // rows 1..2, all columns +NRVec r = c.row(3); +NRMat 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 m({{1,2,3},{4,5,6}}); // 2x3, indices from 1 +const NRMat_from1 &mc = m; +cout << mc(2,3) << " " << mc[1][2] << endl; // both print 6: operator[] stays 0-based + +NRMat re(2,2), im(2,2); +re.randomize(1.); im.randomize(1.); +NRMat > z = complexify(re, im); +NRMat > h(2,2); +h.gemm(0., z, 'c', z, 'n', 1.); // h = z^dagger z +cout << h.nonhermiticity() << endl; // 0 +NRMat rp(z); // real part (explicit constructor) +NRMat ip(z, true); // imaginary part + +NRMat k = re.otimes(im); // Kronecker product, 4x4 +NRMat 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 A(a, n, m)} fills \emph{all} elements with \code{a}. \code{a - A} does not compile; write \code{-A + a}.} +\lawarning{\code{NRMat(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&, 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} (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.} diff --git a/doc/nonclass.tex b/doc/nonclass.tex new file mode 100644 index 0000000..af641b6 --- /dev/null +++ b/doc/nonclass.tex @@ -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} 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 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} 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} \\ +\texttt{linear\_solve(NRMat\&, NRVec\&, ...)}, \texttt{linear\_solve(NRSMat\&, ...)} & \texttt{double}, \texttt{complex} (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} \\ +\texttt{gdiagonalize} & real matrix (real or complex eigenvector storage), complex matrix \\ +\texttt{trace2} with \texttt{NRSMat} & \texttt{double}, \texttt{complex} (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 &a, NRMat *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 &a, NRVec &b, T *det=0, int n=0)}{The same with a single right-hand side \code{b}.} +\lafunc{void linear_solve(NRSMat &a, NRMat *b, T *det=0, int n=0); void linear_solve(NRSMat &a, NRVec &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 &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 &A, NRMat &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 calcinverse(NRMat 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 calcsvdinverse(NRMat 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 svd_solve(NRMat &a, const NRVec &b, double thres=1e-12, int *nullity=NULL); NRMat svd_solve(NRMat &a, const NRMat &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::elementtype determinant(MAT a)}{Determinant by LU (\code{NRMat}) or Bunch--Kaufman (\code{NRSMat}) factorization of a copy.} +\lafunc{const LA_traits::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 &a, bool upper=1); void cholesky(NRMat > &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 &a, NRVec &w, const bool eivec=1, const bool corder=1, int n=0, NRMat *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 &a, NRVec &w, NRMat *v, const bool corder=1, int n=0, NRSMat *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 &d, NRVec &d1, NRMat *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 &a, NRVec &wr, NRVec &wi, NRMat *vl=NULL, NRMat *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat *b=NULL, NRVec *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 &a, NRVec > &w, NRMat > *vl=NULL, NRMat > *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat *b=NULL, NRVec *beta=NULL)}{Template +specialized for \code{T=double} (calls the previous routine and converts to complex eigenvalues and eigenvectors) and \code{T=complex} (\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 > &a, NRVec &wr, NRVec &wi, NRMat > *vl=NULL, NRMat > *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat > *b=NULL, NRVec > *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 &a, NRMat *u, NRVec &s, NRMat *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 matrixfunction(NRSMat 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 matrixfunction(NRMat a, std::complex (*f)(const std::complex &))}{$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 realmatrixfunction(NRMat a, double (*f)(double))}{$f(A)$ for a real symmetric matrix in full storage (only the lower triangle is used).} +\lafunc{NRMat > complexmatrixfunction(NRMat 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 sqrt(const NRSMat &a); NRMat sqrtinv(const NRSMat &a)}{$A^{1/2}$, $A^{-1/2}$ of a symmetric matrix (e.g.\ symmetric orthogonalization).} +\lafunc{NRMat realsqrt(const NRMat &a); NRMat realsqrtinv(const NRMat &a)}{The same for a symmetric matrix in full storage.} +\lafunc{NRMat log(const NRSMat &a)}{Logarithm of a symmetric positive definite matrix.} +\lafunc{NRMat log(const NRMat &a); NRMat > log(const NRMat > &a)}{Principal logarithm of a +general matrix via complex eigendecomposition (for a real matrix the imaginary part of the result is dropped).} +\lafunc{NRMat exp0(const NRMat &a); NRMat > exp0(const NRMat > &a)}{$\exp(A)$ via +eigendecomposition, an alternative to the Taylor-series \code{exp()} of section~\ref{sec:nonclass:matexp}.} +\lafunc{NRMat copytest(const NRMat &a); NRMat > copytest(const NRMat > &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 sqrtinv(const std::complex &)}{Scalar $1/\sqrt{x}$ (used as function argument above).} +\lafunc{void adjustphases(NRMat &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::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 &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 &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 > expi(const NRMat &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::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::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::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 diagofproduct(const NRMat &a, const NRMat &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 &a, const NRMat &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 &a, const NRSMat &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 &a, const NRMat &b, const bool diagscaled=0); T trace2(const NRMat &a, const NRSMat &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 twoside_transform(const NRSMat &S, const NRMat &C, bool transp=0); const NRMat twoside_transform(const NRMat &S, const NRMat &C, bool transp=0)}{$C\Hc S C$, +or $C S C\Hc$ with \code{transp} (similarity/congruence transformation).} +\lafunc{const NRMat diagonalmatrix(const NRVec &x)}{Square matrix with diagonal \code{x}.} +\lafunc{const NRMat commutator(const NRMat &x, const NRMat &y, const bool trx=0, const bool tryy=0); const NRMat 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::normtype MatrixNorm(const MAT &A, const char norm)}{Matrix norm by \code{dlange}/\code{zlange}, implemented for +\code{NRMat} and \code{NRMat >} (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::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::realtype realpart(const T &a); const LA_traits::realtype imagpart(const T &a)}{Real or imaginary part as a real object; +specialized for \code{NRVec}, \code{NRMat}, \code{NRSMat} and \code{SparseMat} over \code{complex}.} +\lafunc{const LA_traits::complextype realmatrix(const T &a); const LA_traits::complextype imagmatrix(const T &a)}{Complex object $A$ resp.\ $\mathrm{i}A$ from a real one; +for \code{NRMat} and \code{SparseSMat}.} +\lafunc{const LA_traits::complextype complexmatrix(const T &re, const T &im)}{Complex object $\mathrm{re}+\mathrm{i}\,\mathrm{im}$; for \code{NRMat} and \code{SparseSMat}.} +\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::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}, \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 reconstructmatrix(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} 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 s(n); +for(int i=0; i work = s; //shallow copy, diagonalize detaches it +NRVec w(n); +NRMat v(n,n); +diagonalize(work, w, &v); //w ascending, columns of v are eigenvectors +NRMat sv = NRMat(s)*v; +for(int k=0; k 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 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 h(n,n); //... filled with h.add(i,j,x) +NRMat e = exp(NRMat(h)); //dense exponential +SparseMat es = exp(h); //sparse arithmetics +NRVec y = exptimes(h, x); //exp(h) x, only gemv used +NRVec > xc(complexify(x)); +NRVec > psi = exptimes(h, xc, false, std::complex(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 g(n,n); //... filled +NRMat scratch = g; +NRVec > lambda(n); +NRMat > vl(n,n), vr(n,n); +gdiagonalize(scratch, lambda, &vl, &vr, false, 0, 0, 0, (NRMat*)NULL, (NRVec*)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} diff --git a/doc/optimize.tex b/doc/optimize.tex new file mode 100644 index 0000000..1cf4b3f --- /dev/null +++ b/doc/optimize.tex @@ -0,0 +1,1005 @@ +\chapter{Optimization}\label{ch:optimize} + +\laheader{optimize.h, optimize.cc} + +The optimization module provides local minimization of a function $f:\mathbb{R}^n\to\mathbb{R}$ (and, for two methods, the +search for saddle points) by a family of methods sharing one interface: quasi-Newton methods with line search (BFGS, DFP, SR1, +L-BFGS), nonlinear conjugate gradients, GDIIS, trust-region Newton and quasi-Newton methods, partitioned rational function +optimization (P-RFO) for saddle points, Levenberg-Marquardt for least squares, the derivative-free methods of Nelder and Mead, +Powell and COBYLA (with nonlinear inequality constraints), and the first-order and stochastic methods SGD, Adam, SPSA, the +quantum natural gradient and Rotosolve used in machine learning and for variational quantum algorithms. + +The design separates three things: +\begin{itemize} +\item the \emph{problem}: a class derived from \code{OptProblem} implementing \code{funcgrad()}, optionally also +deriving from \emph{capability} interfaces (Hessian, metric, trigonometric structure, constraints, residuals) which some +methods require or exploit (section~\ref{sec:optimize:problem}); +\item the \emph{method}: a class derived from \code{Optimizer} with the single virtual function \code{minimize()}; +\item the \emph{hyperparameters}: plain value objects of a class hierarchy rooted in \code{hyperparameters}, one derived +class per method, passed to \code{minimize()} and returned filled with the values actually reached +(section~\ref{sec:optimize:hyper}). +\end{itemize} +Methods are written on top of a small infrastructure (evaluation counter, iteration bookkeeping, line searches) and of three +common drivers (line search, first order, trust region), so that a new method usually consists of a short class supplying the +search direction, the update rule or the trust-region step (section~\ref{sec:optimize:drivers}). + +All templates are parameterized by the real type \code{T}, but optimize.cc instantiates them for \code{T = double} only. +The capability interfaces, adapters and the hyperparameter classes are header-only templates; the optimizers, the adapters with +member functions in optimize.cc (\code{FrozenOptProblem}, \code{ParameterShift}, \code{FiniteDifference}, +\code{OptLeastSquares}) and the auxiliary functions exist in the library only for \code{double}. +The common drivers \code{linesearch_minimize}, \code{firstorder_minimize} and \code{trustregion_minimize}, the extension +points for user-written methods, are explicitly instantiated for \code{double} as well. + +The data members of all classes of this chapter are small, except vectors held by \code{FrozenOptProblem} and +\code{optevaluator}; these are ordinary \code{NRVec} objects following the reference counting and copy-on-demand scheme of +section~\ref{sec:concepts:refcount}, which has consequences for user code described in section~\ref{sec:optimize:pitfalls}. +The library code of this module calls \code{copyonwrite()} (or works on freshly allocated vectors) before every element write; +the only weak point found is noted in section~\ref{sec:optimize:adapters}. + +%----------------------------------------------------------------------------------------------------------------------------- +\section{Problem interface and capabilities}\label{sec:optimize:problem} + +\subsection{OptProblem} + +\begin{lareference} +\lafunc{template class OptProblem}{Abstract base class of every optimization problem. A user problem derives from it +(and possibly from capability classes) and stores any auxiliary data needed to evaluate $f$.} +\lafunc{virtual T funcgrad(const NRVec &x, NRVec *grad) const =0}{Returns $f(x)$. If \code{grad!=NULL}, also stores +$\nabla f(x)$ in \code{*grad}. The gradient vector is allocated by the caller with \code{x.size()} elements, but it may share +its data with another vector, so the implementation must call \code{grad->copyonwrite()} before writing its elements (or +assign a whole new vector to \code{*grad}). Derivative-free methods always pass \code{NULL}; a problem not able to provide the +gradient may then simply ignore \code{grad} (or call \code{laerror} if it is not \code{NULL}) and be wrapped in +\code{FiniteDifference} or \code{ParameterShift} for gradient methods.} +\end{lareference} + +\subsection{Capabilities}\label{sec:optimize:capabilities} + +Further information about the problem is supplied by deriving the problem class also from one or more of the following +abstract classes (multiple inheritance). Methods look them up at run time by \code{optcapability(problem)}; a method +requiring a capability calls \code{laerror} at the start if it is missing, a method that can only use it optionally falls back +silently (with a verbose message in some cases). Matrices and vectors passed to the capability functions are allocated by the +caller with the right dimensions; implementations writing their elements have to call \code{copyonwrite()} first (or assign +whole objects, e.g.\ \code{h=(T)0;} followed by element writes, since assignment of a scalar is a modifying member function). + +\begin{lareference} +\lafunc{template class OptHessian}{Hessian of $f$. Used by \code{TrustRegion_Optimizer} and +\code{PRFO_Optimizer}.} +\lafunc{virtual void hessian(const NRVec &x, NRSMat &h) const =0}{Hessian at \code{x} into \code{h} ($n\times n$ +symmetric, allocated by the caller).} +\lafunc{virtual NRVec hessian_times(const NRVec &x, const NRVec &v) const}{Hessian-vector product $H(x)\,v$; the +default builds the full Hessian by \code{hessian()} and multiplies. It can be overridden for large problems. +Currently no method calls it (the Steihaug subproblem works with the stored matrix $B$; \code{FrozenOptProblem} forwards it); +it is kept for future Hessian-free methods.} + +\lafunc{template class OptMetric}{Metric tensor of the parameter space: the Fubini-Study metric for the quantum +natural gradient \cite{optimize:stokes2020} or the Fisher information for the natural gradient in machine +learning \cite{optimize:amari1998}. Used by \code{QNG_Optimizer} and by every first-order method with \code{my_natural=true}.} +\lafunc{virtual void metric(const NRVec &x, NRSMat &g) const =0}{Metric $G(x)$ into \code{g} ($n\times n$).} + +\lafunc{template class OptTrigonometric}{Declares that $f$ as a function of the single parameter $x_i$ is a +trigonometric polynomial $a_0+\sum_{k=1}^{R_i}\bigl(a_k\cos(k\omega_i x_i)+b_k\sin(k\omega_i x_i)\bigr)$, +$\omega_i=2\pi/\mathrm{period}_i$. For parameterized quantum circuits with gates $\exp(-\mathrm{i}x_iP/2)$, $P$ a Pauli string, +$R_i=1$ and the period is $2\pi$. Used by \code{Rotosolve_Optimizer} and \code{ParameterShift}.} +\lafunc{virtual int degree(const int i) const =0}{Degree $R_i$; 0 means that $f$ is not trigonometric in $x_i$ (such +parameters are treated by line minimization or central differences).} +\lafunc{virtual T period(const int i) const}{Period of $f$ in $x_i$; default $2\pi$.} + +\lafunc{template class OptConstraints}{Inequality constraints $c_i(x)\ge 0$, $i=0,\dots,m-1$, used by +\code{Cobyla_Optimizer}. An equality constraint is given as two inequalities $c(x)\ge0$, $-c(x)\ge0$. Other methods ignore +constraints.} +\lafunc{virtual int nconstraints() const =0}{Number $m$ of constraints.} +\lafunc{virtual void constraints(const NRVec &x, NRVec &c) const =0}{Values $c_i(x)$ into \code{c}, allocated by the +caller with \code{nconstraints()} elements.} + +\lafunc{template class OptResiduals}{Least-squares problem $f(x)=\frac12|r(x)|^2$ given by the residual vector +$r\in\mathbb{R}^m$ and optionally its Jacobian. Required by \code{LM_Optimizer}.} +\lafunc{virtual int nresiduals() const =0}{Number $m$ of residuals.} +\lafunc{virtual void residuals(const NRVec &x, NRVec &r) const =0}{Residuals into \code{r} (allocated by the caller with +\code{nresiduals()} elements).} +\lafunc{virtual bool jacobian(const NRVec &x, NRMat &J) const}{Jacobian $J_{ij}=\partial r_i/\partial x_j$ into +\code{J} ($m\times n$, allocated by the caller); returns \code{true} if provided. The default returns \code{false}, and the +callers then use forward differences.} +\end{lareference} + +\begin{lareference} +\lafunc{template const C *optcapability(const OptProblem *o)}{Returns the capability \code{C} +(e.g.\ \code{OptHessian}) of the problem, or \code{NULL}. It is found by \code{dynamic_cast} on the problem itself and, if +the problem is an \code{OptProblemAdapter}, recursively on the wrapped problem. For an adapter with +\code{transforms_capabilities()==true} the adapter's own implementation of \code{C} is returned only if the wrapped problem also +provides \code{C}, and the untransformed capability of the wrapped problem is never passed on. Methods always use this function +(not a plain \code{dynamic_cast}), so that capabilities remain available through adapters.} +\end{lareference} + +\subsection{Least-squares problems: OptLeastSquares} + +\begin{lareference} +\lafunc{template class OptLeastSquares : public OptProblem, public OptResiduals}{Convenience base class for +least-squares problems. The user derives from it and implements \code{nresiduals()}, \code{residuals()} and optionally +\code{jacobian()}. Its \code{funcgrad()} provides $f=\frac12|r|^2$ and $\nabla f=J\Tr r$, so that every method, not only +\code{LM_Optimizer}, can be applied to the problem.} +\lafunc{T fd_step}{Relative step $h$ of the forward-difference Jacobian used when \code{jacobian()} returns \code{false}.} +\lafunc{OptLeastSquares(const T h=1e-7)}{Sets \code{fd_step}.} +\lafunc{void jacobian_fd(const NRVec &x, const NRVec &r, NRMat &J) const}{Jacobian by forward differences, columns +$(r(x+h_je_j)-r(x))/h_j$ with $h_j=h\max(1,|x_j|)$; \code{r} are the residuals at \code{x} ($n$ further residual evaluations).} +\lafunc{virtual T funcgrad(const NRVec &x, NRVec *grad) const}{$\frac12|r(x)|^2$; with \code{grad}, the gradient $J\Tr r$ +(from \code{jacobian()} or \code{jacobian_fd()}).} +\end{lareference} +Note that \code{LM_Optimizer} does not call \code{funcgrad()}: it evaluates \code{residuals()} directly (counted as function +evaluations) and uses \code{LM_hyperparameters::my_fd_step}, not \code{fd_step}, for its own finite-difference Jacobian. + +\subsection{Adapters}\label{sec:optimize:adapters} + +An adapter is a problem wrapping another one, e.g.\ to supply gradients computed from function values or to optimize in a +subspace. Adapters can be nested. + +\begin{lareference} +\lafunc{template class OptProblemAdapter : public OptProblem}{Base class of adapters; \code{funcgrad()} is left to +the derived class.} +\lafunc{const OptProblem *p}{The wrapped problem (not owned).} +\lafunc{OptProblemAdapter(const OptProblem *pp)}{Wraps \code{pp}.} +\lafunc{virtual bool transforms_capabilities() const}{\code{false} (default): the capabilities of the wrapped problem are +accessible unchanged through \code{optcapability()}. \code{true}: the adapter provides them in a transformed form (e.g.\ in a +different dimension) by its own implementations, see \code{optcapability()}.} +\end{lareference} + +\paragraph{FiniteDifference.} +\begin{lareference} +\lafunc{template class FiniteDifference : public OptProblemAdapter}{Gradient by central differences +$(f(x+he_i)-f(x-he_i))/(2h)$, i.e.\ $2n$ evaluations of the wrapped problem besides $f(x)$. Makes all gradient methods usable +for a problem providing only $f$. Capabilities of the wrapped problem pass through.} +\lafunc{T h}{Difference step (absolute).} +\lafunc{FiniteDifference(const OptProblem *pp, const T hh=1e-6)}{Wraps \code{pp}.} +\lafunc{virtual T funcgrad(const NRVec &x, NRVec *grad) const}{$f$ of the wrapped problem; the gradient by central +differences if requested.} +\end{lareference} + +\paragraph{ParameterShift.} +\begin{lareference} +\lafunc{template class ParameterShift : public OptProblemAdapter}{Exact gradient of a problem providing +\code{OptTrigonometric} by the general parameter-shift rule \cite{optimize:wierichs2022} (for degree 1 the classical two-point +rule \cite{optimize:mitarai2018,optimize:schuld2019}): for $x_i$ of degree $R$, +$\partial_i f=\omega\sum_{\mu=1}^{2R}(-1)^{\mu-1}f(x+s_\mu e_i/\omega)/(4R\sin^2(s_\mu/2))$, $s_\mu=(2\mu-1)\pi/(2R)$, i.e.\ +$2R_i$ evaluations per parameter. Parameters of degree 0 are differentiated by central differences with step \code{h}. +Capabilities of the wrapped problem pass through.} +\lafunc{const OptTrigonometric *trig}{The trigonometric capability of the wrapped problem.} +\lafunc{T h}{Central-difference step for parameters of degree 0.} +\lafunc{ParameterShift(const OptProblem *pp, const T hh=1e-6)}{Wraps \code{pp}; \code{laerror} if it does not provide +\code{OptTrigonometric}.} +\lafunc{virtual T funcgrad(const NRVec &x, NRVec *grad) const}{$f$ of the wrapped problem and the parameter-shift +gradient.} +\end{lareference} + +\paragraph{FrozenOptProblem.} +Optimization in a subset of the parameters, the others (``frozen'') keeping their values. +\begin{lareference} +\lafunc{template class FrozenOptProblem : public OptProblemAdapter, public OptHessian, public OptMetric, +public OptTrigonometric, public OptConstraints}{Problem in the $n_{\rm free}$ free parameters. Its +\code{transforms_capabilities()} returns \code{true}: the Hessian and metric (submatrices for the free parameters), the +Hessian-vector product, degree and period (of the free parameters) and the constraints are provided if and only if the wrapped +problem provides them. \code{OptResiduals} is not forwarded, so \code{LM_Optimizer} cannot be used through it (the other +methods can, via \code{funcgrad()} of an \code{OptLeastSquares} problem).} +\lafunc{NRVec xfull}{Full parameter vector; the values of the frozen parameters are taken from it.} +\lafunc{NRVec freeindex}{Indices of the free parameters in the full vector (ascending).} +\lafunc{FrozenOptProblem(const OptProblem *pp, const NRVec &x, const bitvector &frozen)}{Wraps \code{pp}; \code{xfull} +shares its data with \code{x} (shallow copy); \code{frozen} has bit $i$ set if $x_i$ is frozen.} +\lafunc{void setfrozen(const bitvector &frozen)}{New set of frozen parameters (\code{laerror} if +\code{frozen.size()!=xfull.size()}).} +\lafunc{void setfull(const NRVec &x)}{New full vector (new values of the frozen parameters); shallow copy.} +\lafunc{NRVec reduce(const NRVec &full) const}{The free parameters of a full vector, \code{full.subvector(freeindex)}.} +\lafunc{NRVec expand(const NRVec &reduced) const}{Full vector: a copy of \code{xfull} with the free parameters replaced by +\code{reduced}.} +\lafunc{virtual T funcgrad(const NRVec &x, NRVec *grad) const}{$f$ at \code{expand(x)}; the gradient is the free part of +the full gradient.} +\lafunc{virtual void hessian(const NRVec &x, NRSMat &h) const; virtual NRVec hessian_times(const NRVec &x, const NRVec &v) const}{Free-free block of the wrapped Hessian; product with the free block.} +\lafunc{virtual void metric(const NRVec &x, NRSMat &g) const}{Free-free block of the wrapped metric.} +\lafunc{virtual int degree(const int i) const; virtual T period(const int i) const}{Of the free parameter $i$, i.e.\ of +\code{freeindex[i]} of the wrapped problem.} +\lafunc{virtual int nconstraints() const; virtual void constraints(const NRVec &x, NRVec &c) const}{Constraints of the +wrapped problem at \code{expand(x)}.} +\end{lareference} +Usage: \code{FrozenOptProblem fp(&problem,x,frozen); NRVec xr=fp.reduce(x); optimizer.minimize(&fp,xr); x=fp.expand(xr);} +(see example~\ref{sec:optimize:ex:frozen}). + +%----------------------------------------------------------------------------------------------------------------------------- +\section{Optimizers, hyperparameters and results}\label{sec:optimize:hyper} + +\subsection{Optimizer and optresult} + +\begin{lareference} +\lafunc{template class Optimizer}{Abstract base class of all methods; the methods can be used through +\code{Optimizer *} or \code{&}. Each method class (\code{BFGS_Optimizer} etc.) has no data members and a virtual destructor.} +\lafunc{virtual optresult minimize(const OptProblem *o, NRVec &x, const hyperparameters *par=NULL, hyperparameters *achieved=NULL) =0}{Minimizes +\code{o} (for P-RFO: searches a stationary point of the requested order) starting from \code{x}, which is replaced by the +result. \code{par==NULL} means the default parameters of the method; otherwise the parameters are taken from \code{*par} as +described in section~\ref{sec:optimize:setfrom}. If \code{achieved!=NULL}, it is filled with the parameters used and with the +values actually reached (section~\ref{sec:optimize:achieved}).} +\lafunc{template class optresult}{Result of \code{minimize()}.} +\lafunc{T f; bool converged}{Final function value (for SPSA an estimate); whether a convergence criterion was satisfied +(\code{false} if the iterations stopped on \code{my_maxstep}, \code{my_maxfunc} or a failure).} +\lafunc{optresult(const T ff, const bool c); operator T() const}{Constructor; implicit conversion to \code{f}, so +\code{double f=o.minimize(&p,x);} works.} +\end{lareference} + +\subsection{Hyperparameter hierarchy} + +The hyperparameters form a class hierarchy; each method has its own class adding its specific parameters to those of the +general classes. All members are public, named \code{my_...}, and initialized to the method's defaults by the constructor. The +hierarchy (defaults in parentheses; a derived class may change inherited defaults, given at the method): + +\begin{lstlisting} +hyperparameters maxstep(200) maxfunc(0) f_thr(0) x_thr(0) saddle_order(0) verbose(1) + +- gradient_hyperparameters g_thr(1e-6) + | +- linesearch_hyperparameters c1(1e-4) c2(0.9) maxlinesearch(30) maxsteplength(1e10) + | | +- quasinewton_hyperparameters sy_thr(1e-12) + | | | +- BFGS_hyperparameters update(QN_BFGS) sr1_thr(1e-8) + | | | | +- GDIIS_hyperparameters diis_dim(5) diis_eps(0) + | | | +- LBFGS_hyperparameters memory(10) + | | +- CG_hyperparameters beta(CG_PR) restart(0) powell_thr(0.2); c2=0.1 + | +- firstorder_hyperparameters lr(1e-3) lr_decay(0) lr_power(1) weight_decay(0) return_best(false) + | | | natural(false) metric_reg(1e-3); maxstep=1000 + | | +- SGD_hyperparameters momentum(0.9) nesterov(false); lr=1e-2 + | | | +- QNG_hyperparameters momentum=0 natural=true lr=0.05 + | | +- Adam_hyperparameters beta1(0.9) beta2(0.999) eps(1e-8) amsgrad(false) + | | +- SPSA_hyperparameters c(0.1) gamma(0.101) resamplings(1) calibrate(20) target_step(0.1); + | | lr_decay=0.01 lr_power=0.602 g_thr=0 + | +- trustregion_hyperparameters delta0(1) deltamax(1e3) eta(1e-4) hessian(TR_AUTO) hessian_recompute(1) + | | | update_thr(1e-8) + | | +- TrustRegion_hyperparameters subproblem(TR_EIGEN) maxcg(0) + | | +- PRFO_hyperparameters delta0=0.3 + | +- LM_hyperparameters tau(1e-3) scaling(false) fd_step(1e-7) + +- NelderMead_hyperparameters initstep(0.1) adaptive(true) alpha(1) gamma(2) rho(0.5) sigma(0.5); + | maxstep=5000 f_thr=1e-8 x_thr=1e-6 + +- Powell_hyperparameters initstep(0.1) linmin_tol(1e-4) maxlinemin(50); maxstep=1000 f_thr=1e-10 x_thr=1e-6 + | +- Rotosolve_hyperparameters reevaluate(false) + +- Cobyla_hyperparameters rhobeg(0.5) rhoend(1e-6) flexible_radius(true); maxstep=100000 +\end{lstlisting} + +\paragraph{Base classes.} +\begin{lareference} +\lafunc{template class hyperparameters}{Parameters common to all methods.} +\lafunc{int my_maxstep}{Maximum number of iterations (default 200). What an iteration is depends on the method (a line search +step, a sweep over all directions or parameters, a simplex operation, a trust-region step including rejected ones).} +\lafunc{int my_maxfunc}{Maximum number of function evaluations, $\le0$ = unlimited (default 0). It counts every call of +\code{funcgrad()} by the method (with or without gradient), and for LM every residual evaluation, including those of +finite-difference Jacobians. Evaluations inside adapters (finite differences, parameter shifts) are not counted. Methods check +before each iteration that the budget suffices and never exceed it, except for the unavoidable initial evaluations (the +starting point, the initial simplex of Nelder-Mead and COBYLA, the first SPSA estimate).} +\lafunc{T my_f_thr; T my_x_thr}{Thresholds on $|\Delta f|$ and $|\Delta x|$ (Euclidean norm) in the last step (default 0). +The general criterion is $|\Delta f|<$ \code{my_f_thr} \emph{and} $|\Delta x|<$ \code{my_x_thr}, both strict, so with the +default 0 it is switched off, and setting only one of them has no effect. Some methods reinterpret them (Nelder-Mead, Powell, +Rotosolve, COBYLA, see there).} +\lafunc{int my_saddle_order}{0 = minimum (default), $k>0$ = saddle point with $k$ negative Hessian eigenvalues. Only +\code{PRFO_Optimizer} accepts $k>0$; all other methods call \code{laerror} if it is nonzero. For the trust-region methods it +returns the Hessian index in \code{achieved}.} +\lafunc{int my_verbose}{0 = silent, 1 = one line per iteration and a final report (default), 2 = more details (line searches, +step lengths, trust-region radii, restarts). Output goes to \code{std::cout}.} +\lafunc{hyperparameters()}{Defaults as above.} +\lafunc{virtual void setfrom(const hyperparameters &p)}{Copies the parameters from \code{p} which exist in both classes +(section~\ref{sec:optimize:setfrom}).} +\lafunc{virtual bool converged(const optstate &s) const}{Convergence test: \code{s.stepdone && s.df &s)}{Stores the reached values: \code{my_maxstep} = iterations done, +\code{my_maxfunc} = evaluations done, \code{my_f_thr}, \code{my_x_thr} = $|\Delta f|$, $|\Delta x|$ of the last step.} + +\lafunc{template class gradient_hyperparameters : public hyperparameters}{Methods using the gradient.} +\lafunc{T my_g_thr}{Convergence threshold on the gradient norm (default \code{1e-6}). \code{converged()} is +$|g|<$ \code{my_g_thr} \emph{or} the criterion of the base class; \code{setreached()} stores the last $|g|$ (or $-1$ if the +gradient norm is not available, as for SPSA).} + +\lafunc{template class linesearch_hyperparameters : public gradient_hyperparameters}{Methods with the strong +Wolfe line search.} +\lafunc{T my_c1; T my_c2}{Sufficient decrease (Armijo) and curvature parameters of the strong Wolfe conditions, +$0 class firstorder_hyperparameters : public gradient_hyperparameters}{First-order methods with a +learning rate instead of a line search, suitable also for noisy functions and gradients. Default \code{my_maxstep=1000}.} +\lafunc{T my_lr; T my_lr_decay; T my_lr_power}{Learning rate in iteration $k=0,1,\dots$ is +$\mathrm{lr}/(1+\mathrm{lr\_decay}\cdot k)^{\mathrm{lr\_power}}$ (defaults \code{1e-3}, 0, 1).} +\lafunc{T my_weight_decay}{Decoupled weight decay $x\mathrel{-}=\mathrm{lr}\cdot\mathrm{weight\_decay}\cdot x$ +\cite{optimize:loshchilov2019} (default 0).} +\lafunc{bool my_return_best}{Return the point with the lowest function value seen instead of the last one (default +\code{false}); only meaningful for (nearly) deterministic $f$, the lowest of noisy values is biased.} +\lafunc{bool my_natural}{Use the natural gradient $(G+\mathrm{metric\_reg}\,I)^{-1}g$ with the metric $G$ of the problem +(\code{OptMetric} required) \cite{optimize:amari1998,optimize:stokes2020}; $|g|$ in the convergence test and in the output then +refers to the natural gradient (default \code{false}).} +\lafunc{T my_metric_reg}{Regularization added to the diagonal of the metric (\code{1e-3}).} + +\lafunc{template class trustregion_hyperparameters : public gradient_hyperparameters}{Trust-region methods, +see section~\ref{sec:optimize:tr}.} +\end{lareference} + +The method-specific classes are described with their methods in section~\ref{sec:optimize:methods}. Each of them has a default +constructor and overrides \code{setfrom()}. + +\subsection{How parameters are taken: setfrom()}\label{sec:optimize:setfrom} + +Every \code{minimize()} constructs the hyperparameter object of its own method (with the method's defaults) and calls +\code{hp.setfrom(*par)}. Each class's \code{setfrom()} tries \code{dynamic_cast} of \code{par} to its own type; on success it +assigns \code{*this=*q} at its own level, otherwise it delegates to its parent class. The effect is that all fields of the +deepest class common to the method's class and the dynamic type of \code{*par} are copied from \code{*par}, while the +method-specific fields below it keep the method's defaults. Thus one may pass +\begin{itemize} +\item the method's own class (all parameters given), +\item a more general class, e.g.\ \code{gradient_hyperparameters} to any gradient method, or \code{hyperparameters} +to any method, +\item the class of another method: e.g.\ \code{CG_hyperparameters} passed to \code{BFGS_Optimizer} provides the line search +parameters (including CG's default \code{my_c2=0.1}). +\end{itemize} +\lawarning{All fields of the common level are copied, including those the caller left at the defaults \emph{of that level}, which +may differ from the defaults of the method. For example, a plain \code{hyperparameters} object passed to +\code{NelderMead_Optimizer} replaces its \code{my_maxstep=5000}, \code{my_f_thr=1e-8}, \code{my_x_thr=1e-6} by 200, 0, 0 (no +convergence test); \code{gradient_hyperparameters} passed to Adam replaces \code{my_maxstep=1000} by 200; +\code{SGD_hyperparameters} passed to QNG brings \code{my_momentum=0.9}. Set those fields explicitly, or use the method's own +class.} + +A derived hyperparameter class written for a new method must override \code{setfrom()} in the same way (one line, see the +header), and \code{converged()}, \code{setreached()} if it adds parameters relevant for them. + +\subsection{The achieved values}\label{sec:optimize:achieved} + +If \code{achieved!=NULL}, at the end \code{achieved->setfrom(hp)} copies the parameters used (at the common level of the two +classes, as above) and \code{achieved->setreached()} overwrites the limits and thresholds with the values reached: +\begin{itemize} +\item \code{my_maxstep}: number of iterations done; \code{my_maxfunc}: number of function evaluations done; +\item \code{my_f_thr}, \code{my_x_thr}: $|\Delta f|$ and $|\Delta x|$ of the last step (0 if no step was done); for +Nelder-Mead the spread of $f$ and the diameter of the simplex, for COBYLA the change of $f$ and the radius $\rho$; +\item \code{my_g_thr} (if \code{achieved} is at least a \code{gradient_hyperparameters}): the last gradient norm +($-1$ if not available); +\item \code{my_saddle_order} (trust-region methods only): the number of negative eigenvalues of the Hessian at the result (of the +exact one if the problem provides \code{OptHessian}, otherwise of the approximation); +\item \code{my_lr} (first-order methods): the initial learning rate actually used (after the SPSA calibration), if +\code{achieved} is at least of the class \code{firstorder_hyperparameters}; +\item the other fields: the parameters as they were used. +\end{itemize} +The type of \code{achieved} determines what is returned: an object of the class \code{hyperparameters} suffices for +the counts, the class \code{gradient_hyperparameters} is needed to obtain $|g|$. + +%----------------------------------------------------------------------------------------------------------------------------- +\section{Infrastructure and common drivers}\label{sec:optimize:drivers} + +This section is needed only for writing new methods; the classes are public so that methods can be added outside the library. + +\subsection{Evaluation and iteration bookkeeping} + +\begin{lareference} +\lafunc{template class optevaluator}{Wraps the problem: counts evaluations and keeps the best point seen.} +\lafunc{const OptProblem *o; int maxfunc; int nfunc; T fbest; NRVec xbest}{The problem, the evaluation budget ($\le0$ +unlimited), the number of evaluations done, the lowest $f$ found and its point.} +\lafunc{optevaluator(const OptProblem *oo, const int maxf=0)}{Constructor.} +\lafunc{T operator()(const NRVec &x, NRVec *grad=NULL)}{\code{o->funcgrad(x,grad)}, counted; if $f$ is the lowest so far, +\code{xbest=x} (a shallow copy sharing the data of \code{x}, see section~\ref{sec:optimize:pitfalls}).} +\lafunc{T operator()(const OptResiduals *res, const NRVec &x, NRVec &r)}{Residuals at \code{x} into \code{r}, returns +$\frac12|r|^2$; counted and recorded as above.} +\lafunc{bool exhausted() const; int remaining() const}{Budget used up; number of evaluations left +(\code{std::numeric_limits::max()} if unlimited).} + +\lafunc{template class optstate}{Iteration bookkeeping common to all methods: convergence and limit tests, verbose +output, result.} +\lafunc{const char *name; const hyperparameters &hp; const optevaluator &e}{Method name for the output, the parameters +used (their virtual \code{converged()} is called), the evaluator.} +\lafunc{int iter; T f; T gnorm; T df; T dx; bool stepdone; bool converged}{Iterations done; current $f$; current $|g|$ ($<0$ if +not available); $|\Delta f|$, $|\Delta x|$ of the last step; whether \code{df}, \code{dx} are valid; whether converged.} +\lafunc{optstate(const char *nm, const hyperparameters &h, const optevaluator &ee)}{Constructor.} +\lafunc{bool stop(const T ff, const T gn= -1, const int needed=1)}{To be called at the beginning of each iteration with the +current $f$ and $|g|$: prints the iteration line (verbose $\ge1$) and returns \code{true} if converged (\code{hp.converged()}), +if \code{iter>=my_maxstep}, or if fewer than \code{needed} evaluations (those of the next iteration) remain.} +\lafunc{void step(const T dff, const T dxx)}{Records a step: $|\Delta f|$, $|\Delta x|$, \code{stepdone=true}.} +\lafunc{optresult finish(hyperparameters *achieved) const}{Final report (verbose $\ge1$), fills \code{achieved} if not +\code{NULL}, returns the result.} +\end{lareference} + +\subsection{One-dimensional searches} + +\begin{lareference} +\lafunc{template T linesearch_wolfe(optevaluator &e, NRVec &x, T &f, NRVec &g, const NRVec &p, const T alpha1, const linesearch_hyperparameters &lp)}{Line +search along the descent direction \code{p} satisfying the strong Wolfe conditions (bracketing and zoom with safeguarded cubic +interpolation, \cite{optimize:nocedal2006} alg.~3.5, 3.6). On input \code{x}, \code{f}, \code{g} at the starting point, +\code{alpha1} the initial trial step; on success they are replaced by the new point and the step length $\alpha$ is returned. +If the strong Wolfe conditions cannot be met within \code{my_maxlinesearch} evaluations (or the remaining budget), the best point +satisfying sufficient decrease is accepted; 0 is returned on failure (also if \code{p} is not a descent direction), \code{x}, +\code{f}, \code{g} then unchanged. The step is limited to $|\alpha p|\le$ \code{my_maxsteplength}.} +\lafunc{template T linemin_brent(optevaluator &e, NRVec &x, T &f, const NRVec &u, const T t1, const T tol, const T abstol, const int maxiter, const int verbose=0)}{Derivative-free +minimization of $f(x+tu)$: bracketing starting with the trial step \code{t1} (the opposite direction is tried if uphill), +then Brent's method \cite{optimize:brent1973} with relative and absolute tolerances \code{tol}, \code{abstol} on $t$, at most +\code{maxiter} evaluations (and the remaining budget). On output \code{x}, \code{f} at the best point found; returns $t$, or 0 +if no decrease was found (\code{x}, \code{f} unchanged).} +\end{lareference} + +\subsection{Line-search methods} + +\begin{lareference} +\lafunc{template class linesearch_direction}{Search direction of a line-search method, abstract.} +\lafunc{virtual NRVec direction(const NRVec &g) =0}{Search direction at the point with gradient \code{g}.} +\lafunc{virtual bool update(const NRVec &s, const NRVec &y, const NRVec &p, const NRVec &g) =0}{After a step +$s=\alpha p$ with gradient change $y$ and new gradient $g$; returns \code{false} if the update was skipped (only reported).} +\lafunc{virtual void reset() =0}{Forget the accumulated information (restart).} +\lafunc{virtual bool steepest() const =0}{No information accumulated, \code{direction()} gives steepest descent.} +\lafunc{virtual bool indefinite() const}{\code{true}: a non-descent direction is legitimate (SR1); the driver then takes a +steepest-descent step without reset. Default \code{false}.} +\lafunc{virtual bool alpha_from_previous() const}{\code{true}: initial trial step $\alpha_{k-1}\,g_{k-1}\cdot p_{k-1}/(g_k\cdot p_k)$ +(\cite{optimize:nocedal2006} eq.~3.60, used by CG); otherwise 1 (the quasi-Newton step), or a unit step in $x$ for steepest +descent. Default \code{false}.} +\lafunc{template optresult linesearch_minimize(const char *name, linesearch_direction &dir, const OptProblem *o, NRVec &x, const linesearch_hyperparameters &hp, hyperparameters *achieved)}{Common +driver: in each iteration \code{p=dir.direction(g)}; if $p\cdot g\ge0$, steepest descent (with \code{dir.reset()} unless +\code{indefinite()}); \code{linesearch_wolfe()}; on its failure a restart, or the end if it failed along steepest descent; +then \code{dir.update()} and the step is recorded. One evaluation per line search trial point.} +\end{lareference} + +\subsection{First-order methods} + +\begin{lareference} +\lafunc{template class firstorder_step}{Update rule of a first-order method, abstract.} +\lafunc{virtual NRVec step(const NRVec &g, const int k) =0}{Direction $d$ of the step $x\mathrel{-}=\mathrm{lr}\,d$ in +iteration $k=1,2,\dots$ with gradient \code{g}. The gradient vector passed is freshly allocated in each iteration, so the stepper +may keep it.} +\lafunc{template class gradient_estimator}{Supplies $f$ and the gradient to the driver; the default implementation +is the exact evaluation, derived classes estimate them (SPSA).} +\lafunc{virtual T operator()(optevaluator &e, const NRVec &x, NRVec &g, const int k)}{Returns $f$ at \code{x} and the +gradient in \code{g}; \code{k=0,1,...} is the iteration.} +\lafunc{virtual int cost() const}{Number of evaluations of one call (default 1).} +\lafunc{virtual bool exact() const}{Default \code{true}. With estimates (\code{false}) no convergence test is done, since small +gradients or changes can occur by chance, and the iterations run until \code{my_maxstep} or \code{my_maxfunc}.} +\lafunc{virtual T calibrate(optevaluator &e, const NRVec &x, const firstorder_hyperparameters &hp)}{Learning rate to be +used; default \code{hp.my_lr}.} +\lafunc{template optresult firstorder_minimize(const char *name, firstorder_step &stepper, const OptProblem *o, NRVec &x, const firstorder_hyperparameters &hp, hyperparameters *achieved, gradient_estimator *est=NULL)}{Common +driver: $x\leftarrow x-\mathrm{lr}_k\,d-\mathrm{lr}_k\,\mathrm{weight\_decay}\,x$ with $d$ from the stepper, \code{est==NULL} +meaning exact gradients. With \code{my_natural} the gradient of the estimator is replaced by the natural gradient. Implements +\code{my_return_best}, and returns the learning rate used in \code{achieved} if it is a \code{firstorder_hyperparameters}.} +\end{lareference} + +\subsection{Trust-region methods}\label{sec:optimize:tr} + +The trust-region methods work with the quadratic model $f+g\cdot p+\frac12 p\cdot Bp$ within $|p|\le\Delta$ +\cite{optimize:nocedal2006,optimize:conn2000}. The matrix $B$ is the exact Hessian (\code{OptHessian}) or an approximation +updated by a secant formula. + +\begin{lareference} +\lafunc{enum TR_hessian {TR_AUTO, TR_EXACT, TR_BFGS, TR_SR1, TR_PSB, TR_BOFILL}}{Choice of $B$. +\code{TR_EXACT}: exact Hessian (\code{laerror} if the problem does not provide it). \code{TR_AUTO}: exact if available, +otherwise BFGS for minima and Bofill for saddle points. \code{TR_BFGS}, \code{TR_SR1}, \code{TR_PSB}, \code{TR_BOFILL}: updated +by the BFGS, SR1, Powell-symmetric-Broyden or Bofill \cite{optimize:bofill1994} formula starting from the identity, scaled after +the first step by $y\cdot y/s\cdot y$. Between exact Hessians (\code{my_hessian_recompute>1}) the updates are SR1 for minima (the +exact Hessian may be indefinite) and Bofill for saddle points. BFGS is refused for saddle points.} +\lafunc{template bool hessian_update(NRSMat &B, const NRVec &s, const NRVec &y, const TR_hessian type, const T thr)}{Update +of a Hessian approximation $B$ (not of its inverse) by the step $s$ and gradient change $y$, $\xi=y-Bs$: +BFGS $B+yy\Tr/(y\cdot s)-Bs(Bs)\Tr/(s\cdot Bs)$ (skipped if $s\cdot y\le\mathrm{thr}\,|s||y|$ or $s\cdot Bs\le0$); +SR1 $B+\xi\xi\Tr/(\xi\cdot s)$ (skipped if $|\xi\cdot s|\le\mathrm{thr}\,|\xi||s|$); +PSB $B+(\xi s\Tr+s\xi\Tr)/(s\cdot s)-(\xi\cdot s)ss\Tr/(s\cdot s)^2$; +Bofill $\varphi\,$SR1$+(1-\varphi)\,$PSB with $\varphi=(\xi\cdot s)^2/(|\xi|^2|s|^2)$ (skipped if $\xi=0$). +Returns \code{false} if skipped. Only \code{TR_BFGS}, \code{TR_SR1}, \code{TR_PSB}, \code{TR_BOFILL} are valid.} + +\lafunc{T my_delta0; T my_deltamax}{(\code{trustregion_hyperparameters}) Initial (1) and maximal (\code{1e3}) trust-region +radius.} +\lafunc{T my_eta}{A minimization step is accepted if (actual reduction)/(predicted reduction) $>\eta$ (\code{1e-4}).} +\lafunc{TR_hessian my_hessian}{See above (\code{TR_AUTO}).} +\lafunc{int my_hessian_recompute}{With an exact Hessian: recompute it every $k$-th accepted step and update in between; 1 = +always exact (default).} +\lafunc{T my_update_thr}{Threshold for skipping the updates (\code{thr} of \code{hessian_update()}, \code{1e-8}).} + +\lafunc{template class trustregion_step}{Step of a trust-region method, abstract.} +\lafunc{virtual NRVec step(const NRVec &g, const NRSMat &B, const T delta) =0}{Step within the trust region.} +\lafunc{virtual bool saddle() const}{Searching for a saddle point: steps are always taken and the radius is controlled by the +quality of the model only. Default \code{false}.} +\lafunc{template optresult trustregion_minimize(const char *name, trustregion_step &stepper, const OptProblem *o, NRVec &x, const trustregion_hyperparameters &hp, hyperparameters *achieved)}{Common +driver. For minimization: radius control by \cite{optimize:nocedal2006} alg.~4.1 (ratio $<0.25$: $\Delta=|p|/4$; ratio $>0.75$ +and step on the boundary: $\Delta=\min(2\Delta,\Delta_{\max})$); if the predicted change is below the rounding errors of $f$, +the step is judged by the decrease of $|g|$. For saddle points: every step is taken; ratio outside $[0.25,1.75]$ gives +$\Delta=|p|/4$, ratio in $(0.75,1.25)$ with a boundary step doubles $\Delta$. One evaluation (with gradient) per iteration; +rejected steps count as iterations. At the end the Hessian (exact if available, otherwise $B$) is diagonalized and the number of +its negative eigenvalues is printed and returned in \code{achieved->my_saddle_order}.} +\end{lareference} + +\subsection{Writing a new method} + +A new line-search method needs a \code{linesearch_direction} and an \code{Optimizer} whose \code{minimize()} sets up the +hyperparameters and calls the driver; the same pattern applies to \code{firstorder_step} with the driver +\code{firstorder_minimize()} and to \code{trustregion_step} with the driver \code{trustregion_minimize()}. A complete (compile-tested) example, steepest descent with the +strong Wolfe line search: +\begin{lstlisting} +template +class SD_direction : public linesearch_direction { +public: +NRVec direction(const NRVec &g) {NRVec p(g); p*= (T)-1; return p;} +bool update(const NRVec &s, const NRVec &y, const NRVec &p, const NRVec &g) {return true;} +void reset() {} +bool steepest() const {return true;} +}; + +template +class SD_Optimizer : public Optimizer { +public: +optresult minimize(const OptProblem *o, NRVec &x, const hyperparameters *par=NULL, hyperparameters *achieved=NULL) + { + linesearch_hyperparameters hp; //a derived class would set the method's defaults + if(par) hp.setfrom(*par); + if(hp.my_saddle_order) laerror("SD cannot search for saddle points"); + SD_direction dir; + return linesearch_minimize("SD",dir,o,x,hp,achieved); + } +}; +\end{lstlisting} +Methods with their own iteration loop use \code{optevaluator} for all evaluations and \code{optstate}: \code{stop()} at the +beginning of each iteration, \code{step()} after each step, \code{finish(achieved)} at the end. + +%----------------------------------------------------------------------------------------------------------------------------- +\section{Methods}\label{sec:optimize:methods} + +Table~\ref{tab:optimize:methods} summarizes the methods. ``Gradient'' means that \code{funcgrad()} is called with a gradient +request; such methods can be applied to an $f$-only problem through \code{FiniteDifference} or \code{ParameterShift}. +``Optional'' capabilities are used if present. + +\begin{table}[htbp] +\centering\footnotesize +\caption{Methods, their requirements and properties.}\label{tab:optimize:methods} +\begin{tabular}{@{}>{\raggedright}p{0.25\textwidth}>{\raggedright}p{0.11\textwidth}>{\raggedright}p{0.24\textwidth}>{\raggedright}p{0.14\textwidth}>{\raggedright\arraybackslash}p{0.12\textwidth}@{}} +\toprule +Class & Deriva\-tives & Capabilities & Constraints, saddles & Noisy $f$ \\ +\midrule +\texttt{BFGS\_Optimizer} (BFGS, DFP, SR1) & gradient & -- & no & no \\ +\texttt{LBFGS\_Optimizer} & gradient & -- & no & no \\ +\texttt{CG\_Optimizer} & gradient & -- & no & no \\ +\texttt{GDIIS\_Optimizer} & gradient & -- & no & no \\ +\texttt{TrustRegion\_\-Optimizer} & gradient & \texttt{OptHessian} optional (required for \texttt{TR\_EXACT}) & no & no \\ +\texttt{PRFO\_Optimizer} & gradient & \texttt{OptHessian} optional & saddles of any order & no \\ +\texttt{LM\_Optimizer} & Jacobian optional & \texttt{OptResiduals} required & no & no \\ +\texttt{NelderMead\_\-Optimizer} & none & -- & no & partly \\ +\texttt{Powell\_Optimizer} & none & -- & no & no \\ +\texttt{Rotosolve\_\-Optimizer} & none & \texttt{OptTrigono\-metric} optional & no & with \texttt{my\_re\-evaluate} \\ +\texttt{Cobyla\_Optimizer} & none & \texttt{OptConstraints} optional & inequality constraints & no \\ +\texttt{SGD\_Optimizer} & gradient & \texttt{OptMetric} if \texttt{my\_natural} & no & yes \\ +\texttt{Adam\_Optimizer} & gradient & \texttt{OptMetric} if \texttt{my\_natural} & no & yes \\ +\texttt{QNG\_Optimizer} & gradient & \texttt{OptMetric} required & no & yes \\ +\texttt{SPSA\_Optimizer} & none & \texttt{OptMetric} if \texttt{my\_natural} & no & yes \\ +\bottomrule +\end{tabular} +\end{table} + +Every \code{XXX_Optimizer} class below has only the member \code{minimize()} with the signature of +\code{Optimizer::minimize()} and a virtual destructor; only the hyperparameters are listed. All methods except P-RFO call \code{laerror} if +\code{my_saddle_order!=0}. + +\subsection{Quasi-Newton methods: BFGS, DFP, SR1} + +\code{BFGS_Optimizer} keeps an explicit approximation $H$ of the inverse Hessian, starting from the identity scaled after the +first step by $s\cdot y/y\cdot y$ (\cite{optimize:nocedal2006} eq.~6.20), and uses the strong Wolfe line search with the initial +trial step 1 (the quasi-Newton step). Memory and work are $O(n^2)$ per iteration; for large $n$ use L-BFGS. + +\begin{lareference} +\lafunc{template class quasinewton_hyperparameters : public linesearch_hyperparameters}{} +\lafunc{T my_sy_thr}{Skip the update if $s\cdot y\le\mathrm{sy\_thr}\,|s||y|$ (curvature condition violated numerically; +\code{1e-12}). Also used by L-BFGS.} +\lafunc{enum QN_update {QN_BFGS, QN_DFP, QN_SR1}}{Update formula of $H$ (\cite{optimize:nocedal2006} eqs.~6.17, 6.15, 6.25).} +\lafunc{template class BFGS_hyperparameters : public quasinewton_hyperparameters}{} +\lafunc{QN_update my_update}{\code{QN_BFGS} (default), \code{QN_DFP} (needs an accurate line search, \code{my_c2}$\approx0.1$, +otherwise it can stall), \code{QN_SR1} ($H$ may become indefinite; a non-descent direction then leads to a steepest descent step +without reset). The verbose name is BFGS, DFP or SR1.} +\lafunc{T my_sr1_thr}{SR1: skip the update if $|(s-Hy)\cdot y|\le\mathrm{sr1\_thr}\,|s-Hy||y|$ (\cite{optimize:nocedal2006} +eq.~6.26; \code{1e-8}).} +\lafunc{template class BFGS_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{L-BFGS} + +Limited-memory BFGS (\cite{optimize:nocedal2006} sec.~7.2): the last \code{my_memory} pairs $(s,y)$ in a ring buffer, the +direction by the two-loop recursion (alg.~7.4) with $H_0=\gamma I$, $\gamma=s\cdot y/y\cdot y$ of the newest pair. Memory +$O(mn)$. Pairs violating the curvature condition (\code{my_sy_thr}) are skipped. +\begin{lareference} +\lafunc{template class LBFGS_hyperparameters : public quasinewton_hyperparameters}{} +\lafunc{int my_memory}{Number of stored pairs (10; \code{laerror} if $<1$).} +\lafunc{template class LBFGS_Optimizer : public Optimizer}{Verbose name L-BFGS.} +\end{lareference} + +\subsection{Nonlinear conjugate gradients} + +$p_{k+1}=-g_{k+1}+\beta p_k$ (\cite{optimize:nocedal2006} sec.~5.2, \cite{optimize:hager2005}), with the initial trial step of +the line search from the previous step. Restarts with steepest descent every \code{my_restart} iterations, by Powell's test, +after a failed line search, and if the curvature $p\cdot y\le0$ for the formulas dividing by it. +\begin{lareference} +\lafunc{enum CG_beta {CG_FR, CG_PR, CG_HS, CG_DY, CG_HZ}}{Formula for $\beta$ ($y=g_{k+1}-g_k$): +Fletcher-Reeves $|g_{k+1}|^2/|g_k|^2$; Polak-Ribi\`ere+ $\max(0,g_{k+1}\cdot y/|g_k|^2)$; +Hestenes-Stiefel+ $\max(0,g_{k+1}\cdot y/p_k\cdot y)$; Dai-Yuan $|g_{k+1}|^2/p_k\cdot y$; Hager-Zhang +$\max\bigl(\beta^{\rm HZ},-1/(|p_k|\min(0.01,|g_k|))\bigr)$, $\beta^{\rm HZ}=(g_{k+1}\cdot y-2(p_k\cdot g_{k+1})|y|^2/p_k\cdot y)/p_k\cdot y$. +Verbose names CG-FR, CG-PR+, CG-HS+, CG-DY, CG-HZ.} +\lafunc{template class CG_hyperparameters : public linesearch_hyperparameters}{Default \code{my_c2=0.1} +(CG needs a more accurate line search).} +\lafunc{CG_beta my_beta}{\code{CG_PR} (default), see above.} +\lafunc{int my_restart}{Restart every \code{my_restart} iterations, $\le0$ = the dimension $n$ (default 0).} +\lafunc{T my_powell_thr}{Restart if $|g_{k+1}\cdot g_k|\ge\mathrm{powell\_thr}\,|g_{k+1}|^2$ (\cite{optimize:nocedal2006} +eq.~5.52), $\le0$ disables (0.2).} +\lafunc{template class CG_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{GDIIS} + +Geometry DIIS \cite{optimize:csaszar1984} with a quasi-Newton Hessian \cite{optimize:farkas2002}. The error vectors are the +quasi-Newton steps $e_i=-Hg_i$; the new point is $\sum_ic_i(x_i+e_i)$ with $c$ minimizing $|\sum_ic_ie_i|$ subject to +$\sum_ic_i=1$ (the \code{DIIS} class of diis.h, chapter~\ref{ch:iterative}). One evaluation per iteration. If the DIIS step goes +uphill ($s\cdot g\ge0$), is longer than \code{my_maxsteplength}, or does not decrease $f$, the DIIS history is reset and a +strong Wolfe line search along the quasi-Newton step is done; the first step after a (re)start is always a line search. $H$ is +updated as in \code{BFGS_Optimizer} (all its parameters apply, including \code{my_update}). +\begin{lareference} +\lafunc{template class GDIIS_hyperparameters : public BFGS_hyperparameters}{} +\lafunc{int my_diis_dim}{Number of stored points (5; \code{laerror} if $<1$).} +\lafunc{T my_diis_eps}{Regularization added to the diagonal of the DIIS matrix (0).} +\lafunc{template class GDIIS_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{Trust-region Newton and quasi-Newton} + +Minimization by \code{trustregion_minimize()} (section~\ref{sec:optimize:tr}) with the subproblem +$\min g\cdot p+\frac12p\cdot Bp$, $|p|\le\Delta$, solved either exactly in the eigenbasis of $B$ \cite{optimize:more1983} +(Newton step if $B$ is positive definite and the step lies inside, otherwise $p(\mu)=-(B+\mu I)^{-1}g$ with $|p(\mu)|=\Delta$ +found by a safeguarded Newton iteration on $1/|p(\mu)|$, including the hard case; $O(n^3)$ per iteration, handles indefinite +$B$), or approximately by truncated conjugate gradients \cite{optimize:steihaug1983} (\cite{optimize:nocedal2006} alg.~7.2), +which avoids the diagonalization for large $n$. With the exact Hessian (default if \code{OptHessian} is provided) this is a +globally convergent Newton method. +\begin{lareference} +\lafunc{enum TR_subproblem {TR_EIGEN, TR_STEIHAUG}}{Subproblem solver.} +\lafunc{template class TrustRegion_hyperparameters : public trustregion_hyperparameters}{All parameters of +\code{trustregion_hyperparameters} (section~\ref{sec:optimize:tr}) and:} +\lafunc{TR_subproblem my_subproblem}{\code{TR_EIGEN} (default) or \code{TR_STEIHAUG}. Verbose names TR-Newton, TR-Steihaug.} +\lafunc{int my_maxcg}{Maximum number of CG iterations of Steihaug's method, $\le0$ = the dimension (0).} +\lafunc{template class TrustRegion_Optimizer : public Optimizer}{Minimization only (\code{laerror} for +\code{my_saddle_order!=0}, use P-RFO). \code{achieved->my_saddle_order} returns the Hessian index at the result, which is 0 +for a true minimum.} +\end{lareference} +Note that the Steihaug variant works with the stored matrix $B$ (exact or updated), so it saves the $O(n^3)$ diagonalization but +not the $O(n^2)$ storage. + +\subsection{P-RFO: minima and saddle points} + +Partitioned rational function optimization \cite{optimize:banerjee1985,optimize:baker1986}: search for a stationary point with +\code{my_saddle_order}$=k$ negative Hessian eigenvalues. In the eigenbasis of $B$ ($\lambda_i$ ascending, $\tilde g_i$ the +components of $g$), the step is $\tilde p_i=-\tilde g_i/(\lambda_i-\mu)$ with $\mu=\mu_{\max}$, the largest eigenvalue of the +bordered $(k+1)\times(k+1)$ matrix $\bigl[\begin{smallmatrix}\Lambda_k&\tilde g_k\\ \tilde g_k\Tr&0\end{smallmatrix}\bigr]$ of +the $k$ lowest modes (maximized), and $\mu=\mu_{\min}$, the lowest eigenvalue of the corresponding matrix of the other modes +(minimized); the step is scaled down to $\Delta$ if longer. $k=0$ gives RFO minimization. For $k>0$ the trust-region driver works +in its saddle mode (steps always taken). The modes to be maximized are always the $k$ lowest ones (no mode following). +The Hessian choice follows \code{my_hessian}: with \code{TR_AUTO} the exact Hessian if available, otherwise Bofill updates for +$k>0$ (\code{TR_BFGS} is refused for $k>0$). Convergence is normally by \code{my_g_thr}; the number of negative eigenvalues at +the result is only reported (in the verbose output and in \code{achieved->my_saddle_order}), not checked, so the caller should +compare it with $k$. +\begin{lareference} +\lafunc{template class PRFO_hyperparameters : public trustregion_hyperparameters}{All parameters of +\code{trustregion_hyperparameters}, with the default \code{my_delta0=0.3}; \code{my_saddle_order} in $[0,n]$.} +\lafunc{template class PRFO_Optimizer : public Optimizer}{Verbose name P-RFO; \code{laerror} for an invalid +saddle order.} +\end{lareference} + +\subsection{Levenberg-Marquardt} + +For least-squares problems providing \code{OptResiduals} (typically derived from \code{OptLeastSquares}): the step $h$ solves +$(J\Tr J+\mu D)h=-J\Tr r$ (\code{linear_solve}), $D=I$ or $D=\mathrm{diag}(J\Tr J)$ (Marquardt scaling); the damping starts at +$\mu=\tau\max_i(J\Tr J)_{ii}$ and is updated by the gain ratio $\varrho$ \cite{optimize:madsen2004} (alg.~3.16): for $\varrho>0$ +the step is accepted and $\mu\leftarrow\mu\max(1/3,1-(2\varrho-1)^3)$, $\nu=2$, otherwise $\mu\leftarrow\mu\nu$, $\nu\leftarrow2\nu$. +The Jacobian comes from \code{jacobian()} of the problem or from forward differences (relative step \code{my_fd_step}, $n$ +residual evaluations counted in \code{my_maxfunc}). Convergence: $|J\Tr r|<$ \code{my_g_thr}, the general $f$/$x$ criterion, or +when a rejected step becomes negligible at machine precision ($|h|\le10\epsilon(|x|+\epsilon)$). The function value returned is +$f=\frac12|r|^2$. The problem's \code{funcgrad()} is not used. +\begin{lareference} +\lafunc{template class LM_hyperparameters : public gradient_hyperparameters}{} +\lafunc{T my_tau}{Initial damping factor $\tau$ (\code{1e-3}).} +\lafunc{bool my_scaling}{Marquardt scaling $D=\mathrm{diag}(J\Tr J)$ (default \code{false}, $D=I$).} +\lafunc{T my_fd_step}{Relative step of the forward-difference Jacobian (\code{1e-7}).} +\lafunc{template class LM_Optimizer : public Optimizer}{\code{laerror} if the problem does not provide +\code{OptResiduals}. Verbose name Levenberg-Marquardt.} +\end{lareference} +\subsection{Nelder-Mead downhill simplex} + +Derivative-free simplex method \cite{optimize:lagarias1998} with reflection, expansion, outside and inside contraction and +shrink, starting from the simplex $x_0$, $x_0+\mathrm{initstep}\,e_i$ ($n+1$ evaluations). Convergence: \code{my_f_thr} is +compared with the spread $f_{\rm worst}-f_{\rm best}$ of the function values in the simplex and \code{my_x_thr} with its +diameter (largest distance from the best vertex); both have to be satisfied. The best vertex is returned. +\begin{lareference} +\lafunc{template class NelderMead_hyperparameters : public hyperparameters}{Defaults \code{my_maxstep=5000}, +\code{my_f_thr=1e-8}, \code{my_x_thr=1e-6}.} +\lafunc{T my_initstep}{Edge length of the initial simplex (0.1).} +\lafunc{bool my_adaptive}{Dimension-dependent coefficients $\alpha=1$, $\gamma=1+2/n$, $\rho=3/4-1/(2n)$, $\sigma=1-1/n$ +\cite{optimize:gao2012} (default \code{true}, recommended above about 10 dimensions); otherwise the following ones.} +\lafunc{T my_alpha; T my_gamma; T my_rho; T my_sigma}{Reflection (1), expansion (2), contraction (0.5), shrink (0.5) +coefficients.} +\lafunc{template class NelderMead_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{Powell's conjugate direction method} + +Derivative-free \cite{optimize:powell1964}: in each iteration (sweep) line minimizations by \code{linemin_brent()} along $n$ +directions, initially the coordinate axes; the overall displacement of the sweep replaces the direction of the largest decrease if +Powell's test indicates it is worthwhile, followed by a line minimization along it. Convergence: \code{my_f_thr} and +\code{my_x_thr} are compared with the change of $f$ and $x$ in one sweep, both have to be satisfied. The trial step of each +direction is adapted to its last line minimum. +\begin{lareference} +\lafunc{template class Powell_hyperparameters : public hyperparameters}{Defaults \code{my_maxstep=1000}, +\code{my_f_thr=1e-10}, \code{my_x_thr=1e-6}.} +\lafunc{T my_initstep}{Initial trial step of the line minimizations (0.1).} +\lafunc{T my_linmin_tol}{Relative tolerance of the line minimizations (\code{1e-4}); the absolute one is $0.01\,$\code{my_x_thr}.} +\lafunc{int my_maxlinemin}{Maximum number of evaluations in one line minimization (50).} +\lafunc{template class Powell_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{COBYLA} + +Constrained optimization by linear approximations \cite{optimize:powell1994,optimize:powell1998}, derivative-free, minimizing $f$ +subject to the inequality constraints of \code{OptConstraints} (without them unconstrained). Linear models of $f$ and of the +constraints are interpolated on a simplex of $n+1$ points (initially $x$, $x+\mathrm{rhobeg}\,e_j$). The trust-region step +minimizes the linear model of $f$ subject to the linearized constraints within $|d|\le\Delta$ (an active-set method on the small +linear program; if the linearized constraints cannot be satisfied within the trust region, the largest violation is minimized +first); the merit function is $f+\mu\cdot$(largest constraint violation) with an automatically increased penalty $\mu$. Geometry +steps keep the simplex acceptable. The resolution $\rho$ decreases from \code{my_rhobeg} to \code{my_rhoend}; the iterations +finish when $\rho$ has reached \code{my_rhoend} and the trust-region step does not improve the point (\code{converged=true}), or +by the general criterion, in which \code{my_f_thr} is compared with the change of $f$ at the best vertex in an iteration and +\code{my_x_thr} with $\rho$ (both 0 by default, i.e.\ off). The best vertex with respect to the merit function is returned; +with verbose output the largest remaining constraint violation is printed. COBYLA does not need a constraint to be satisfied at +the starting point. + +\begin{lareference} +\lafunc{template class Cobyla_hyperparameters : public hyperparameters}{Default \code{my_maxstep=100000}.} +\lafunc{T my_rhobeg}{Initial trust-region radius, about one tenth of the expected change of $x$ (0.5).} +\lafunc{T my_rhoend}{Final trust-region radius, the accuracy of $x$ (\code{1e-6}).} +\lafunc{bool my_flexible_radius}{\code{true} (default): the trust-region radius $\Delta\ge\rho$ is revised by the reduction +ratio and $\rho$ is reduced by the rules of NEWUOA \cite{optimize:powell2004} (eqs.~7.1, 7.6, 6.16), so that $\Delta$ can also +increase again; geometry steps have the length $\max(\min(0.1\,\mathrm{dist},0.5\Delta),\rho)$. \code{false}: Powell's original +COBYLA with $\Delta=\rho$, $\rho$ halved when reduced.} +\lafunc{template class Cobyla_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{Rotosolve} + +Sweeps of exact minimizations along single parameters in which $f$ is trigonometric (\code{OptTrigonometric}) +\cite{optimize:ostaszewski2021,optimize:nakanishi2020}, for degree $>1$ \cite{optimize:wierichs2022}. For $x_i$ of degree $R$, +$f$ along $x_i$ is reconstructed by a discrete Fourier transform from $2R+1$ equidistant samples over its period (the current +value and $2R$ new evaluations) and minimized without further evaluations (in closed form for $R=1$, otherwise by a dense grid +refined by Brent's method on the reconstruction); the step taken is the shortest one modulo the period. Parameters of degree 0, +or all parameters if the problem does not provide \code{OptTrigonometric}, are minimized by \code{linemin_brent()} as in Powell's +method, whose parameters apply. Convergence as for Powell (change of $f$ and $x$ in one sweep). If the evaluation budget does +not suffice for the next minimization, the sweep is interrupted and the method stops without convergence. +\begin{lareference} +\lafunc{template class Rotosolve_hyperparameters : public Powell_hyperparameters}{} +\lafunc{bool my_reevaluate}{Evaluate $f$ at the new point instead of taking the value of the reconstruction (for noisy $f$ or +doubtful degrees; one more evaluation per parameter; default \code{false}).} +\lafunc{template class Rotosolve_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{SGD with momentum} + +By \code{firstorder_minimize()}: velocity $v\leftarrow\mathrm{momentum}\cdot v+g$ ($v=g$ in the first iteration), step direction +$d=v$, or $d=g+\mathrm{momentum}\cdot v$ (Nesterov) \cite{optimize:sutskever2013}, as in the PyTorch update formula. +\begin{lareference} +\lafunc{template class SGD_hyperparameters : public firstorder_hyperparameters}{Default \code{my_lr=1e-2}.} +\lafunc{T my_momentum; bool my_nesterov}{Momentum (0.9; 0 gives plain gradient descent); Nesterov variant (\code{false}). +Verbose names SGD, SGD-Nesterov.} +\lafunc{template class SGD_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{Quantum natural gradient} + +$x\leftarrow x-\mathrm{lr}\,(G+\mathrm{metric\_reg}\,I)^{-1}g$ \cite{optimize:stokes2020}: SGD without momentum with the natural +gradient; the problem has to provide the metric (\code{OptMetric}), the gradient may come e.g.\ from \code{ParameterShift}. +Momentum, Adam or SPSA can be combined with the natural gradient by setting \code{my_natural=true} in their hyperparameters. +Natural steps are longer than gradient steps with the same learning rate (the metric eigenvalues are typically $\le1/4$): use +lr up to about 0.1 or a larger \code{my_metric_reg}. +\begin{lareference} +\lafunc{template class QNG_hyperparameters : public SGD_hyperparameters}{Defaults \code{my_momentum=0}, +\code{my_natural=true}, \code{my_lr=0.05}.} +\lafunc{template class QNG_Optimizer : public Optimizer}{Forces \code{my_natural=true} also if other +hyperparameters were passed; \code{laerror} if the problem does not provide \code{OptMetric}.} +\end{lareference} + +\subsection{Adam, AdamW, AMSGrad} + +Adam \cite{optimize:kingma2015} with bias-corrected moment estimates, $d=\hat m/(\sqrt{\hat v}+\epsilon)$; with +\code{my_weight_decay>0} the decoupled weight decay of AdamW \cite{optimize:loshchilov2019}; optionally AMSGrad +\cite{optimize:reddi2018} (the elementwise maximum of the past second moments). \code{my_beta1=0} gives RMSprop. +\begin{lareference} +\lafunc{template class Adam_hyperparameters : public firstorder_hyperparameters}{Default learning rate +\code{1e-3} of the base class.} +\lafunc{T my_beta1; T my_beta2}{Decay rates of the first (0.9) and second (0.999) moment estimates.} +\lafunc{T my_eps}{Regularization of the division by the second moment (\code{1e-8}).} +\lafunc{bool my_amsgrad}{AMSGrad (\code{false}). Verbose names Adam, AdamW, AMSGrad.} +\lafunc{template class Adam_Optimizer : public Optimizer}{} +\end{lareference} + +\subsection{SPSA} + +Simultaneous perturbation stochastic approximation \cite{optimize:spall1992,optimize:spall1998}: derivative-free gradient estimate +$\hat g_i=(f(x+c_k\Delta)-f(x-c_k\Delta))/(2c_k\Delta_i)$ with random $\Delta_i=\pm1$ (from \code{randdouble()} of \header{la_random.h}), +two evaluations per estimate independently of the dimension, robust to noise (e.g.\ shot noise in VQE). The function value +reported is the average of the two evaluations. Gain sequences: $a_k=\mathrm{lr}/(1+\mathrm{lr\_decay}\cdot k)^{\mathrm{lr\_power}}$, +i.e.\ Spall's $a/(k+1+A)^\alpha$ with $\mathrm{lr}=a/(1+A)^\alpha$, $\mathrm{lr\_decay}=1/(1+A)$, $\mathrm{lr\_power}=\alpha$; +$c_k=c/(k+1)^\gamma$. Since $f$ and $g$ are estimates, no convergence test is done: the iterations run until \code{my_maxstep} +or \code{my_maxfunc}, and \code{converged} is \code{false}. The first estimate at the starting point is always done, even if +\code{my_maxfunc} is smaller than its $2\cdot$\code{my_resamplings} evaluations. Before it, the learning rate can be calibrated +(evaluations counted; the value used is returned in \code{achieved->my_lr} if \code{achieved} is a +\code{firstorder_hyperparameters}). +\begin{lareference} +\lafunc{template class SPSA_hyperparameters : public firstorder_hyperparameters}{Defaults +\code{my_lr_decay=0.01}, \code{my_lr_power=0.602}, \code{my_g_thr=0}.} +\lafunc{T my_c; T my_gamma}{Perturbation size $c$, of the order of the noise of $f$ (0.1), and its decay exponent $\gamma$ (0.101).} +\lafunc{int my_resamplings}{Number of gradient estimates averaged in one iteration (1; \code{laerror} if $<1$).} +\lafunc{int my_calibrate}{Number of gradient estimates at the starting point for the calibration of lr, 0 = no calibration (20).} +\lafunc{T my_target_step}{Calibration: lr is set so that the first step has on average this length (0.1).} +\lafunc{template class SPSA_Optimizer : public Optimizer}{} +\end{lareference} + +%----------------------------------------------------------------------------------------------------------------------------- +\section{Examples}\label{sec:optimize:examples} + +All examples were compiled and run; they assume \texttt{\#include "la.h"} and \code{using namespace LA;}. + +\subsection{A user-defined problem and BFGS} + +The extended Rosenbrock function with its analytic gradient. Note the \code{copyonwrite()} on the gradient vector before its +elements are written. +\begin{lstlisting} +class Rosenbrock : public OptProblem { +public: +double funcgrad(const NRVec &x, NRVec *g) const + { + const int n=x.size(); + double f=0; + if(g) {g->copyonwrite(); *g=0.;} //g may share data with another vector + for(int i=0; i x(-1.2,4); //starting point (-1.2,...,-1.2) +BFGS_hyperparameters param; +param.my_g_thr=1e-8; +param.my_maxstep=1000; +param.my_verbose=0; +gradient_hyperparameters achieved; +BFGS_Optimizer o; +optresult res=o.minimize(&problem,x,¶m,&achieved); +std::cout <<"converged "< fd(&problem);} an $f$-only problem can be minimized by BFGS as +\code{BFGS_Optimizer().minimize(&fd,x);}. + +\subsection{Freezing parameters}\label{sec:optimize:ex:frozen} + +\begin{lstlisting} +Rosenbrock problem; +NRVec x(0.5,6); +bitvector frozen(6); +frozen.clear(); +frozen.set(0); frozen.set(3); //x_0 and x_3 keep their values +FrozenOptProblem fp(&problem,x,frozen); +NRVec xr=fp.reduce(x); //the 4 free parameters +LBFGS_Optimizer o; +double f=o.minimize(&fp,xr); //default parameters; optresult converts to f +NRVec xopt=fp.expand(xr); //full vector, x_0 = x_3 = 0.5 +\end{lstlisting} +\code{fp.xfull} shares its data with \code{x}; reading elements of \code{x} afterwards in a DEBUG build has to go through a const +reference, writing them needs \code{x.copyonwrite()} first (which then also detaches \code{x} from \code{fp.xfull}). + +\subsection{COBYLA with constraints} + +\begin{lstlisting} +//min (x0-1)^2+(x1-2)^2 subject to x0+x1 <= 1, solution (0,1) +class ConstrainedQuadratic : public OptProblem, public OptConstraints { +public: +double funcgrad(const NRVec &x, NRVec *g) const + {return (x[0]-1)*(x[0]-1)+(x[1]-2)*(x[1]-2);} //COBYLA never asks for g +int nconstraints() const {return 1;} +void constraints(const NRVec &x, NRVec &c) const + {c.copyonwrite(); c[0]=1-x[0]-x[1];} //c_i >= 0 +}; + +ConstrainedQuadratic problem; +NRVec x(0.,2); +Cobyla_hyperparameters param; +param.my_rhobeg=0.5; +param.my_rhoend=1e-8; +param.my_verbose=0; +Cobyla_Optimizer o; +optresult res=o.minimize(&problem,x,¶m); +NRVec c(1); +problem.constraints(x,c); //check the feasibility +\end{lstlisting} + +\subsection{Least-squares fit by Levenberg-Marquardt} + +\begin{lstlisting} +//fit of y = b0 exp(-b1 t) to data; residuals r_i = model - y_i +class ExpFit : public OptLeastSquares { +public: +NRVec t,y; //data +ExpFit(const NRVec &tt, const NRVec &yy) : t(tt), y(yy) {}; +int nresiduals() const {return t.size();} +void residuals(const NRVec &b, NRVec &r) const + { + r.copyonwrite(); + for(int i=0; i &b, NRMat &J) const + { + J.copyonwrite(); + for(int i=0; i t(m), y(m); +for(int i=0; i b(2); +b[0]=1; b[1]=0.1; +LM_hyperparameters param; +param.my_g_thr=1e-10; +param.my_verbose=0; +hyperparameters achieved; +LM_Optimizer o; +optresult res=o.minimize(&problem,b,¶m,&achieved); +//res.f = 1/2 |r|^2, i.e. half the residual sum of squares +\end{lstlisting} +Since \code{ExpFit} is an \code{OptLeastSquares}, the same object can also be passed to e.g.\ \code{BFGS_Optimizer}. + +\subsection{Further examples in the distribution} + +t.cc contains testbeds (blocks starting with \code{//testbed of the opt...}) for the methods on \code{testproblem}, +\code{testproblem_vqe(_constrained)} (with \code{ParameterShift}), \code{FrozenOptProblem}, P-RFO on the M\"uller-Brown potential +and LM on a NIST problem. The header testoptimize.h (in the source distribution, not installed) defines these test problems, which can serve as +templates for user problems: +\begin{itemize} +\item \code{testproblem}: a smooth function with analytic gradient; +\item \code{testproblem_vqe}: a VQE-like Ising chain in a product state providing \code{OptHessian}, \code{OptMetric} and +\code{OptTrigonometric}; \code{testproblem_vqe_constrained}: the same with one constraint (\code{OptConstraints}); +\item \code{testproblem_mullerbrown}: the M\"uller-Brown potential \cite{optimize:mueller1979} with analytic Hessian, three +minima and two saddle points; +\item NIST StRD nonlinear regression problems (\code{OptLeastSquares}) with starting values, certified parameters and +residual sums of squares: \code{testproblem_misra1a}, \code{testproblem_mgh09} (analytic Jacobians) and +\code{testproblem_thurber} (finite-difference Jacobian). +\end{itemize} The regression program testoptimize.cc, run by +\code{make check}, tests all methods, adapters, capabilities, the \code{maxfunc} limits, the hyperparameter inheritance and the +copy-on-write behaviour of the functions used; it prints PASS/FAIL lines and its exit code is the number of failures. + +%----------------------------------------------------------------------------------------------------------------------------- +\section{Pitfalls}\label{sec:optimize:pitfalls} + +\lawarning{\textbf{Gradient and other output arguments.} The vector \code{*grad} passed to \code{funcgrad()}, and the arguments +\code{h}, \code{g}, \code{c}, \code{r}, \code{J} of the capability functions, are allocated by the caller but may share their +data with other objects. Call \code{copyonwrite()} before writing elements through \code{operator[]}/\code{operator()} +(assignments of whole objects or scalars, e.g.\ \code{*grad=0.;}, are safe and detach by themselves). Forgetting it can silently +corrupt vectors of the optimizer (e.g.\ the previous gradient).} + +\lawarning{\textbf{Shared reference to the best point.} \code{optevaluator} keeps \code{xbest=x}, a shallow copy sharing the data +of the vector evaluated. Code calling an \code{optevaluator} (a new method, or a gradient estimator) and then modifying that vector +element-wise must call \code{copyonwrite()} before each such modification, otherwise the recorded best point changes as well +(see \code{forward_difference_jacobian} and \code{trig_minimize} in optimize.cc). Whole-vector operations (\code{axpy}, +\code{+=}, assignment) are safe.} + +\lawarning{\textbf{Reading possibly shared vectors.} After \code{minimize()} the vector \code{x}, and the vectors given to +\code{FrozenOptProblem} (\code{xfull}), may share data with other objects. In DEBUG builds the non-const \code{operator[]} raises +\code{laerror} when the reference count is $>1$ (section~\ref{sec:concepts:errors}); read elements of a possibly shared non-const +vector through a const reference, e.g.\ \code{const NRVec &xc=x; ... xc[i]}. Inside \code{funcgrad()} the argument +\code{x} is already const.} + +\lawarning{\textbf{Convergence thresholds.} The general criterion needs \emph{both} \code{my_f_thr} and \code{my_x_thr} (strict +inequalities); with the default 0 it is off, and gradient methods then converge only by \code{my_g_thr}. When passing a +hyperparameter object of a more general class, its defaults replace those of the method (section~\ref{sec:optimize:setfrom}). +Methods print by default (\code{my_verbose=1}); set it to 0 for silent operation.} + +\lawarning{\textbf{Saddle points.} \code{converged} of P-RFO does not imply the requested saddle order; check +\code{achieved->my_saddle_order}. Likewise a converged \code{TrustRegion_Optimizer} run may end at a saddle point if started at +one with zero gradient components along the negative modes; the reported index tells.} + +\lawarning{\textbf{Noisy functions.} SPSA (and first-order methods with an inexact \code{gradient_estimator}) never report +convergence; set \code{my_maxstep} or \code{my_maxfunc}. \code{my_return_best} should not be used with noisy $f$. SPSA uses the +global random generator of \header{la_random.h}; seed it for reproducible runs.} diff --git a/doc/permutation.tex b/doc/permutation.tex new file mode 100644 index 0000000..694a947 --- /dev/null +++ b/doc/permutation.tex @@ -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} and \code{PermutationAlgebra} +for \code{T = int} with \code{R = int}, \code{double}, \code{std::complex}. +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}, \code{CyclePerm}, \code{Partition}, + \code{CompressedPartition} and \code{YoungTableaux} 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 class NRPerm : public NRVec_from1} 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 explicit NRPerm(const T (&a)[SIZE])}{from an array or brace list, e.g.\ \code{NRPerm p({1,4,2,3})}.} +\lafunc{NRPerm(const NRVec_from1 &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 &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 &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 &q, bool inverse) const}{$pq$, or $pq^{-1}$ if \code{inverse} (without forming $q^{-1}$).} +\lafunc{NRPerm operator*(const CyclePerm &r) const}{product with a permutation in cycle notation, converted to size \code{size()}.} +\lafunc{template PermutationAlgebra operator*(const PermutationAlgebra &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 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 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=|\{kp(j)\}|$; + 3: $I_j=|\{k>j: p(k)&)}; 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&))}{all $n!$ permutations in lexicographic order, starting from the identity, using \code{next()}.} +\lafunc{PERM_RANK_TYPE generate_all(void (*callback)(const NRPerm&), 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&))}{recursive generation, not lexicographic.} +\lafunc{PERM_RANK_TYPE generate_all_multi(void (*callback)(const NRPerm&))}{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&), const NRVec_from1 &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 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 list_all_lex()}{all permutations in lexicographic order (term $k$ has rank $k$), all with weight 1, i.e.\ the unnormalized symmetrizer.} +\lafunc{PermutationAlgebra list_restricted(const NRVec_from1 &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 class CyclePerm}, derived from \code{NRVec_from1< NRVec_from1 >}, 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 explicit CyclePerm(const NRVec_from1 (&a)[SIZE])}{empty (the identity); from an array of cycles, e.g.\ \code{CyclePerm c({NRVec_from1({1,2}),NRVec_from1({3,4,5})})}.} +\lafunc{explicit CyclePerm(const NRPerm &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 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 operator*(const NRPerm &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 &x)}{reads one line and calls \code{readfrom}.} +\lafunc{std::ostream & operator<<(std::ostream &s, const CyclePerm &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 class WeightPermutation} is a pair of public members \code{R weight; NRPerm 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 &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 &x); std::istream & operator>>(...)}{weight followed by the permutation.} +\end{lareference} + +\subsection{PermutationAlgebra} +\code{template class PermutationAlgebra : public NRVec >} 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 > &x)}{empty; $n$ uninitialized terms; from a vector of terms.} +\lafunc{template PermutationAlgebra(const PermutationAlgebra &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::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) &rhs) const; operator&(const NRPerm &rhs) const; operator|(const NRPerm &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 &q, bool reverse=false) const; PermutationAlgebra commutator(const NRPerm &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 PermutationAlgebra 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 PermutationAlgebra general_antisymmetrizer(const NRVec > &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 void cast_permutation_algebra(PermutationAlgebra &lhs, const PermutationAlgebra &rhs)}{conversion of the weight type into an existing object.} +\lafunc{template NRMat 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 NRMat RegularRepresentation(const PermutationAlgebra &a, const NRMat &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} stores the parts in non-increasing order, padded with zeros ($[3,1]$ of 4 is \code{3 1 0 0}); +\code{CompressedPartition} 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 explicit CompressedPartition(const T (&a)[SIZE])}{empty; size $n$ uninitialized; from an array.} +\lafunc{explicit CompressedPartition(const Partition &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 &x)}{e.g.\ \code{3,2^2,1}.} +\end{lareference} + +\subsection{Partition} +\begin{lareference} +\lafunc{Partition(); Partition(const int n); template 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 &rhs); explicit Partition(const YoungTableaux &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&), 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 class YoungTableaux}, derived from \code{NRVec_from1< NRVec_from1 >}, 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 &frame)}{empty; empty (zero-filled) diagram of the shape \code{frame}.} +\lafunc{template explicit YoungTableaux(const NRVec_from1 (&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 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&))}{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 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 &x)}{one row per line.} +\end{lareference} + +\section{Characters of the symmetric group}\label{sec:permutation:characters} + +\begin{lareference} +\lafunc{template T Sn_character(const Partition &irrep, const Partition &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 &irrep, const CompressedPartition &cclass)}{the same for compressed partitions.} +\end{lareference} + +\code{template class Sn_characters} is the whole character table of $S_n$ with public members +\begin{lstlisting} +T n; +NRVec_from1 > classes; //cycle types +NRVec_from1 > irreps; //irrep labels +NRVec_from1 classsizes; +NRMat_from1 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 &c)}{prints class sizes, class labels \code{(..)}, and rows \code{[irrep] chi}.} +\end{lareference} + +\code{template 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 &rhs); bool is_valid() const}{take the classes from a character table; check that all classes are partitions of the same $n$.} +\lafunc{Polynomial substitute(const Polynomial &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} or \code{CyclePerm}; 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(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(wp,direction)}, \code{NRMat(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 NRVec_from1 applypermutation(const NRPerm &p, const NRVec_from1 &set, bool inverse=false)}{a free function + (element type independent of \code{T}), same convention as \code{NRVec::permuted}.} +\lafunc{int NRVec::sort(int direction, NRPerm &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 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 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 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 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}, + 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 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 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 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 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 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 p({2,3,1}); +NRPerm q=p*p; // (3 1 2) +std::cout << p.rank() << ' ' << p.parity() << std::endl; // 3 1 +NRPerm r(3,p.rank()); // unranking, r==p +CyclePerm c(p); // (1 2 3) +std::cout << c << ' ' << c.cycles() << std::endl; +NRVec_from1 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 &p) {std::cout << p.parity() << ' ' << p;} +... +NRPerm p(4); +PERM_RANK_TYPE n=p.generate_all_lex(printme); // 24 +NRVec_from1 classes({1,1,2,2}); +PermutationAlgebra 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 mtable; + +static void ycallback(const YoungTableaux &y) +{ +PermutationAlgebra Y = y.young_operator(); +NRMat R = RegularRepresentation(Y,mtable); +std::cout << y << "Y = " << Y << "trace of R = " << R.trace() << std::endl; +} + +int main() +{ +Sn_characters Sn(4); +std::cout << Sn; +if(!Sn.is_valid()) laerror("internal error"); +mtable = Multable(4); +Partition lambda({2,1,1,0}); +YoungTableaux 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} diff --git a/doc/polynomial.tex b/doc/polynomial.tex new file mode 100644 index 0000000..25ec789 --- /dev/null +++ b/doc/polynomial.tex @@ -0,0 +1,207 @@ +\chapter{Polynomials and continued fractions}\label{ch:polynomial} + +This chapter describes the polynomial class \code{Polynomial} and the exact rational and continued-fraction arithmetics +(\code{Rational}, \code{ContFrac} with Gosper's algorithms). + +\section{Polynomial}\label{sec:polynomial:polynomial} +\laheader{polynomial.h, polynomial.cc} + +\subsection{Purpose and storage} +\code{template class Polynomial : public NRVec} 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}; 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(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 Polynomial(const T (&a)[SIZE])}{from coefficients $c_0,c_1,\ldots$, e.g.\ \code{Polynomial p({1,2,3})} is $1+2x+3x^2$.} +\lafunc{Polynomial(const NRVec &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::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 Polynomial operator+(const T &a, const Polynomial &rhs)}{$a+p$.} +\lafunc{template Polynomial operator-(const T &a, const Polynomial &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 C value(const Polynomial &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 C even_value(const Polynomial &p, const C &x); C odd_value(const Polynomial &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 NRVec values(const Polynomial &p, const NRVec &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 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::complextype> roots() const}{all roots as eigenvalues of the companion matrix (\code{gdiagonalize}, chapter~\ref{ch:nonclass}); for \code{double} + and \code{std::complex} only (laerror for integer types).} +\lafunc{NRVec realroots(const typename LA_traits::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::normtype thr=1e-14, const int maxit=1000) const}{Newton iteration from \code{x0} until $|p(x)|<$ \code{thr}; no convergence report.} +\lafunc{template Polynomial polyfromroots(const NRVec &roots, const int skip= -1)}{monic $\prod_{i\ne\mathrm{skip}}(x-r_i)$.} +\lafunc{template Polynomial lagrange_interpolation(const NRVec &x, const NRVec &y)}{interpolating polynomial of degree $n-1$ through the $n$ points; laerror for repeated $x$.} +\lafunc{template NRMat Sylvester(const Polynomial &p, const Polynomial &q)}{Sylvester matrix of dimension $\deg p+\deg q$ (its determinant is the resultant).} +\lafunc{template Polynomial poly_gcd(const Polynomial &p, const Polynomial &q, const typename LA_traits::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 Polynomial svd_gcd(const Polynomial &p, const Polynomial &q, const typename LA_traits::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 Polynomial poly_lcm(const Polynomial &p, const Polynomial &q, const typename LA_traits::normtype thr=0)}{$pq/\gcd(p,q)$.} +\end{lareference} + +\subsection{Orthogonal polynomials} +\begin{lareference} +\lafunc{template Polynomial 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 p=polyfromroots(NRVec({1.,2.,3.})); // (x-1)(x-2)(x-3) +Polynomial q({1.,1.}); // 1+x +Polynomial a,b; +p.polydiv(q,a,b); // p = a*q + b +std::cout << (a*q+b-p).norm() << std::endl; +NRVec r = p.realroots(1e-10); // 1 2 3 +Polynomial pp = p.integral(2).derivative(2); // == p +NRMat m(3,3); m.randomize(1.); +NRMat pm = value(p,m); // p(m) +Polynomial h = hermite_polynomial(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 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 &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 operator+(const ContFrac &rhs) const; -, *, /}{mixed arithmetic, the result is a continued fraction.} +\lafunc{std::ostream & operator<<(std::ostream &s, const Rational &x); std::istream & operator>>(...)}{format \code{p/q} (input: any single separator character).} +\end{lareference} + +\subsection{ContFrac} +\code{template class ContFrac : public NRVec} 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 ContFrac(const T (&a)[SIZE]); ContFrac(const NRVec &v); explicit ContFrac(const std::list &x)}{from the terms $a_0,a_1,\ldots$} +\lafunc{ContFrac(T p, T q); explicit ContFrac(const Rational &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 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 &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 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 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 value(const ContFrac &z) const}{the continued fraction of $h(z)$.} +\lafunc{BiHomographic(); explicit BiHomographic(const T (&a)[2][2][2])}{uninitialized; from the coefficients.} +\lafunc{ContFrac value(const ContFrac &x, const ContFrac &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 x(11,101), v(3,7); +std::cout << Rational(x+v) << ' ' << Rational(x*v) << std::endl; // 380/707 33/707 +BiHomographic h({{{12,4},{3,1}},{{0,1},{-1,0}}}); // (x+3)(y+4)/(x-y) +std::cout << Rational(h.value(x,v)) << std::endl; // -4867/113 +ContFrac pi(3.14159265358979,10,1000000); +Rational r = pi.rational(3); // 355/113 +\end{lstlisting} + +\subsection{Pitfalls} +\begin{itemize} +\item \code{Polynomial(n)} and \code{ContFrac(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} diff --git a/doc/quaternion.tex b/doc/quaternion.tex new file mode 100644 index 0000000..540e100 --- /dev/null +++ b/doc/quaternion.tex @@ -0,0 +1,102 @@ +\chapter{Quaternions}\label{ch:quaternion} +\laheader{quaternion.h, quaternion.cc} + +Quaternions; plain data, no reference counting. + +\section{Purpose} +\code{Quaternion} 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>} declares it plain data. The stream operators are templates in the +header, so that e.g.\ \code{Quaternion>} (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 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 &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 *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 &); std::istream &operator>>(std::istream &, Quaternion &)}{Four components separated by spaces.} +\lafunc{void normquat2su2mat(const Quaternion &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}); $\pm q$ give different elements.} +\lafunc{void normquat2rotmat(const Quaternion &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 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 &q, Quaternion &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 &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 exp(const Quaternion &x)}{$e^{x_0}(\cos|\mathbf v|, \hat{\mathbf v}\sin|\mathbf v|)$, $\mathbf v=(x_1,x_2,x_3)$.} +\lafunc{Quaternion log(const Quaternion &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 pow(const Quaternion &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 q(1.,2.,3.,4.); +q.normalize(NULL,true); //unit quaternion with q[0]>=0 +Mat3 a; +normquat2rotmat(q,a); +Vec3 v(1.,-2.,.5), w; +q.rotate(w,v); //w == a*v +Quaternion back; +rotmat2normquat(a,back); //back == q +Vec3 axis={0,1/sqrt(2),1/sqrt(2)}; +Quaternion r; +r.axis2normquat(axis,0.5); +Quaternion rv = Quaternion(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.} diff --git a/doc/regression.tex b/doc/regression.tex new file mode 100644 index 0000000..1e6c612 --- /dev/null +++ b/doc/regression.tex @@ -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{} 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{} (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 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 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.} diff --git a/doc/smat.tex b/doc/smat.tex new file mode 100644 index 0000000..ebfeda5 --- /dev/null +++ b/doc/smat.tex @@ -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} stores a square symmetric (real \code{T}) or hermitian (complex \code{T}) +matrix of order $n$ in packed form, i.e.\ only $n(n+1)/2$ elements of one triangle are kept. +It halves the memory compared to \code{NRMat} and gives access to the packed BLAS/LAPACK +routines (\code{dspmv}, \code{zhpmv}, \code{dspr2}, \code{zhpr2}, \code{dspev}, \ldots). +Typical uses are Hamiltonians, overlap and density matrices, Hessians (see \ref{ch:optimize}) +and as input for \code{diagonalize()} (see \ref{ch:nonclass}). +Results of operations which are not symmetric in general (e.g.\ the product of two symmetric +matrices) are returned as \code{NRMat}. + +\code{NRSMat_from1} (section~\ref{sec:smat:from1}) is the same class with the +two-index \code{operator()} counting from 1. + +The file \code{smat.cc} contains explicit instantiations for the element types \code{double}, +\code{std::complex} 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}. 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} can be viewed as an \code{NRVec} of length $n(n+1)/2$ and +vice versa without copying (see section~\ref{sec:smat:construct}). + +\code{smat.h} defines the macro \code{NN2} (\code{(size_t)nn*(nn+1)/2}) which it uses internally and +does not undefine; avoid using this name in your own code. + +\subsection{Index functions}\label{sec:smat:indexfunc} +The following inline function templates (namespace \code{LA}, template parameter = integer type of the +indices) compute the packed index and return \code{size_t}. They are used by the class itself and +are useful for code working directly with packed data (e.g.\ two-electron integrals in \ref{ch:fourindex}). + +\begin{lareference} +\lafunc{size_t SMat_index(T i, T j)}{Index (\ref{eq:smat:index}) for $0\le i,jj$, symmetric in $i,j$; returns \code{(size_t)-1} for $i=j$.} +\lafunc{size_t ASMat_index_1(T i, T j)}{The same for indices counting from 1: $(i-2)(i-1)/2+j-1$ for $i>j$; \code{(size_t)-1} for $i=j$. Computed in the arithmetic of \code{T} (no cast to \code{size_t}), so it may overflow for large \code{int} indices.} +\end{lareference} + +\subsection{Hermitian interpretation of complex matrices}\label{sec:smat:hermitian} +For complex \code{T} an \code{NRSMat} is a \emph{hermitian} matrix: the lower triangle is stored and the +upper triangle is its complex conjugate, $A_{ij}=A_{ji}^*$ for $i(const NRSMat&)} conjugates the upper triangle and + \code{NRSMat(const NRMat&)} takes the hermitian part $(A+A\Hc)/2$; +\item \code{fprintf()}, \code{sum()} and \code{NRMat::operator+=/-=} (also \code{+}, \code{-}) + with an \code{NRSMat} operand use the full hermitian matrix; \code{operator>>} and \code{fscanf} read the full square matrix, the elements + of the lower triangle (read later) are the ones stored; +\item \code{operator<<}, \code{permuted()}, \code{submatrix(const NRVec&)}, \code{rsum()} and \code{csum()} are built on + \code{operator()} and therefore use the stored element also for $i&, int)} and \code{NRVec(const NRSMat&)} +also share the data (the reference count is common to the vector and the matrix). + +GPU support (\code{CUDALA}) is experimental, see section~\ref{sec:concepts:gpu}; many members +are CPU only (\code{NOT_GPU}). + +\section{Construction, assignment, conversion}\label{sec:smat:construct} +\begin{lareference} +\lafunc{NRSMat()}{Empty matrix (no data, no reference count).} +\lafunc{explicit NRSMat(const int n, const GPUID loc = undefined)}{Order $n$, elements uninitialized.} +\lafunc{NRSMat(const T &a, const int n)}{Order $n$, all $n(n+1)/2$ stored elements (i.e.\ also the off-diagonal ones) set to \code{a}.} +\lafunc{NRSMat(const T *a, const int n)}{Copy of $n(n+1)/2$ packed elements (lower triangle by rows) from \code{a}.} +\lafunc{NRSMat(const NRSMat &rhs)}{Shallow copy.} +\lafunc{NRSMat(const typename LA_traits_complex::NRSMat_Noncomplex_type &rhs, bool imagpart = false)}{Complex matrix from a real one: \code{rhs} becomes the real part (\code{imagpart=false}) or the imaginary part (\code{imagpart=true}), the other part is zero. Defined only for \code{T=std::complex}.} +\lafunc{explicit NRSMat(const NRMat &rhs)}{Symmetric part $(A+A\Tr)/2$ of a square dense matrix (deep copy; nonsquare input is checked only in DEBUG).} +\lafunc{explicit NRSMat(const NRVec &rhs, const int n)}{Interprets a vector of length $n(n+1)/2$ as packed matrix of order $n$. \emph{Shares} the data with \code{rhs} (no copy); length checked only in DEBUG.} +\lafunc{explicit NRSMat(const SparseMat &rhs); explicit NRSMat(const SparseSMat &rhs)}{Dense from sparse, see \ref{ch:sparse}.} +\lafunc{~NRSMat()}{Destructor, frees the data when the last reference disappears.} +\lafunc{NRSMat & operator=(const NRSMat &rhs)}{Shallow copy, see section~\ref{sec:concepts:assign}.} +\lafunc{NRSMat & operator|=(const NRSMat &rhs)}{Deep copy.} +\lafunc{NRSMat & operator=(const T &a)}{Scalar matrix: zero all elements (by \code{memset}), set the diagonal to \code{a}. Dimension must be set before.} +\lafunc{void identity()}{\code{*this = (T)1}.} +\lafunc{void clear()}{Set all elements to zero (keeps the dimension).} +\lafunc{void resize(const int n)}{Change the order; data are not preserved and new elements are uninitialized. A shared matrix is detached; an unshared one of the same order is left untouched; \code{n=0} deallocates.} +\lafunc{void dealloc()}{\code{resize(0)}.} +\lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{Make the data private, see section~\ref{sec:concepts:refcount}.} +\lafunc{void swap(NRSMat &rhs)}{Exchange contents without copying.} +\lafunc{void moveto(const GPUID dest); GPUID getlocation() const}{GPU location (experimental), see section~\ref{sec:concepts:gpu}.} +\lafunc{int getcount() const}{Current reference count (0 for an empty matrix).} +\end{lareference} +Conversions in other classes: \code{explicit NRMat(const NRSMat&)} expands to a full square matrix +(hermitian for complex types, see section~\ref{sec:smat:hermitian}); \code{explicit NRVec(const NRSMat&)} views the +packed data as a vector (shared data). \code{NRMat::transposedtimes()} and \code{timestransposed()} +return $A\Hc A$ and $A A\Hc$ as \code{NRSMat} (see \ref{ch:mat}). + +\section{Element access}\label{sec:smat:access} +\begin{lareference} +\lafunc{T& operator()(const int i, const int j); const T& operator()(const int i, const int j) const}{Element $(i,j)$, $0\le i,jj}; helper for code that uses packed storage for a matrix one triangle of which is redundant but not equal to the other (e.g.\ antisymmetric), to know whether the stored element must be transformed.} +\lafunc{bool issymmetric() const}{Always true (interface compatibility with sparse matrices).} +\lafunc{void simplify()}{Does nothing (interface compatibility with sparse matrices).} +\end{lareference} +\lawarning{The non-const \code{operator()} and \code{operator[]} do not call \code{copyonwrite()}. Call +\code{x.copyonwrite()} before writing through them; in DEBUG builds they raise an error for a shared +matrix even when only reading, so read elements of a possibly shared matrix through a const reference.} + +\section{Arithmetic and BLAS/LAPACK-backed operations}\label{sec:smat:arith} +All in-place operators call \code{copyonwrite()}. The binary operators returning \code{NRSMat} are +generated from the in-place ones. +\begin{lareference} +\lafunc{NRSMat & operator*=(const T &a)}{Multiply all elements by \code{a} (\code{dscal}/\code{zscal}).} +\lafunc{NRSMat & operator+=(const T &a); NRSMat & operator-=(const T &a)}{Add/subtract $a\cdot 1$, i.e.\ \code{a} on the diagonal only.} +\lafunc{NRSMat & operator+=(const NRSMat &rhs); NRSMat & operator-=(const NRSMat &rhs)}{Elementwise sum/difference (\code{daxpy}/\code{zaxpy}).} +\lafunc{const NRSMat operator-() const}{Unary minus.} +\lafunc{const NRSMat operator*(const T &a) const; const NRSMat operator+(const T &a) const; const NRSMat operator-(const T &a) const}{As the in-place versions (\code{+}/\code{-} act on the diagonal).} +\lafunc{const NRSMat operator+(const NRSMat &rhs) const; const NRSMat operator-(const NRSMat &rhs) const}{Sum, difference.} +\lafunc{const NRMat operator+(const NRMat &rhs) const; const NRMat operator-(const NRMat &rhs) const}{Sum/difference with a dense matrix, dense result.} +\lafunc{const NRMat operator*(const NRSMat &rhs) const}{Matrix product (not symmetric in general). Real: dedicated BLAS-1/2 code; complex: \code{*this * NRMat(rhs)}, see section~\ref{sec:smat:hermitian}.} +\lafunc{const NRMat operator*(const NRMat &rhs) const}{$S A$ by \code{dspmv}/\code{zhpmv} column by column.} +\lafunc{const NRVec operator*(const NRVec &rhs) const}{$S x$ (\code{dspmv}/\code{zhpmv}).} +\lafunc{const NRVec > operator*(const NRVec > &rhs) const}{Real symmetric matrix times complex vector (real and imaginary parts separately).} +\lafunc{void gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x) const}{$r \leftarrow \beta r + \alpha S x$, forwards to \code{r.gemv(beta,*this,trans,alpha,x)}; \code{trans} is ignored.} +\lafunc{void gemv(const T beta, NRVec > &r, const char trans, const T alpha, const NRVec > &x) const}{The same, real matrix and complex vectors.} +\lafunc{void axpy(const T alpha, const NRSMat &x)}{$S \leftarrow S + \alpha x$. For hermitian complex matrices the result is hermitian only for real $\alpha$ +(not checked).} +\lafunc{void rank2update(const T alpha, const NRVec &x, const NRVec &y)}{Rank-2 update $S \leftarrow S + \alpha x y\Hc + \bar\alpha y x\Hc$ (real: $\alpha(xy\Tr+yx\Tr)$); \code{dspr2}/\code{zhpr2} for \code{double}/\code{complex}, generic loop otherwise. A symmetric rank-1 update $S \leftarrow S + \beta x x\Hc$ (real $\beta$) is obtained as \code{rank2update(beta/2,x,x)}; there is no separate rank-1 member.} +\lafunc{const T dot(const NRSMat &rhs) const}{$\sum_{i\ge j} \bar A_{ij} B_{ij}$, i.e.\ the dot product of the \emph{packed} vectors (off-diagonal elements counted once; \code{ddot}/\code{zdotc}).} +\lafunc{const T dot(const NRVec &rhs) const}{Dot product of the packed data with a vector of length $n(n+1)/2$.} +\lafunc{const typename LA_traits::normtype norm(const T scalar = (T)0) const}{Euclidean norm of the packed data of $S - \mathtt{scalar}\cdot 1$; off-diagonal elements are counted \emph{once}, so this is not the Frobenius norm of the full matrix.} +\lafunc{const T trace() const}{Trace.} +\lafunc{const T sum(bool onlytriangle=false) const}{Sum of all elements of the full matrix, or of the stored triangle only.} +\lafunc{const NRVec rsum() const; const NRVec csum() const}{Row sums and column sums of the full matrix (equal for real, complex conjugate of each other for hermitian matrices).} +\lafunc{inline const T amax() const; inline const T amin() const}{Stored element with the largest/smallest absolute value (complex: $|\Re|+|\Im|$); \code{double} and \code{complex} only.} +\lafunc{const T* diagonalof(NRVec &r, const bool divide = 0, bool cache = false) const}{\code{divide=false}: store the diagonal into \code{r} and return \code{&r[0]}; \code{divide=true}: divide \code{r} elementwise by the nonzero diagonal elements and return \code{NULL}. \code{cache} is unused. Interface used by iterative solvers (\ref{ch:iterative}).} +\lafunc{NRSMat inverse()}{Inverse via \code{NRMat(*this).inverse()}, symmetrized back. \code{double} and \code{complex} only.} +\lafunc{NRSMat& conjugateme(); const NRSMat conjugate() const}{Complex conjugation of all stored elements (no-op for real types).} +\lafunc{NRSMat::normtype> abs() const}{Elementwise absolute values.} +\lafunc{NRSMat::normtype> diffabs(const NRSMat &rhs) const}{Elementwise $|A_{ij}|-|B_{ij}|$.} +\lafunc{void randomize(const typename LA_traits::normtype &x)}{Uniformly distributed elements in $(-x,x)$; complex: real and imaginary parts independent, imaginary part of the diagonal set to zero. \code{double} and \code{complex} only.} +\lafunc{const bool operator==(const NRSMat &rhs) const; const bool operator!=(const NRSMat &rhs) const}{Exact elementwise comparison.} +\end{lareference} + +\section{Other member functions}\label{sec:smat:other} +\subsection{Submatrices and permutations} +\begin{lareference} +\lafunc{const NRSMat submatrix(const int from, const int to) const}{Diagonal block with rows/columns \code{from}\ldots\code{to} (inclusive).} +\lafunc{const NRSMat submatrix(const NRVec &selection) const}{Matrix $B_{ij} = A_{s_i s_j}$ for the index list $s$ = \code{selection}.} +\lafunc{void storesubmatrix(const int from, const NRSMat &rhs)}{Store \code{rhs} as diagonal block starting at \code{from}.} +\lafunc{void storesubmatrix(const NRVec &selection, const NRSMat &rhs)}{$A_{s_i s_j} = B_{ij}$.} +\lafunc{const NRSMat permuted(const NRPerm &p, const bool inverse=false) const}{Symmetric permutation of rows and columns by \code{p} (indices from 1, see \ref{ch:permutation}): \code{inverse=false} gives $B_{p_i p_j}=A_{ij}$, \code{inverse=true} gives $B_{ij}=A_{p_i p_j}$.} +\end{lareference} + +\subsection{Input/output} +See section~\ref{sec:concepts:io} for the general conventions. +\begin{lareference} +\lafunc{void put(int fd, bool dimensions = 1, bool transp = 0, bool orcaformat=false) const}{Binary output: if \code{dimensions}, $n$ is written twice (as for a square \code{NRMat}), followed by \code{sizeof(T)} if \code{orcaformat}; then the $n(n+1)/2$ packed elements. \code{transp} is ignored.} +\lafunc{void get(int fd, bool dimensions = 1, bool transp = 0, bool orcaformat=false)}{Binary input matching \code{put}; with \code{dimensions=false} the current order is used.} +\lafunc{void fprintf(FILE *f, const char *format, const int modulo) const}{Formatted output of the full square matrix by \code{lawritemat} (as \code{NRMat::fprintf}, see \ref{ch:mat}).} +\lafunc{void fscanf(FILE *f, const char *format)}{Reads the dimensions (must be equal) and then the \emph{whole square} matrix row by row; this is intentional, the data are assumed symmetric. Since $(i,j)$ and $(j,i)$ share the storage, the element read later, i.e.\ the lower triangle one, wins.} +\lafunc{void printsorted(std::ostream &s, int direction=1, bool absvalue=false, bool stable=false, typename LA_traits::normtype thr=0, int topmax=0) const}{Print the stored elements sorted with their indices, see \code{NRVec::printsorted} in \ref{ch:vec}.} +\end{lareference} + +\section{Related free functions}\label{sec:smat:free} +\begin{lareference} +\lafunc{std::ostream& operator<<(std::ostream &s, const NRSMat &x)}{Text output: \code{n n}, then the full square matrix row by row.} +\lafunc{std::istream& operator>>(std::istream &s, NRSMat &x)}{Text input of the same format; the full square is read, the lower triangle element wins (as in \code{fscanf}). Error if the dimensions differ.} +\lafunc{const NRSMat operator+(const T &a, const NRSMat &rhs); const NRSMat operator*(const T &a, const NRSMat &rhs)}{Scalar from the left: $a\cdot 1 + S$, $aS$.} +\lafunc{const NRSMat operator-(const T &a, const NRSMat &rhs) = delete}{Deliberately not available (compile error); write \code{-S+a} for $a\cdot1-S$.} +\lafunc{NRSMat > complexify(const NRSMat &rhs)}{Complex copy of a real matrix.} +\lafunc{void NRSMat_convert(NRSMat &a, const NRSMat &b)}{Element type conversion (\code{a} is resized, elements converted by a cast).} +\end{lareference} +Functions in other headers taking \code{NRSMat} (described in \ref{ch:nonclass}): \code{diagonalize()} +(\code{dspev}/\code{zhpev}, also generalized problem), \code{linear_solve()}, \code{trace2()}, +\code{twoside_transform()} ($C\Hc S C$), \code{matrixfunction()}, \code{sqrt()}, \code{sqrtinv()}, \code{log()}, +\code{realpart()}, \code{imagpart()}. \code{NRMat} has \code{operator+=}, \code{operator-=}, +\code{operator+}, \code{operator-} and \code{operator*} with an \code{NRSMat} right operand, and +\code{NRVec} has \code{gemv} with an \code{NRSMat} and \code{operator*(const NRSMat&)} (vector from the left); +see \ref{ch:mat} and \ref{ch:vec}. + +\section{NRSMat\_from1}\label{sec:smat:from1} +\code{NRSMat_from1} is derived from \code{NRSMat} and differs only in \code{operator()}, whose +indices run from 1 to $n$ (\code{SMat_index_1}). \code{operator[]} and everything else are inherited and +still count from 0; arithmetic operators return \code{NRSMat}, which converts back implicitly. +\begin{lareference} +\lafunc{NRSMat_from1(); explicit NRSMat_from1(const int n); NRSMat_from1(const T &a, const int n); NRSMat_from1(const T *a, const int n)}{As for \code{NRSMat} (the last one inherits the bug of the base constructor).} +\lafunc{NRSMat_from1(const NRSMat &rhs)}{Implicit conversion from the base class (shallow).} +\lafunc{explicit NRSMat_from1(const NRMat &rhs); explicit NRSMat_from1(const NRVec &rhs, const int n)}{As for \code{NRSMat}.} +\lafunc{const T& operator()(const int i, const int j) const; T& operator()(const int i, const int j)}{Element $(i,j)$, $1\le i,j\le n$. Range checked in DEBUG; unlike the base class the non-const version does not check the reference count. Plain storage access as in the base class (no conjugation, section~\ref{sec:smat:hermitian}).} +\end{lareference} + +\section{Examples}\label{sec:smat:examples} +\begin{lstlisting} +#include "la.h" +using namespace LA; + +int main() +{ +const int n=4; +NRSMat s(n); +for(int i=0; i x(1.,n); +NRVec y = s*x; //dspmv +NRMat full(s); //full square copy +NRSMat t = s; //shares data with s +t.copyonwrite(); t(0,3) = 5.; //same storage as t(3,0); s unchanged +t.rank2update(0.5,x,y); //t += 0.5*(x y^T + y x^T) +const NRSMat &sc = s; +double sum=0; for(size_t k=0; k w(n); NRMat v(n,n); +NRSMat work(s); //diagonalize() detaches work itself +diagonalize(work,w,&v); //eigenvalues/vectors, work is overwritten +std::cout << s << w; +} +\end{lstlisting} + +\section{Pitfalls}\label{sec:smat:pitfalls} +\begin{itemize} +\item \code{size()} is $n(n+1)/2$, use \code{nrows()} for the order. +\item \code{dot()} and \code{norm()} work on the packed triangle; for the Frobenius quantities of the + full matrix use e.g.\ \code{NRMat(s).norm()} or \code{trace2()}. +\item For complex types read section~\ref{sec:smat:hermitian} carefully. +\item \code{NRSMat(const NRVec&,int)} and \code{NRVec(const NRSMat&)} share data with their argument. +\item \code{NRSMat(const NRMat&)} silently symmetrizes (takes the hermitian part for complex types), it does not check symmetry. +\item Complex matrices: \code{operator()(i,j)} with $i}; converting back with \code{NRSMat(...)} symmetrizes. +\item \code{operator=(const T&)} produces a scalar (diagonal) matrix, whereas the constructor \code{NRSMat(a,n)} fills all elements. +\end{itemize} diff --git a/doc/sparse.tex b/doc/sparse.tex new file mode 100644 index 0000000..e79208d --- /dev/null +++ b/doc/sparse.tex @@ -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} (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} (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} (section~\ref{sec:sparse:semisparse}): a dense diagonal plus a + \code{SparseMat} off-diagonal part, just enough to be used in iterative eigensolvers. +\end{itemize} +\code{CSRMat} (section~\ref{sec:sparse:csr}) is unfinished. + +Common definitions in \code{sparsemat.h}: +\begin{lareference} +\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 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}. +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} (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 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 &rhs)}{From a dense matrix, elements with $|a_{ij}|>$ \code{SPARSEEPSILON}.} +\lafunc{explicit SparseMat(const NRSMat &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(const SparseMat&)} (symmetric matrices are unfolded, +duplicates summed), \code{explicit NRSMat(const SparseMat&)} (requires the symmetric flag, duplicates summed), +\code{explicit NRVec(const SparseMat&)} (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 *getlist() const; void setlist(matel *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&}, \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::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 &rhs) const}{$\sum_{ij} a_{ij} b_{ij}$ with a dense matrix; \code{double} only.} +\lafunc{const T* diagonalof(NRVec &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 operator*(const NRVec &rhs) const}{$A x$.} +\lafunc{void gemv(const T beta, NRVec &r, const char trans, const T alpha, const NRVec &x, bool treat_as_symmetric=false) const}{$r \leftarrow \beta r + \alpha\,\mathrm{op}(A)x$; forwards to \code{NRVec::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 operator*(const NRMat &rhs) const}{$A B$ via \code{NRMat::gemm} with a sparse first operand; \code{double} only (\code{complex} 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::gemv(const T beta, const SparseMat &a, const char trans, const T alpha, const NRVec &x, const bool treat_as_symmetric=false)}, +\code{NRVec::operator*(const SparseMat&)} ($x\Tr A$), +\code{NRMat::gemm(const T &beta, const SparseMat &a, const char transa, const NRMat &b, const char transb, const T &alpha)} +(\code{double} only) and \code{NRMat::operator+=}, \code{operator-=(const SparseMat&)}. + +\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 &p)}{Square matrix: $(i,j)\to(p_i,p_j)$, indices from 0.} +\lafunc{void permuterows(const NRVec &p); void permutecolumns(const NRVec &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 &p, const bool inverse=false); void permuteme_cols(const NRPerm &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 &p, const NRPerm &q, const bool inverse=false)}{Rows by \code{p}, columns by \code{q} (same index convention).} +\lafunc{const SparseMat permuted_rows(const NRPerm &p, const bool inverse=false) const; const SparseMat permuted_cols(const NRPerm &p, const bool inverse=false) const; const SparseMat permuted_both(const NRPerm &p, const NRPerm &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 &rhs); const SparseMat oplus(const NRMat &rhs) const}{Direct sum with a dense matrix (general \code{*this} only).} +\lafunc{SparseMat & oplusequal(const NRSMat &rhs); const SparseMat oplus(const NRSMat &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 &rhs) const; const SparseMat otimes(const NRSMat &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::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>} 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 &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 &x)}{Text input of the same format; the result is a general (not symmetric) matrix.} +\lafunc{inline const SparseMat commutator(const SparseMat &x, const SparseMat &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 anticommutator(const SparseMat &x, const SparseMat &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>}. 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 diagonal; SparseMat 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 &r, const char trans, const T alpha, const NRVec &x, bool treat_as_symmetric=false) const; NRVec operator*(const NRVec &rhs) const}{Matrix-vector product.} +\lafunc{const T* diagonalof(NRVec &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 &x)}{Prints both parts.} +\lafunc{explicit NRMat(const SemiSparseMat &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}; 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 &rhs)}{From a packed symmetric (complex: hermitian) matrix (both triangles inserted, the upper one conjugated, $|a_{ij}|>$ \code{SPARSEEPSILON}).} +\lafunc{explicit SparseSMat(const NRMat &rhs)}{From a dense $n\times m$ matrix, nonzero elements copied as they are (no symmetrization).} +\lafunc{explicit SparseSMat(const SparseMat &rhs)}{From a \code{SparseMat} (same dimensions); duplicates are summed, a symmetric \code{SparseMat} is unfolded without conjugation (complex symmetric, as \code{SparseMat} defines it).} +\lafunc{explicit SparseSMat(const CSRMat &rhs)}{Declared but not implemented (\code{CSRMat} is not supported).} +\lafunc{~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(const SparseSMat&)} copies all stored elements; +\code{explicit NRSMat(const SparseSMat&)} (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 *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*} (\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} \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 &r, const char trans, const T alpha, const NRVec &x) const}{$r\leftarrow\beta r+\alpha Ax$ (general $A$); only \code{trans='n'} is implemented.} +\lafunc{inline const NRVec operator*(const NRVec &rhs) const}{$Ax$.} +\lafunc{typename LA_traits::normtype norm(const T scalar=(T)0) const}{Frobenius norm of $A-\mathtt{scalar}\cdot1$ over all stored elements.} +\lafunc{const T* diagonalof(NRVec &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 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 &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 &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 otimes_sparse(const NRVec &lhs, const NRVec &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 NRMat::operator*(const SparseSMat &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}.} +\end{lareference} +In \ref{ch:nonclass}: \code{complexmatrix()}, \code{realmatrix()} and \code{imagmatrix()} build a +\code{SparseSMat>} 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} 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 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 x(1.,4); +NRVec y = a*x; //sparse * dense vector +NRMat d(a); //dense copy +for(SparseMat::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 s(4,4); +s.setsymmetric(); +s.add(2,0,1.5); s.add(3,3,1.); +NRVec z = s*x; //uses s(2,0) and s(0,2) +NRSMat sd(s); //dense packed copy + +//shared matrices: detach before add() +SparseMat b(a); //shares the list with a +b.copyonwrite(); +b.add(2,2,3.); + +//map-based sparse matrix +SparseSMat h(4); +h.add(0,1,0.5); //adds (0,1) and (1,0) +h.add(2,2,1.); +SparseSMat h2 = h*h; //result stays symmetric +NRVec w = h*x; +std::cout << y << z << w << NRMat(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} diff --git a/doc/tensor.tex b/doc/tensor.tex new file mode 100644 index 0000000..d870de5 --- /dev/null +++ b/doc/tensor.tex @@ -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} 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} and \code{Tensor >} + 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 >} 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 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 shape}{the index groups; \code{shape.size()==0} for a scalar (rank 0).} +\lafunc{NRVec 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 groupsizes; NRVec cumsizes}{redundant, precomputed from \code{shape} by \code{calcsize()}.} +\lafunc{NRVec 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 FLATINDEX}{all indices of an element in flat order.} +\lafunc{typedef NRVec SUPERINDEX}{indices structured by groups: \code{I[g][i]}.} +\lafunc{typedef NRVec GROUPINDEX}{one position (inside the compressed group storage) per group.} +\lafunc{typedef NRMat 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 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 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 &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 &s, const NRVec &newnames); Tensor(const INDEXGROUP &g, const NRVec &newnames)}{the same with index names; \code{newnames} must be empty or of length \code{rank()}.} +\lafunc{Tensor(const NRVec &s, const NRVec &mydata); Tensor(const NRVec &s, const NRVec &mydata, const NRVec &newnames)}{use \code{mydata} as the data (shared, not copied); its size must match the shape.} +\lafunc{Tensor(int xrank, const NRVec &xshape, const NRVec &xgroupsizes, const NRVec xcumsizes, const NRVec &xdata); Tensor(..., const NRVec &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 &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 &x)}{rank-1 tensor, offset 0, data shared with \code{x}.} +\lafunc{explicit Tensor(const NRMat &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 &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(x))}. If \code{NRSMat} gets the planned \code{hermitian} flag (section~\ref{sec:smat:hermitian}), this constructor has to respect it.} +\lafunc{NRMat 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(x).matrix()} reproduces \code{x}.} +\lafunc{template Tensor fourindex2tensor(const fourindex_dense &f)}{conversion from a dense four-index object (chapter~\ref{ch:fourindex}), see the table below; indices get offset 1.} +\lafunc{template void tensor2fourindex(const Tensor &t, fourindex_dense &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=" lhs(LA_index i1...); Signedpointer lhs(const FLATINDEX &I); Signedpointer 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 &shape); int flatposition(const INDEX &i, const NRVec &shape); INDEX indexposition(int flatindex, const NRVec &shape)}{free versions for a given shape.} +\lafunc{LA_largeindex subindex(int *sign, const INDEXGROUP &g, const NRVec &I)}{position of the index tuple \code{I} inside the storage of one group, with sign as for \code{index()}.} +\lafunc{NRVec 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::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::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 &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 &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 &nl1, const NRVec &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} (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 &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 &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 &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 &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 &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 &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 &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 &rhsvec, const PermutationAlgebra &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 > Tucker(typename LA_traits::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 > &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 > &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 &x) const; Tensor linear_transform(const int g, const NRMat &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(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 findindexlist(const NRVec &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 std::ostream & operator<<(std::ostream &s, const Tensor &x)}{text output: the shape, the names, then one line per stored element with its flat indices and value.} +\lafunc{template std::istream & operator>>(std::istream &s, Tensor &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 T signeddata(const int sgn, const T &data)}{apply a sign code ($\pm1$, $\pm2$ = with conjugation, 0 gives 0) to a value.} +\lafunc{template 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 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 &tc=t; +cout << tc(1,2,3) << " " << tc(3,2,1) << " " << tc(1,1,2) << endl; // -5 5 0 +Tensor 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 shape(4); for(int i=0; i<4; ++i) shape[i]=s; +Tensor 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 z = x.contractions(y,NRVec(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 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 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 a(sh); a.randomize(1.); +Tensor a0(a); a0.copyonwrite(); // keep the original +NRVec > u = a.Tucker(1e-12); // a now holds the core tensor +Tensor b = a.inverseTucker(u); // b == a0 +Tensor c = a.linear_transform(u); // the same +\end{lstlisting} + +Conversion from a dense four-index object (chapter~\ref{ch:fourindex}): +\begin{lstlisting} +fourindex_dense f(n); +f.randomize(1.); +Tensor t4 = fourindex2tensor(f); // f(i,j,k,l) == t4(k,l,i,j), indices from 1 +fourindex_dense 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(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} diff --git a/doc/utilities.tex b/doc/utilities.tex new file mode 100644 index 0000000..9e4c5b0 --- /dev/null +++ b/doc/utilities.tex @@ -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} 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}, \dots); it will not work if \code{T} is a +class with dynamically allocated components (e.g.\ \code{NRVec >} or \code{NRVec >}), +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 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 &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 &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 &x, const int pos); void get(NRMat &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 &x, const int pos); void get(NRSMat &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 st; //temporary file AUX_xxxxxx in $TMPDIR or the working directory +NRVec 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 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::elements()}.} +\lafunc{template 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 &f)}{Prints the coefficients separated by spaces.} +\end{lareference} + +Example (from \header{t.cc}), fit of a parabola: +\begin{lstlisting} +simple_linfit 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= "< NRIMat; typedef NRVec NRIVec; +typedef NRMat NRDMat; typedef NRVec NRDVec; +typedef NRMat > NRCMat; +typedef NRVec > 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 = "< : public std::list>}{List of (prime, exponent) pairs in increasing order.} +\lafunc{FACTORIZATION 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 &x)}{Prints \code{(p,e) (p,e) ...}.} +\lafunc{N eulerphi(const FACTORIZATION &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&)} returning a \code{G}. They are instantiated only for +\code{G = NRSMat} (undirected graphs, chapter~\ref{ch:smat}). Vertices are numbered from 0; +diagonal elements are ignored. +\begin{lareference} +\lafunc{NRVec neighbors(const G &adjacency, const int v); NRVec nonneighbors(const G &adjacency, const int v)}{Sorted lists of vertices adjacent / not adjacent to $v$ (excluding $v$).} +\lafunc{NRVec 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 complement(const G &adjacency, const NRVec &vertexlist)}{Vertices not contained in \code{vertexlist}.} +\lafunc{NRVec 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 adj; +cin >> adj; +NRVec clique = findclique(adj); +NRVec cover = cliquecover(adj); +NRPerm p(cover.size()); +cover.sort(0,p,true); //permutation grouping the cliques +NRSMat adjperm = adj.permuted(p); //block structure +\end{lstlisting} diff --git a/doc/vec.tex b/doc/vec.tex new file mode 100644 index 0000000..d020c76 --- /dev/null +++ b/doc/vec.tex @@ -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} 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}), as a general resizable container of arbitrary +elements (including nested \LA{} objects such as \code{NRVec >}), and as the storage backbone of other classes +(\code{NRMat} and \code{NRSMat} can be viewed as vectors without copying, see Sec.~\ref{sec:vec:construct}). +\code{NRVec_from1} (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}. 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&)}, \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} (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::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 NRVec(const T (&a)[SIZE])}{Copy of a C array; also accepts a braced list, e.g.\ +\code{NRVec 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 &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 l)}{Copy of the elements of a \code{std::list} (the list is passed by value).} +\lafunc{NRVec(const typename LA_traits_complex::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 >} from \code{NRVec}.} +\lafunc{explicit NRVec(const NRSMat &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 &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 &rhs)}{Dense vector from a sparse matrix having one row or one column (otherwise +\code{laerror}); duplicate entries are summed. Defined in \texttt{sparsemat.cc} for \code{double} and \code{std::complex}.} +\lafunc{~NRVec()}{Decrements the reference count, frees the data when it reaches zero.} +\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::clear}); detaches shared plain data.} +\lafunc{void swap(NRVec &rhs)}{Exchange contents (size, data, counter) of two vectors in $O(1)$.} +\lafunc{template NRVec > complexify(const NRVec &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 void NRVec_convert(NRVec &a, const NRVec &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 &selection) const}{Gather into a new vector: \code{r[i] = x[selection[i]]}. +Index range is always checked.} +\lafunc{void subvector_from(const NRVec &selection, const NRVec &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 &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 &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::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} 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} and +\code{std::complex}).} +\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}.} +\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}.} +\lafunc{void gemv(const T beta, const NRMat &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 &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 &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}.) 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 &mat) const}{Row vector times matrix, $x\Tr A$ (gemv with \code{'t'}; no conjugation). +For $A x$ see \code{NRMat::operator*} in Chapter~\ref{ch:mat}.} +\lafunc{const NRVec operator*(const NRSMat &mat) const}{Row vector times packed symmetric/hermitian matrix, $x\Tr S$ (for hermitian $S$ computed as $S\Tr x = S^* x$ by \code{gemv} with \code{'t'}).} +\lafunc{const NRVec operator*(const SparseMat &mat) const}{Row vector times sparse matrix; CPU only.} +\lafunc{const NRMat otimes(const NRVec &rhs, const bool conjugate = false, const T &scale = 1) const}{Outer product +$A_{ij}=\mathrm{scale}\cdot x_i\, y_j$, with $y_j^*$ if \code{conjugate} (complex only); BLAS ger/geru/gerc.} +\lafunc{const NRMat operator|(const NRVec &rhs) const}{Same as \code{otimes(rhs,true)}, i.e.\ $x y\Hc$.} +\lafunc{const NRVec otimes2vec(const NRVec &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}; 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::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}.} +\lafunc{NRVec& normalize(typename LA_traits::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}.} +\lafunc{const NRVec unitvector() const}{Normalized copy.} +\lafunc{const typename LA_traits::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}.} +\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}. \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}. \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::normtype> abs() const}{Vector of $|x_i|$.} +\lafunc{NRVec::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::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 &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::normtype thr=0, int topmax=0, const NRMat *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 &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 &p, const bool inverse=false); void permuteme(const CyclePerm &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 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 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::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}.} +\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::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 &x)}{Text output: the size on one line, then the elements +separated by spaces on the next line (converted to \code{LA_traits_io::IOtype}, so e.g.\ \code{char} is printed as a number).} +\lafunc{std::istream & operator>>(std::istream &s, NRVec &x)}{Reads the format written by \code{<<}; resizes \code{x}.} +\lafunc{template void NRVec_simplify(NRVec &x, const typename LA_traits::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::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 LA_traits::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} is derived publicly from \code{NRVec} 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 NRVec_from1(const T (&a)[SIZE]); explicit NRVec_from1(const std::list l)}{As for \code{NRVec}.} +\lafunc{NRVec_from1(const NRVec &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 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 x({1.,2.,3.}); +NRVec y(x); // shares data with x +y *= 2.; // y gets its own copy, x unchanged +double d = x*y; // scalar product = 28 +NRVec z = x + y*0.5; +z.axpy(-1.,x); // z = x +std::cout << d << " " << z.norm() << std::endl; + +NRVec w(0.,5); // five zeros +NRVec sel({4,0,2}); +w.storesubvector(sel,x); // scatter: w[4]=x[0], w[0]=x[1], w[2]=x[2] +NRVec 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 f(1.,3); +f[3] = 5.; // last element +std::cout << f << f.iamax() << std::endl; // prints 3 + +NRVec > c(x,true); // purely imaginary +std::cout << c << complexify(x); +} +\end{lstlisting} + +Sorting with the permutation returned: +\begin{lstlisting} +NRVec v(12); +v.randomize(1.); +NRPerm p(v.size()); +NRVec 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 x(n)} is not initialized; use \code{NRVec 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} (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} diff --git a/doc/vecmat3.tex b/doc/vecmat3.tex new file mode 100644 index 0000000..ff0ed2c --- /dev/null +++ b/doc/vecmat3.tex @@ -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} and \code{Mat3} 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{}, 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} holds the public array \code{T q[3]}, \code{Mat3} 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 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 &rhs) const}{Row vector times matrix, $\mathbf{q}^{\mathsf T} M$.} +\lafunc{Vec3 timesT(const Mat3 &rhs) const}{$\mathbf{q}^{\mathsf T} M^{\mathsf T}$, i.e.\ $M\mathbf{q}$.} +\lafunc{Mat3 outer(const Vec3 &rhs) const}{Outer (tensor) product $\mathbf{q}\,\mathbf{rhs}^{\mathsf T}$.} +\lafunc{void addouter(Mat3 &m, const Vec3 &rhs, const T weight) const}{$m \mathrel{+}= w\,\mathbf{q}\,\mathbf{rhs}^{\mathsf T}$.} +\lafunc{void inertia(Mat3 &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 &c, const Vec3 &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 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 a(5)} into a $3\times3$ matrix; +write \code{Mat3(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 operator*(const Vec3 &rhs) const; Vec3 Ttimes(const Vec3 &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 linear_solve(const Vec3 &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 &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 &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 &rhs); void diagmultr(const Vec3 &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 &x); std::istream &operator>>(std::istream &s, Vec3 &x)}{Three numbers separated by spaces (no dimensions, no newline).} +\lafunc{std::ostream &operator<<(std::ostream &s, const Mat3 &x); std::istream &operator>>(std::istream &s, Mat3 &x)}{Nine numbers, one row per line on output.} +\lafunc{void euler2rotmat(const T *eul, Mat3 &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 &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 &point, const Mat3 &rot_angle, const Vec3 &camera, const Vec3 &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.