LaTeX manual
This commit is contained in:
1 parent
176562f088
commit
648c8d1061
30 files changed
+6090
No files matched your search
@@ -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).
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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).
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -0,0 +1,21 @@
|
||||
%bibliography; the items of each chapter are kept in bib/<chapter>.tex
|
||||
\begin{thebibliography}{99}
|
||||
\addcontentsline{toc}{chapter}{Bibliography}
|
||||
\InputIfFileExists{bib/concepts}{}{}
|
||||
\InputIfFileExists{bib/vec}{}{}
|
||||
\InputIfFileExists{bib/mat}{}{}
|
||||
\InputIfFileExists{bib/smat}{}{}
|
||||
\InputIfFileExists{bib/sparse}{}{}
|
||||
\InputIfFileExists{bib/nonclass}{}{}
|
||||
\InputIfFileExists{bib/iterative}{}{}
|
||||
\InputIfFileExists{bib/optimize}{}{}
|
||||
\InputIfFileExists{bib/tensor}{}{}
|
||||
\InputIfFileExists{bib/fourindex}{}{}
|
||||
\InputIfFileExists{bib/permutation}{}{}
|
||||
\InputIfFileExists{bib/polynomial}{}{}
|
||||
\InputIfFileExists{bib/vecmat3}{}{}
|
||||
\InputIfFileExists{bib/quaternion}{}{}
|
||||
\InputIfFileExists{bib/bitvector}{}{}
|
||||
\InputIfFileExists{bib/regression}{}{}
|
||||
\InputIfFileExists{bib/utilities}{}{}
|
||||
\end{thebibliography}
|
||||
@@ -0,0 +1,123 @@
|
||||
\chapter{Bit vectors and polynomials over GF(2)}\label{ch:bitvector}
|
||||
\laheader{bitvector.h, bitvector.cc}
|
||||
|
||||
Compressed bit vectors, which also represent polynomials over GF(2) and elements of GF($2^n$). \code{bitvector} is a reference-counted class derived from \code{NRVec} (see section~\ref{sec:concepts:refcount}).
|
||||
|
||||
\section{Purpose and storage}
|
||||
\code{bitvector} is a compressed vector of bits stored in 64-bit blocks
|
||||
(\code{typedef uint64_t bitvector_block}, macro \code{blockbits}=64). It is publicly derived from
|
||||
\code{NRVec<bitvector_block>}, so it shares its reference counting and copy-on-demand
|
||||
behaviour; the number of blocks is \code{NRVec::size()} of the base, the number of bits is
|
||||
the (hiding) \code{bitvector::size()}. Bit $i$ is bit $i \bmod 64$ of block $\lfloor i/64\rfloor$
|
||||
(least significant first). The private member \code{modulo} $=n \bmod 64$ remembers the
|
||||
bit length; the unused high bits of the last block (``padding'') may contain garbage and are
|
||||
cleared by \code{zero_padding()} where needed. \code{bitvector_rounded(n)} rounds $n$ up to a
|
||||
multiple of 64.
|
||||
|
||||
Besides a plain bit set, a \code{bitvector} is interpreted as a polynomial over GF(2): bit $i$ is
|
||||
the coefficient of $x^i$. Addition and subtraction are XOR, multiplication and division are
|
||||
polynomial operations, and with an irreducible polynomial the field GF($2^n$) is available.
|
||||
A comment in the header plans a \code{monic_bitvector} class (not storing the leading 1, so that
|
||||
field operations need no resizing); it is \emph{not implemented}.
|
||||
|
||||
\section{Copy on demand}
|
||||
Copy constructor and \code{operator=} share the data (\code{operator=} is compiler-generated,
|
||||
calling \code{NRVec::operator=}), \code{|=} of the base class makes a deep copy (but beware it is
|
||||
overloaded as bitwise OR here, see Pitfalls). The following members call \code{copyonwrite()}:
|
||||
\code{clear}, \code{fill}, \code{randomize}, \code{operator++}, \code{operator--}, \code{&=}, \code{|=}, \code{^=}
|
||||
(with a bitvector argument), \code{>>=}, \code{leftshift}/\code{<<=}; \code{resize} detaches.
|
||||
The single-bit and block setters do \emph{not}: \code{set}, \code{reset}, \code{flip}, \code{assign},
|
||||
\code{setblock}, and \code{&=}, \code{|=}, \code{^=} with a \code{bitvector_block} argument (also the
|
||||
\code{bitmatrix} and \code{bitvector_from1} wrappers of \code{set}/\code{reset}/\code{flip}/\code{assign}).
|
||||
They are element-level access by design (used in inner loops) and behave like writes through \code{operator[]}:
|
||||
call \code{x.copyonwrite()} once before using them on a possibly shared vector. In DEBUG builds they raise \code{laerror}
|
||||
when the data are shared (count check as in \code{NRVec::operator[]}).
|
||||
|
||||
\section{Reference: construction, access}
|
||||
\begin{lareference}
|
||||
\lafunc{bitvector(); explicit bitvector(const unsigned int n)}{Empty; $n$ bits, cleared.}
|
||||
\lafunc{bitvector(const bitvector_block a, const unsigned int n)}{$n$ bits, every \emph{block} set to \code{a}.}
|
||||
\lafunc{bitvector(const bitvector &rhs)}{Shallow copy.}
|
||||
\lafunc{explicit bitvector(const uint8_t *data, const unsigned int n); void getdata(uint8_t *data)}{Import/export $n$ bits from/to a byte array (\code{(n+7)/8} bytes, bit $i$ in byte \code{i/8}); little-endian machines only (laerror otherwise).}
|
||||
\lafunc{void resize(const unsigned int n, bool preserve=false)}{New bit length; with \code{preserve} keep the old bits and zero the new ones, otherwise clear everything.}
|
||||
\lafunc{unsigned int size() const}{Number of bits.}
|
||||
\lafunc{const bool operator[](const unsigned int i) const; const bool get(const unsigned int i) const}{Read bit $i$ (no assignable non-const \code{[]} exists).}
|
||||
\lafunc{bitvector_block getblock(const unsigned int i) const; void setblock(const unsigned int i, const bitvector_block b); int getblocksize() const}{Raw block access, block size in bits (64).}
|
||||
\lafunc{void set(const unsigned int i); void reset(const unsigned int i); void flip(const unsigned int i); const bool assign(const unsigned int i, const bool r)}{Set to 1, to 0, negate, assign bit $i$ (range checked in DEBUG; no copyonwrite, see above).}
|
||||
\lafunc{void clear(); void fill()}{All bits 0 / 1.}
|
||||
\lafunc{void zero_padding() const}{Clear the unused bits of the last block (logically const).}
|
||||
\lafunc{bool is_zero() const; bool iszero() const; bool is_one() const}{Test for the zero vector / the polynomial 1.}
|
||||
\lafunc{void randomize()}{Random bits via \code{RANDINT64} (chapter~\ref{ch:utilities}).}
|
||||
\lafunc{void read(int fd, bool dimensions=1, bool transp=0); void write(int fd, bool dimensions=1, bool transp=0)}{Binary I/O on a file descriptor: \code{modulo} followed by \code{NRVec::get}/\code{put} (section~\ref{sec:concepts:io}).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Reference: bitwise and integer operations}
|
||||
\begin{lareference}
|
||||
\lafunc{bitvector &operator++(); bitvector &operator--()}{Increment/decrement as a multi-word binary integer (bit 0 least significant), wrapping around.}
|
||||
\lafunc{bool operator==(const bitvector &) const; bool operator!=(const bitvector &) const}{Equality; vectors of different length are equal iff the extra bits of the longer one are zero.}
|
||||
\lafunc{bool operator<(const bitvector &) const; operator>, operator<=, operator>=}{Comparison as unsigned integers (bit $i$ has the weight $2^i$, the same reading as \code{getblock} and \code{++}), only for equal lengths (laerror otherwise).}
|
||||
\lafunc{bitvector operator~() const}{Bitwise negation.}
|
||||
\lafunc{bitvector &operator&=(const bitvector &); operator|=, operator^=; operator&, operator|, operator^}{Bitwise operations; \code{*this} is enlarged (zero-filled) if \code{rhs} is longer.}
|
||||
\lafunc{bitvector &operator&=(const bitvector_block rhs); operator|=, operator^=}{Bitwise operation with the \emph{first block} only.}
|
||||
\lafunc{bitvector &operator>>=(unsigned int i); bitvector operator>>(unsigned int i) const}{Logical right shift (towards bit 0), length kept.}
|
||||
\lafunc{bitvector &leftshift(unsigned int i, bool autoresize=false); bitvector &operator<<=(unsigned int i); bitvector operator<<(unsigned int i) const}{Logical left shift; \code{<<} and \code{<<=} enlarge the vector by $i$ bits (autoresize), \code{leftshift} by default keeps the length and drops the top bits.}
|
||||
\lafunc{unsigned int population(const unsigned int before=0) const}{Hamming weight (number of ones); \code{before}$\neq0$ is not implemented (laerror).}
|
||||
\lafunc{unsigned int bitdiff(const bitvector &y) const}{Hamming distance (same number of blocks required).}
|
||||
\lafunc{unsigned int nlz() const; unsigned int ntz() const}{Number of leading (from the top bit) and trailing zeros.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Reference: polynomials over GF(2) and GF($2^n$)}
|
||||
\begin{lareference}
|
||||
\lafunc{bitvector operator+(const bitvector &) const; operator-, operator+=, operator-=}{Addition = subtraction = XOR.}
|
||||
\lafunc{bitvector multiply(const bitvector &rhs, bool autoresize=true) const; bitvector operator*(const bitvector &rhs) const; bitvector &operator*=(const bitvector &rhs)}{Polynomial product (naive shift-and-add, no carry-less multiply instructions; for short vectors). With \code{autoresize} the result has \code{size()+rhs.size()} bits, otherwise \code{max} of the sizes (the caller guarantees no overflow).}
|
||||
\lafunc{bitvector pow(unsigned int n) const}{$p^n$ by repeated squaring.}
|
||||
\lafunc{bitvector division(const bitvector &rhs, bitvector &remainder) const; bitvector operator/(const bitvector &) const}{Polynomial division with remainder (\texttt{operator\%} returns the remainder); the remainder is resized to \code{rhs.size()}. Division by zero: laerror.}
|
||||
\lafunc{bitvector gcd(const bitvector &rhs) const; bitvector lcm(const bitvector &rhs) const}{Greatest common divisor (Euclid) and least common multiple.}
|
||||
\lafunc{bitvector composition(const bitvector &rhs) const}{$p(r(x))$ by the Horner scheme.}
|
||||
\lafunc{unsigned int degree() const}{Polynomial degree (0 for the zero polynomial).}
|
||||
\lafunc{void truncate(int t=0)}{Resize to \code{max(degree()+1, t)} bits, preserving data.}
|
||||
\lafunc{bool is_irreducible() const}{Irreducibility test over GF(2) (Rabin-type test: $x^{2^d}\equiv x$ and $\gcd(x^{2^{d/p}}-x, f)=1$ for prime divisors $p$ of the degree $d$, cf.\ Brent--Zimmermann; uses \code{factorization} of section~\ref{sec:utilities:numbers}).}
|
||||
\lafunc{bitvector field_mult(const bitvector &rhs, const bitvector &irpolynom) const}{Product in GF($2^n$)$\,=\,$GF(2)[x]/(irpolynom), $n=\deg(\mathrm{irpolynom})$; the operands must have at least $n$ bits.}
|
||||
\lafunc{bitvector field_inv(const bitvector &irpolynom) const; bitvector field_div(const bitvector &rhs, const bitvector &irpolynom) const}{Multiplicative inverse (extended Euclid; laerror if the polynomial is reducible or the input is 0 modulo it) and division in GF($2^n$).}
|
||||
\lafunc{bitvector field_pow(unsigned int n, const bitvector &irpolynom) const; bitvector field_sqrt(const bitvector &irpolynom) const}{Power and square root ($\sqrt a=a^{2^{n-1}}$) in GF($2^n$).}
|
||||
\lafunc{bitvector field_composition(const bitvector &rhs, const bitvector &irpolynom) const}{$p(r)$ evaluated in GF($2^n$).}
|
||||
\lafunc{bitvector find_irreducible(int deg, int pop=-1, int nth=1)}{Free function: an irreducible polynomial of degree \code{deg}. With \code{pop}$=-1$ random trials; otherwise the \code{nth} one (in the order of a recursive search) with Hamming weight \code{pop} (odd, $\ge3$); returns a zero vector if none was found.}
|
||||
\end{lareference}
|
||||
|
||||
\section{bitmatrix, bitvector\_from1 and helper functions}
|
||||
\code{bitmatrix} (derived from \code{bitvector}) stores an $n\times m$ bit matrix row-wise
|
||||
(bit \code{i*m+j}). \code{bitvector_from1} is a \code{bitvector} indexed from 1.
|
||||
\begin{lareference}
|
||||
\lafunc{bitmatrix(); bitmatrix(unsigned int nn0, unsigned int mm0); void resize(const unsigned int n, const unsigned int m)}{Construct/resize (data cleared).}
|
||||
\lafunc{unsigned int nrows() const; unsigned int ncols() const}{Dimensions.}
|
||||
\lafunc{const bool operator()(const unsigned int i, const unsigned int j) const}{Read element.}
|
||||
\lafunc{void set(i,j); void reset(i,j); void flip(i,j); const bool assign(i,j,const bool r)}{Write element (no copyonwrite).}
|
||||
\lafunc{bitvector_from1(); bitvector_from1(const bitvector &rhs); explicit bitvector_from1(const unsigned int n)}{Constructors.}
|
||||
\lafunc{operator[](i); get(i); set(i); reset(i); assign(i,r); population(before)}{As in \code{bitvector}, with indices shifted by one. (Note \code{operator[]} and \code{get} are non-const here.)}
|
||||
\lafunc{void bitvector_expand(const bitvector &v, NRVec<T> &r); void bitvector_compress(bitvector &r, const NRVec<T> &v)}{Convert to/from a vector of 0/1 values of type \code{T}.}
|
||||
\lafunc{bitvector mantissa(T x, int nbits, int shift=0)}{Binary expansion of $x\,2^{\mathrm{shift}}\in[0,1)$ (laerror otherwise): bit $i$ ($1\le i<\mathrm{nbits}$) is the coefficient of $2^{-i}$.}
|
||||
\lafunc{void bitvector_decimal(T &x, const bitvector &b, int shift=0)}{Inverse of \code{mantissa}: $x=2^{\mathrm{shift}}\sum_i b_i 2^{-i}$.}
|
||||
\lafunc{std::ostream &operator<<(std::ostream &, const bitvector &); std::istream &operator>>(std::istream &, bitvector &)}{Text I/O as a string of 0/1 characters, bit 0 first, no length (input: one whitespace-delimited token, any character other than \code{'0'} is 1).}
|
||||
\lafunc{std::ostream &operator<<(std::ostream &, const bitmatrix &); std::istream &operator>>(std::istream &, bitmatrix &)}{Dimensions, then rows of 0/1 (input: whitespace-separated integers).}
|
||||
\end{lareference}
|
||||
\code{LA_traits<bitvector>} declares \code{elementtype} and \code{normtype} \code{bool}.
|
||||
|
||||
\section{Example}
|
||||
\begin{lstlisting}
|
||||
int d=13;
|
||||
bitvector ir=find_irreducible(d,3); //trinomial, if any
|
||||
if(ir.is_zero()) ir=find_irreducible(d,5);
|
||||
bitvector a(d);
|
||||
do a.randomize(); while(a.is_zero());
|
||||
bitvector ai = a.field_inv(ir);
|
||||
if(!a.field_mult(ai,ir).is_one()) laerror("GF(2^n) inversion failed");
|
||||
bitvector s = a.field_sqrt(ir); //s*s == a in GF(2^13)
|
||||
bitvector u(100),v(100);
|
||||
u.randomize(); v.randomize();
|
||||
bitvector w=u*v; //polynomial product, 200 bits
|
||||
if(!(w%u).is_zero() || !(w/u-v).is_zero()) laerror("division");
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}
|
||||
\lawarning{\code{|=} is bitwise OR here, not the deep copy of the base class \code{NRVec}; to get an independent copy use \code{bitvector b(a); b.copyonwrite();}.}
|
||||
\lawarning{Bits beyond \code{size()} in the last block may be nonzero after \code{fill()}, \code{setblock()} or the block constructor; most predicates call \code{zero_padding()}, but use it explicitly before raw block access.}
|
||||
@@ -0,0 +1,121 @@
|
||||
\chapter{Building and using the library}\label{ch:building}
|
||||
|
||||
\section{Requirements}
|
||||
|
||||
\begin{itemize}
|
||||
\item a C++11 capable compiler (the library is developed with GNU g++),
|
||||
\item BLAS and LAPACK (Fortran interface); optionally the C interfaces CBLAS and CLAPACK,
|
||||
or Intel MKL, or ATLAS,
|
||||
\item GNU autotools (autoconf, automake, libtool) when building from the git repository,
|
||||
\item optionally NVIDIA CUBLAS (experimental, section~\ref{sec:concepts:gpu}),
|
||||
\item optionally doxygen for the generated source code reference, which is independent of this manual.
|
||||
\end{itemize}
|
||||
|
||||
\section{Configuration and installation}
|
||||
|
||||
\begin{lstlisting}[language=bash]
|
||||
autoreconf --install # only if ./configure does not exist yet
|
||||
./configure [options] CXXFLAGS=-I<path> LDFLAGS=-L<path>
|
||||
make # library, and this manual and the doxygen pages if pdflatex/doxygen were found
|
||||
make check # builds the test programs, runs testoptimize
|
||||
make install
|
||||
\end{lstlisting}
|
||||
\code{CXXFLAGS} and \code{LDFLAGS} should point to the preferred BLAS/LAPACK installation; after
|
||||
running \code{configure}, check in the generated \header{Makefile} that the intended library was found.
|
||||
\code{./configure --disable-optimize CXXFLAGS='' LDFLAGS=''} gives a fast compilation for development.
|
||||
|
||||
\code{configure} looks for the libraries in the following order and sets the preprocessor macros
|
||||
accordingly:
|
||||
\begin{enumerate}
|
||||
\item Intel MKL (\code{mkl_intel_lp64}, \code{mkl_sequential}, \code{mkl_core} and the header
|
||||
\header{mkl_cblas.h}); if found, \code{-DHAS_MKL -DNONCLAPACK} is used;
|
||||
\item otherwise ATLAS (optional) and the reference BLAS (mandatory), CBLAS (if \code{cblas_ddot} and \header{cblas.h}
|
||||
are not found, \code{-DNONCBLAS} is set) and LAPACK (mandatory; if the C interface \code{clapack_dgesv}
|
||||
with \header{clapack.h} is not found, \code{-DNONCLAPACK} is set);
|
||||
\item CUBLAS: if \code{cublasInit} and \header{cublas.h} are found, the GPU support is switched on
|
||||
(\code{-DCUDALA}) and the C interfaces of BLAS and LAPACK are not used.
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Configure options}
|
||||
|
||||
\begin{longtable}{@{}p{0.36\linewidth}p{0.6\linewidth}@{}}
|
||||
\toprule
|
||||
\textbf{Option} & \textbf{Effect} \\
|
||||
\midrule
|
||||
\endhead
|
||||
\code{--disable-debug} & do not define \code{DEBUG}; by default range checks and other consistency checks are compiled in (section~\ref{sec:concepts:errors}) \\
|
||||
\code{--enable-optimize=<flags>} & compiler optimization flags, default \code{-O3 -finline-limit=1000}; \code{--disable-optimize} for none \\
|
||||
\code{--enable-profile} & compile with \code{-pg} \\
|
||||
\code{--enable-matptr} & define \code{MATPTR}: \code{NRMat} stores an array of row pointers (\code{T**}), see chapter~\ref{ch:mat}; incompatible with CUDA \\
|
||||
\code{--enable-fortran64int} & define \code{LONG_FORTRAN_INT} to link with BLAS and LAPACK using 64-bit integers; implies \code{NONCBLAS} and \code{NONCLAPACK} (the old misspelling \code{--enable-fotran64int} is accepted as an alias) \\
|
||||
\bottomrule
|
||||
\end{longtable}
|
||||
|
||||
\subsection{Preprocessor macros}
|
||||
|
||||
The macros below are set by \code{configure} in the \header{Makefile}. Since most of the library consists of
|
||||
templates compiled in the user's code, \emph{a program using the library must be compiled with the same
|
||||
macros} (at least \code{DEBUG}, \code{MATPTR}, \code{CUDALA}, \code{NONCBLAS}, \code{NONCLAPACK},
|
||||
\code{HAS_MKL}, \code{LONG_FORTRAN_INT}, \code{FORTRAN_}), otherwise the object layouts or the called
|
||||
functions do not match.
|
||||
|
||||
\begin{longtable}{@{}p{0.25\linewidth}p{0.71\linewidth}@{}}
|
||||
\toprule
|
||||
\textbf{Macro} & \textbf{Meaning} \\
|
||||
\midrule
|
||||
\endhead
|
||||
\code{DEBUG} & range checks of indices, dimension checks, the check of the reference count in non-const element access \\
|
||||
\code{NONCBLAS} & no CBLAS library: the \code{cblas_*} functions used by the library are provided by the wrappers in \header{noncblas.cc}, which call the Fortran BLAS \\
|
||||
\code{NONCLAPACK} & the same for the C interface of LAPACK (the library calls the Fortran LAPACK routines) \\
|
||||
\code{HAS_MKL} & use the headers of Intel MKL \\
|
||||
\code{FORTRAN_} & Fortran routine names have a trailing underscore (always set in \header{Makefile.am}) \\
|
||||
\code{LONG_FORTRAN_INT} & Fortran integers are 64-bit (\code{long}) \\
|
||||
\code{MATPTR} & row pointer representation of \code{NRMat} \\
|
||||
\code{CUDALA} & GPU support via CUBLAS (experimental) \\
|
||||
\code{NO_STRASSEN} & do not compile the experimental Strassen matrix multiplication interface (\header{strassen.cc}, needs an external library); always set in \header{Makefile.am} \\
|
||||
\bottomrule
|
||||
\end{longtable}
|
||||
|
||||
The headers \header{fortran.h}, \header{noncblas.h}, \header{cuda_la.h}, \header{config.h} and
|
||||
\header{LA_version.h} are internal to the library and are not documented in this manual.
|
||||
|
||||
\section{Compiling and linking user programs}
|
||||
|
||||
Include \header{la.h} (or the individual headers) and link with \code{-lla}, followed by the
|
||||
libraries found by \code{configure}, e.g.
|
||||
\begin{lstlisting}[language=bash]
|
||||
g++ -O3 -DDEBUG -DFORTRAN_ -DNONCLAPACK -I$PREFIX/include prog.cc \
|
||||
-L$PREFIX/lib -lla -llapack -lcblas -lblas
|
||||
\end{lstlisting}
|
||||
The variables \code{AM_CXXFLAGS} and \code{AM_LDFLAGS} in the generated \header{Makefile} show the exact
|
||||
options used for the library itself.
|
||||
|
||||
The string \code{LA::version} (declared in \header{LA_version.h}) contains the git version, the commit date
|
||||
and the time and place of compilation of the library; it is generated by the script \header{get_git_version}
|
||||
at every \code{make}.
|
||||
|
||||
\section{Test programs}
|
||||
|
||||
\code{make check} builds the programs
|
||||
\begin{description}
|
||||
\item[\code{t}] a large collection of tests and usage examples of nearly all parts of the library
|
||||
(\header{t.cc}, \header{t2.cc}); the individual tests are switched on and off by \code{if(0)} / \code{if(1)}
|
||||
blocks in the source, many of them read their input from the standard input;
|
||||
\item[\code{test}] further small tests (\header{test.cc});
|
||||
\item[\code{tX}] a test with X11 graphics (\header{tX.cc});
|
||||
\item[\code{test_reg}, \code{test_regsurf}] tests of the regression module;
|
||||
\item[\code{testoptimize}] the regression test of the optimization module (chapter~\ref{ch:optimize}); it
|
||||
prints PASS/FAIL lines and its exit code is the number of failed checks.
|
||||
\end{description}
|
||||
\code{make check} builds all these programs and runs \code{testoptimize} (automake \code{TESTS}); its result is in
|
||||
\header{test-suite.log}.
|
||||
|
||||
\section{Documentation}
|
||||
|
||||
This manual is in the subdirectory \header{doc}. \code{make manual} (alias \code{make doc}) compiles it by running
|
||||
\code{pdflatex} three times in the scratch directory \header{doc/.build} and copies the result to \header{doc/manual.pdf}; this
|
||||
is done only when a \code{.tex} file is newer than the PDF, and a \LaTeX{} error stops the build with the end of the log.
|
||||
A source code reference is generated by doxygen with the configuration file \header{doxygen.cfg} by \code{make doxygen}
|
||||
(output in \header{doxygen-doc}, rebuilt when a source file changes); it is independent of this manual.
|
||||
\code{make} (i.e.\ \code{make all}) builds both in addition to the library, each only if \code{configure} found
|
||||
\code{pdflatex} resp.\ \code{doxygen}; \code{make clean} removes \header{doc/.build} and \header{doxygen-doc} (not the PDF).
|
||||
@@ -0,0 +1,278 @@
|
||||
\chapter{Common concepts}\label{ch:concepts}
|
||||
|
||||
This chapter describes the design common to all classes of the library. The class chapters refer to
|
||||
it and describe only what is specific to the class.
|
||||
|
||||
\section{Reference counting and copy on demand}\label{sec:concepts:refcount}
|
||||
|
||||
\subsection{Shared data}
|
||||
|
||||
An object of \code{NRVec}, \code{NRMat}, \code{NRSMat} and of all other classes holding large data
|
||||
(\code{SparseMat}, \code{SparseSMat}, \code{Tensor}, \code{fourindex}, \code{Polynomial}, the permutation
|
||||
classes, \code{bitvector}, \dots) consists of a small header (dimensions, a pointer to the data and a pointer
|
||||
to a reference counter) and the data block. The copy constructor and the assignment operator between objects
|
||||
of the same type copy only the header and increment the reference counter, so that both objects share the
|
||||
same data:
|
||||
\begin{lstlisting}
|
||||
NRVec<double> a(1.,1000); //1000 elements equal to 1
|
||||
NRVec<double> b(a); //no copy of the data, a.getcount()==2
|
||||
NRVec<double> c;
|
||||
c = a; //no copy, count is 3 now
|
||||
\end{lstlisting}
|
||||
The data are deallocated when the last object referring to them is destroyed or assigned a different
|
||||
value. The current value of the counter is returned by \code{getcount()}.
|
||||
|
||||
Consequently, objects can be passed to and returned from functions by value at low cost, and expressions
|
||||
like \code{x = A*x} are safe, since the result is computed into a new temporary object which is then
|
||||
assigned to \code{x}.
|
||||
|
||||
\subsection{Copy on demand}
|
||||
|
||||
Before shared data are modified, the object must obtain its own copy of them. This is done by the member
|
||||
function
|
||||
\begin{lareference}
|
||||
\lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{If the reference count is larger than one,
|
||||
allocate a private copy of the data (copy them unless \code{detachonly}, which only allocates
|
||||
uninitialized memory) and decrement the count of the shared block. For nested element types (see
|
||||
section~\ref{sec:concepts:traits}) it is applied recursively to the elements when \code{deep} is true, also when the
|
||||
outer object is not shared. For an object with count one it does nothing (apart from the recursion).}
|
||||
\end{lareference}
|
||||
|
||||
The rules are:
|
||||
\begin{enumerate}
|
||||
\item \emph{Every member function and operator of the library which modifies the data of an object as a whole calls
|
||||
\code{copyonwrite()} itself} (\code{axpy}, \code{gemv}, \code{gemm}, \code{+=}, \code{*=}, assignment of a
|
||||
scalar, \code{randomize}, \code{storesubmatrix}, \code{resize}, \dots). Explicit calls before these
|
||||
functions are not needed.
|
||||
\item \emph{Before writing to individual elements through \code{operator[]}, \code{operator()}, a raw pointer}
|
||||
(\code{operator T*()}, \code{&x[0]}, \dots) \emph{or a member function accessing individual elements} (such as
|
||||
\code{swap(i,j)} of \code{NRVec}, used in sorting loops) the user has to call \code{copyonwrite()} explicitly.
|
||||
In the first cases the library cannot distinguish a write access from a read access; element-level members do not call it
|
||||
by design, since a \code{copyonwrite()} inside inner loops over individual elements would be an unacceptable overhead:
|
||||
\begin{lstlisting}
|
||||
NRVec<double> b(a); //b shares data with a
|
||||
b.copyonwrite(); //b gets its own copy
|
||||
b[0] = 5.; //a is not changed
|
||||
\end{lstlisting}
|
||||
Without the \code{copyonwrite()}, the assignment would silently change \code{a} as well.
|
||||
The rule applies to every object which might share its data: objects obtained by copying or assignment,
|
||||
objects received as function arguments by reference or pointer (they may share data with other copies in
|
||||
the caller), objects stored in other containers. A freshly constructed object (by a constructor taking
|
||||
dimensions) has count one, but calling \code{copyonwrite()} for it costs nothing.
|
||||
\item \code{resize()} detaches an object from shared data: a shared object always gets newly allocated
|
||||
memory (with the old contents only if their preservation is requested), even when the size does not change.
|
||||
An object which has just been resized can be written to. \code{resize(0)} deallocates.
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{The DEBUG check of the reference count}
|
||||
|
||||
In programs compiled with \code{DEBUG}, the non-const versions of \code{operator[]} and \code{operator()}
|
||||
of the basic classes call \code{laerror} (section~\ref{sec:concepts:errors}) when the reference count is larger
|
||||
than one, since this is a possible write to shared data:
|
||||
\begin{lstlisting}
|
||||
NRVec<double> c = a;
|
||||
c[1] = 7.; //DEBUG: LA:ERROR - possible use of NRVec[] with count>1 as l-value
|
||||
\end{lstlisting}
|
||||
The check cannot distinguish reading from writing. Reading elements of a possibly shared object should
|
||||
therefore be done through a const reference, which selects the const version of the operator:
|
||||
\begin{lstlisting}
|
||||
void f(NRVec<double> &x) //non-const, x may share data with another vector
|
||||
{
|
||||
const NRVec<double> &xc = x;
|
||||
double s = xc[0] + xc[1]; //no false alarm
|
||||
x.copyonwrite();
|
||||
x[0] = s; //write
|
||||
}
|
||||
\end{lstlisting}
|
||||
The check can be switched off at run time by setting the global variable \code{LA::_LA_count_check=false}.
|
||||
It is a safety net only: writes through raw pointers and writes in programs compiled without \code{DEBUG}
|
||||
are not checked.
|
||||
|
||||
\lawarning{A missing \code{copyonwrite()} does not cause a crash, it silently modifies other objects which share
|
||||
the data. It is the most frequent error when using the library. A library member function which modifies
|
||||
data without calling \code{copyonwrite()} is a bug of the library.}
|
||||
|
||||
\subsection{Objects referring to external memory}
|
||||
|
||||
The constructor \code{NRVec(T *a, int n, bool skeleton)} with \code{skeleton=true} (and analogous
|
||||
constructors of other classes, see the class chapters) creates an object which refers to memory owned by
|
||||
somebody else, without copying. Its reference count is set to two, so that the memory is never deallocated by
|
||||
the library and any modification by a library function first makes a private copy (writes through
|
||||
\code{operator[]} go to the external memory, but are reported by the DEBUG check). It is useful for applying
|
||||
library functions to data stored elsewhere. Such an object is intended as a \emph{read-only view} of the external data.
|
||||
|
||||
\section{Shallow and deep copy}\label{sec:concepts:assign}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{X(const X &rhs); X &operator=(const X &rhs)}{shallow copy: share the data with \code{rhs}
|
||||
(section~\ref{sec:concepts:refcount}).}
|
||||
\lafunc{X &operator|=(const X &rhs)}{deep copy: \code{*this = rhs} followed by \code{copyonwrite()}; this object gets its own copy
|
||||
of the data, recursively for nested types.}
|
||||
\lafunc{X &operator=(const T &a)}{assignment of a scalar. For vectors every element is set to \code{a}; for square matrices
|
||||
(\code{NRMat}, \code{NRSMat}) the matrix is set to $a\,\mathbf{1}$, i.e.\ the diagonal is set to \code{a} and the
|
||||
off-diagonal elements to zero. In the same spirit \code{+=} and \code{-=} with a scalar add to the diagonal of a matrix.}
|
||||
\end{lareference}
|
||||
The deep copy is needed when the copy will be modified by element access, or when a function
|
||||
destroys its argument (e.g.\ \code{diagonalize}):
|
||||
\begin{lstlisting}
|
||||
NRMat<double> a(n,n);
|
||||
...
|
||||
NRMat<double> asave;
|
||||
asave |= a; //a will be overwritten
|
||||
\end{lstlisting}
|
||||
\code{x.swap(y)} exchanges the contents of two objects without any copying.
|
||||
|
||||
Conversions between different classes (e.g.\ \code{NRSMat} $\to$ \code{NRMat}, \code{NRMat} $\to$ \code{NRVec},
|
||||
real $\to$ complex) are done by explicit constructors and always create new data, unless stated otherwise
|
||||
in the class chapters.
|
||||
|
||||
\section{Traits and element types}\label{sec:concepts:traits}
|
||||
|
||||
\laheader{la_traits.h}
|
||||
|
||||
The algorithms of the library are written generically with the help of the traits class template
|
||||
\code{LA_traits<T>}, which is defined for all scalar types and for all container classes of the library.
|
||||
Its main members are:
|
||||
\begin{lareference}
|
||||
\lafunc{typedef ... elementtype}{type of the elements (for a scalar the type itself)}
|
||||
\lafunc{typedef ... normtype}{type of the norm, e.g.\ \code{double} for \code{std::complex<double>} and for \code{NRMat<std::complex<double> >}}
|
||||
\lafunc{typedef ... realtype; typedef ... complextype}{the corresponding real and complex type, e.g.\ \code{NRVec<double>} and \code{NRVec<std::complex<double> >}}
|
||||
\lafunc{typedef ... producttype}{type of the product of two objects (for \code{NRSMat} it is \code{NRMat})}
|
||||
\lafunc{static bool is_plaindata()}{true for scalar types, which can be copied by \code{memcpy}, false for the classes}
|
||||
\lafunc{static bool is_complex()}{whether the (innermost) element type is complex}
|
||||
\lafunc{static normtype norm(const T &x); static void axpy(T &s, const T &x, const elementtype c)}{norm and $s \leftarrow s + c\,x$, used by generic algorithms such as \code{matexp.h}}
|
||||
\lafunc{static void copyonwrite(T &x); static void clearme(T &x); static void deallocate(T &x)}{generic versions of the member functions (no operation for scalars)}
|
||||
\lafunc{static void put(int fd, const T &x, ...); static void get(int fd, T &x, ...); multiput; multiget}{binary I/O, recursive for nested types}
|
||||
\lafunc{static T conjugate(const T &x); realpart(x); imagpart(x); setrealpart(x,y); setimagpart(x,y)}{complex helpers, trivial for real types}
|
||||
\lafunc{static bool bigger(const T &x, const T &y); smaller(x,y)}{comparison used for sorting; for complex types it is an error}
|
||||
\end{lareference}
|
||||
The auxiliary template \code{isscalar<T>} distinguishes scalars from classes, \code{LA_traits_complex<T>}
|
||||
gives for a complex type the component type and the corresponding real vector and matrix types (used by
|
||||
the mixed real--complex functions such as \code{gemv} of a complex vector with a real matrix), and
|
||||
\code{LA_traits_io<T>} makes \code{char} types be read and written as numbers by the stream operators.
|
||||
The functions \code{LA::real(c)} and \code{LA::imag(c)} return references to the real and imaginary part of a
|
||||
\code{std::complex}, which can be used as l-values.
|
||||
|
||||
\subsection{Element types and instantiation}
|
||||
|
||||
The class templates are explicitly instantiated in the library for \code{double}, \code{std::complex<double>}
|
||||
and the integer types \code{char}, \code{short}, \code{int}, \code{long}, \code{long long} and their unsigned
|
||||
variants. The BLAS and LAPACK backed functions are implemented for \code{double} and \code{std::complex<double>}
|
||||
(some also for mixed real--complex arguments); calling them for other types results in a run-time
|
||||
\code{laerror} (``not implemented for this type'') or a link error. Functions implemented in the headers work
|
||||
for any type with the necessary arithmetic operations.
|
||||
|
||||
\subsection{Nested types}
|
||||
|
||||
The element type can be a class of the library, e.g.\ \code{NRVec<NRMat<double> >} (a list of matrices) or
|
||||
\code{NRMat<NRVec<double> >}. Copy on demand is then applied on both levels: \code{copyonwrite()} of the outer
|
||||
object copies the inner objects shallowly and calls their \code{copyonwrite()}, so that \code{|=} makes a
|
||||
complete deep copy; \code{put} and \code{get} write and read the inner objects including their dimensions.
|
||||
Arithmetic operations of the outer class (e.g.\ \code{axpy}) work elementwise via the traits.
|
||||
\lawarning{Writing into an inner object obtained through \code{operator[]} of the outer object
|
||||
(\code{v[i](j,k)=x}) requires both objects to be unshared: call \code{v.copyonwrite()} (which recursively
|
||||
detaches the inner objects as well) before.}
|
||||
|
||||
\section{Storage conventions}\label{sec:concepts:storage}
|
||||
|
||||
\begin{itemize}
|
||||
\item Indices run from 0. The classes \code{NRVec_from1}, \code{NRMat_from1} and \code{NRSMat_from1} are
|
||||
derived from the basic classes and differ only in their element access operators, which take indices from 1.
|
||||
They convert to and from their base classes without copying.
|
||||
\item \code{NRVec}: contiguous array of \code{size()} elements.
|
||||
\item \code{NRMat}: row major (C) order, element $(i,j)$ of an $n\times m$ matrix is at position $i\,m+j$;
|
||||
\code{a[i]} is a pointer to the $i$-th row. Note that BLAS and LAPACK use column major order, so the
|
||||
library internally works with the transposed matrices; the user does not need to care, except when calling
|
||||
Fortran routines directly.
|
||||
\item \code{NRSMat}: lower triangle of a symmetric or hermitian matrix, packed by rows, element $(i,j)$ with
|
||||
$i \ge j$ is at position $i(i+1)/2+j$ (which is also the upper triangle packed by columns, the format of the
|
||||
BLAS/LAPACK packed routines). The macros/functions \code{SMat_index} compute these positions.
|
||||
\item \code{Tensor}: the leftmost index changes fastest (chapter~\ref{ch:tensor}).
|
||||
\end{itemize}
|
||||
All classes can return a raw pointer to their data (e.g.\ \code{operator T*()} of \code{NRVec} and \code{NRMat}),
|
||||
for passing them to external routines; the copy on demand rule for writes through pointers applies.
|
||||
|
||||
\section{GPU support (experimental)}\label{sec:concepts:gpu}
|
||||
|
||||
When compiled with \code{CUDALA} (configure finds CUBLAS), the data of \code{NRVec}, \code{NRMat} and
|
||||
\code{NRSMat} can reside either in the main memory or in the memory of a GPU. The location is described by
|
||||
the enumeration
|
||||
\begin{lstlisting}
|
||||
typedef enum {undefined=-1, cpu=0, gpu1=1, gpu2=2, gpu3=3, gpu4=4} GPUID;
|
||||
\end{lstlisting}
|
||||
Constructors take an optional location argument (default \code{undefined}, which means the global default
|
||||
location \code{DEFAULT_LOC}, settable by \code{set_default_loc()}); \code{getlocation()} returns the location
|
||||
and \code{moveto(loc)} moves the data. BLAS-like operations of objects on the GPU are performed by CUBLAS;
|
||||
operands of a binary operation must reside in the same location. Operations which are not implemented for the
|
||||
GPU call \code{laerror} (``Operation not implemented on GPU (yet). Use .moveto(0) first.''); element access
|
||||
by \code{operator[]} and \code{operator()} is possible only in the main memory. Without \code{CUDALA},
|
||||
\code{getlocation()} always returns \code{cpu} and \code{moveto()} does nothing, so that code written for
|
||||
the GPU compiles also without it.
|
||||
|
||||
\lawarning{The GPU support is experimental and has not been thoroughly tested. It is not documented in
|
||||
detail in this manual; the class chapters at most mention operations restricted to the CPU.}
|
||||
The GPU code is currently not maintained: it uses the legacy CUBLAS API (\code{cublasInit}) and a compute capability
|
||||
(\code{-arch sm_20}) not supported by current CUDA versions, and the \code{CUDALA} build has known compile errors. It will be revisited
|
||||
with current NVIDIA hardware.
|
||||
|
||||
\section{Error handling and DEBUG checks}\label{sec:concepts:errors}
|
||||
|
||||
\laheader{laerror.h, laerror.cc}
|
||||
|
||||
Errors are reported by the macro \code{laerror(message)}, which calls \code{LA::laerror2(message, function)}
|
||||
with the name of the calling function. \code{laerror2} prints the message and the function name to both
|
||||
\code{std::cout} and \code{std::cerr}, the system error message if \code{errno} is set, the command line, the
|
||||
user name, host name and working directory (and the list of files in the working directory if
|
||||
\code{LA_error_list_files} is true), and then throws an exception of the class
|
||||
\begin{lstlisting}
|
||||
class LAerror : public std::exception {public: std::string msg; ... const char *what() const noexcept;};
|
||||
\end{lstlisting}
|
||||
which can be caught and printed by \code{operator<<}:
|
||||
\begin{lstlisting}
|
||||
try { ... }
|
||||
catch(LAerror &e) {std::cerr << "error: " << e << std::endl;}
|
||||
\end{lstlisting}
|
||||
Errors detected by BLAS, CBLAS and ATLAS (via \code{xerbla} and its variants) are converted to \code{laerror}
|
||||
as well. Users may call \code{laerror} in their own code. The function \code{sigtraceback(signal,flags)}
|
||||
installs a handler printing a stack trace (by running \code{gdb} on the process) when the given signal arrives,
|
||||
e.g.\ \code{sigtraceback(SIGSEGV,1)}; see chapter~\ref{ch:utilities}.
|
||||
|
||||
With \code{DEBUG} defined, the library checks index ranges, dimensions of operands, allocation of objects
|
||||
and the reference count in element access (section~\ref{sec:concepts:refcount}). Without \code{DEBUG}, these
|
||||
errors lead to undefined behaviour. Programs should be developed and tested with \code{DEBUG}.
|
||||
|
||||
\section{Input and output}\label{sec:concepts:io}
|
||||
|
||||
All classes support three kinds of I/O:
|
||||
\begin{description}
|
||||
\item[Stream operators] \code{operator<<} and \code{operator>>} write and read a human readable text format:
|
||||
the dimensions followed by the elements (a matrix by rows, one row per line). The input operator resizes the
|
||||
object. \code{char} elements are written as numbers.
|
||||
\item[\code{fprintf}, \code{fscanf}] formatted output with a \code{printf} format for the elements and input
|
||||
from a C \code{FILE*} (see the class chapters).
|
||||
\item[\code{put}, \code{get}] unformatted binary I/O to a POSIX file descriptor:
|
||||
\begin{lstlisting}
|
||||
void put(int fd, bool dimensions=1, bool transp=0, bool orca_format=false) const;
|
||||
void get(int fd, bool dimensions=1, bool transp=0, bool orca_format=false);
|
||||
\end{lstlisting}
|
||||
With \code{dimensions} true, a header with the dimensions is written before the data and \code{get} resizes the
|
||||
object accordingly; otherwise nothing but the elements is written and the object must already have the right size.
|
||||
The format is the binary representation of the machine (no conversion of endianity). Per class:
|
||||
\begin{itemize}
|
||||
\item \code{NRVec}: header $n$ and a padding \code{int} 1 (8-byte alignment), then the $n$ elements; \code{transp} is ignored.
|
||||
\item \code{NRMat}: header \code{nrows}, \code{ncols}, then the elements by rows. With \code{transp} the transposed matrix is
|
||||
written (dimensions swapped, elements column by column); \code{get} with \code{transp} reads such data back into the
|
||||
original orientation, i.e.\ it reads a file stored by columns.
|
||||
\item \code{NRSMat}: header $n$, $n$, then the packed lower triangle; \code{transp} is ignored.
|
||||
\item \code{SparseMat}: header \code{nrows}, \code{ncols} (\code{SPMatindex}) and the \code{int} flags \code{symmetric} and
|
||||
\code{nonzero}, then triples (row, column, element) and a terminating pair of indices $-1$; \code{SparseSMat}: header
|
||||
\code{nrows}, \code{ncols}, triples and the terminating pair. With \code{transp} rows and columns are swapped.
|
||||
\item \code{Tensor} and \code{fourindex} have their own \code{put}/\code{get} (see their chapters).
|
||||
\end{itemize}
|
||||
\code{orca_format} (dense classes only; \code{laerror} for the sparse ones) writes the header of the ORCA program: the
|
||||
dimensions followed by \code{sizeof(T)} instead of the padding; ORCA matrices are stored by columns, so they are read by
|
||||
\code{get(fd,true,true,true)}. Elements of class type (e.g.\ a vector of matrices) are written recursively by their own
|
||||
\code{put}, with the same \code{dimensions} flag; \code{transp} is passed on to them only by the transposed
|
||||
\code{NRMat} and the sparse matrices.
|
||||
\end{description}
|
||||
For storing many objects of the same size in a scratch file, see \code{AuxStorage} (chapter~\ref{ch:utilities}).
|
||||
@@ -0,0 +1,322 @@
|
||||
\chapter{Four-index quantities}\label{ch:fourindex}
|
||||
|
||||
\laheader{fourindex.h, fourindex.cc}
|
||||
|
||||
\section{Purpose and main idea}\label{sec:fourindex:purpose}
|
||||
|
||||
The header provides containers for four-index quantities with permutational symmetry, typically
|
||||
two-electron integrals, coupled-cluster $T_2$ amplitudes and two-particle (transition) density matrices.
|
||||
Only permutationally non-equivalent elements are stored. There are three kinds of classes:
|
||||
\begin{itemize}
|
||||
\item \code{fourindex<I,T>}: in-core \emph{sparse} storage, a reference-counted linked list of
|
||||
elements \code{(i,j,k,l,value)} with indices of type \code{I} (e.g.\ \code{unsigned char} or \code{int});
|
||||
\item \code{fourindex_ext<I,T>}: the same list stored in a binary file, read and written in large
|
||||
page-aligned blocks;
|
||||
\item \code{fourindex_dense<S,T,I>}: \emph{dense} in-core storage for a given symmetry type \code{S},
|
||||
derived from \code{NRMat<T>} or \code{NRSMat<T>} (``supermatrix'' of index pairs).
|
||||
\end{itemize}
|
||||
Iterators run over the stored elements or over all their symmetry-equivalent permutations, so that
|
||||
algorithms like Fock matrix builds can be written without caring about the symmetry.
|
||||
Conversions between \code{fourindex_dense} and the general \code{Tensor} class are described in
|
||||
chapter~\ref{ch:tensor}.
|
||||
|
||||
Indices are opaque to the sparse classes (they are never compared with the dimension), so both 0- and
|
||||
1-based indexing are possible there; the dense classes use indices from 1.
|
||||
|
||||
Only \code{fourindex<unsigned char,double>}, \code{fourindex_ext<unsigned char,double>} and
|
||||
\code{fourindex_dense<nosymmetry,double,unsigned char>} are explicitly instantiated in fourindex.cc;
|
||||
everything else is implemented in the header and instantiated on use, except \code{fscanf()} and
|
||||
\code{fprintf()} which exist only for \code{fourindex<unsigned char,double>}.
|
||||
|
||||
\section{Symmetry types}\label{sec:fourindex:symmetry}
|
||||
|
||||
\begin{lstlisting}
|
||||
typedef enum {undefined_symmetry=-1, nosymmetry=0, ...} fourindexsymtype;
|
||||
\end{lstlisting}
|
||||
Each symmetry type is defined by the list of index permutations (with a sign) generating the equivalent
|
||||
elements, in the static table \code{fourindex_permutations}\allowbreak\code{[fourindex_n_symmetrytypes]}\allowbreak\code{[max_fourindex_permnumbers+1][5]};
|
||||
an entry $(p_0,p_1,p_2,p_3,s)$ means that the element with indices $(I_{p_0},I_{p_1},I_{p_2},I_{p_3})$ equals $s$ times
|
||||
the stored element $(I_0,I_1,I_2,I_3)=(i,j,k,l)$. \code{fourindex_permnumbers[]} gives the number of
|
||||
permutations. Several enumerators are aliases (same value).
|
||||
|
||||
\begin{longtable}{@{}>{\raggedright\arraybackslash}p{0.43\linewidth}r>{\raggedright\arraybackslash}p{0.45\linewidth}@{}}
|
||||
\toprule
|
||||
enumerator(s) & perms & equivalent elements\\
|
||||
\midrule
|
||||
\endhead
|
||||
\code{nosymmetry} & 1 & none\\
|
||||
\code{twoelectronrealmullikan}, \code{twoelectronrealmullikanAA} & 8 & $(ij|kl)=(ji|kl)=(ij|lk)=(kl|ij)=\dots$ (real integrals, Mulliken notation)\\
|
||||
\code{twoelectronrealdirac} & 8 & $\langle ij|kl\rangle=\langle kj|il\rangle=\langle il|kj\rangle=\langle kl|ij\rangle=\langle ji|lk\rangle=\dots$ (Dirac notation)\\
|
||||
\code{T2ijab_aces}, \code{trdm2AA} & 4 & antisymmetric in $i,j$ and in $k,l$\\
|
||||
\code{antisymtwoelectronrealdirac}, \code{rdm2AA} & 8 & $\langle ij\|kl\rangle$: antisymmetric in $i,j$ and in $k,l$, symmetric in the exchange of the pairs\\
|
||||
\code{T2IjAb_aces}, \code{trdm2AB} & 1 & none (different index ranges)\\
|
||||
\code{twoelectronrealmullikanAB} & 4 & symmetric in $i,j$ and in $k,l$, no pair exchange (integrals over two different spin orbital sets)\\
|
||||
\code{T2ijab_unitary} & 8 & antisymmetric in $i,j$ and in $k,l$, antisymmetric in the pair exchange (antihermitean exponent)\\
|
||||
\code{T2IjAb_unitary} & 2 & $(kl,ij)=-(ij,kl)$\\
|
||||
\code{antisymtwoelectronrealdiracAB}, \code{rdm2AB}, \code{twoelectroncomplexmullikan_}\allowbreak\code{without_conjugation} & 2 & symmetric in the pair exchange $(ij,kl)=(kl,ij)$\\
|
||||
\code{twoelectronrealmullikan}\allowbreak\code{reducedsymAA}, \code{rdm2SS} & 4 & $(ij|kl)=(kl|ij)=(ji|lk)=(lk|ji)$ (e.g.\ unitary downfolded CC effective integrals)\\
|
||||
\code{twoelectronrealmullikan}\allowbreak\code{reducedsymAB} & 2 & $(ij|kl)=(ji|lk)$ (e.g.\ spin-summed 2-RDM)\\
|
||||
\code{twoelectroncomplexmullikan} & 4 & $(ij|kl)=(kl|ij)$, $(ji|lk)=(lk|ji)=(ij|kl)^*$\\
|
||||
\bottomrule
|
||||
\end{longtable}
|
||||
The last entry of each permutation is applied by \code{fourindex_permfactor()}: $\pm1$ is a sign, $\pm2$ means complex
|
||||
conjugation (with the sign), used for \code{twoelectroncomplexmullikan}: $(ij|kl)=(kl|ij)=(ji|lk)^*=(lk|ji)^*$.
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template<class I, class T> void symmetry_faktor(const fourindexsymtype symmetry, const union packed_index<I> &index, T &elem)}{multiply \code{elem} by the factor $1/2$ for each coincidence of indices which makes two of the generated permutations identical, so that summing over all permutations generated by a \code{piterator} counts every distinct element exactly once. Implemented for all symmetry types: the element is scaled by $1/n$, $n$ the number of the type's permutations leaving the index tuple unchanged (e.g.\ $1/(1+m)$, $m\in\{0,1,3\}$, for \code{twoelectroncomplexmullikan} and \code{twoelectronrealmullikanreducedsymAA}); for coincidences making an element vanish by antisymmetry no scaling is applied (the generated contributions cancel).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Element records}\label{sec:fourindex:records}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template<class I> struct indiv_index {I i; I j; I k; I l;}}{the four indices (packed with GNU C).}
|
||||
\lafunc{template<class I> union packed_index {I packed[4]; struct indiv_index<I> indiv;}}{the indices accessible by name or as an array.}
|
||||
\lafunc{template<class I, class T> struct matel4 {T elem; matel4 *next; union packed_index<I> index;}}{list element of \code{fourindex}; also the type returned by the permutation iterators.}
|
||||
\lafunc{template<class I, class T> struct matel4stored {T elem; union packed_index<I> index;}}{packed record of the binary file format (used by \code{fourindex::put/get}, \code{fourindex_ext} and \code{fourindex_dense::putext}).}
|
||||
\end{lareference}
|
||||
The binary format is thus a raw sequence of \code{matel4stored<I,T>} records (machine dependent, no header
|
||||
unless written by \code{fourindex::put(fd,true)}).
|
||||
|
||||
\section{In-core sparse storage: \texttt{fourindex<I,T>}}\label{sec:fourindex:fourindex}
|
||||
|
||||
\subsection{Reference counting}
|
||||
|
||||
\code{fourindex} is reference counted: the copy constructor and \code{operator=} share the list,
|
||||
\code{copyonwrite()} copies it if shared. There is no \code{operator|=}.
|
||||
\code{permuteme()}, \code{operator+=} and \code{join()} call \code{copyonwrite()}, \code{resize()} detaches.
|
||||
The \code{add()} functions (and therefore \code{get()} and \code{operator>>}) are element-level access by design:
|
||||
they prepend new elements to this object's list without \code{copyonwrite()}, so call \code{copyonwrite()} once
|
||||
before adding to a possibly shared object (otherwise the copies diverge while sharing one reference count).
|
||||
Writing through the non-const \code{iterator} modifies the shared list; call \code{copyonwrite()} first.
|
||||
A default-constructed \code{fourindex} is empty but defined and can be filled by \code{add()} or \code{get()}.
|
||||
|
||||
\subsection{Construction and basic operations}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{fourindex()}{undefined (no list, symmetry \code{undefined_symmetry}, dimension 0).}
|
||||
\lafunc{fourindex(const I n, const fourindexsymtype symmetry0=undefined_symmetry, const I terminator0= -1, const bool doscaling0=true)}{empty list for dimension (number of orbitals) \code{n}, with symmetry, terminator for text I/O and scaling flag for \code{piterator}.}
|
||||
\lafunc{fourindex(const fourindex &rhs); fourindex & operator=(const fourindex &rhs)}{shallow copy (list shared, all attributes including \code{symmetry} copied).}
|
||||
\lafunc{~fourindex()}{deletes the list when the last reference disappears.}
|
||||
\lafunc{void resize(const I n)}{discard all elements (detach if shared) and set the dimension; \code{n} must be positive.}
|
||||
\lafunc{void dealloc()}{intended to release the list; calls \code{resize(0)}, which raises \code{laerror}.}
|
||||
\lafunc{void copyonwrite()}{copy the list if shared (the order of elements is reversed by copying).}
|
||||
\lafunc{int getcount() const}{reference count (0 if undefined).}
|
||||
\lafunc{I size() const}{the dimension \code{n}, \emph{not} the number of elements.}
|
||||
\lafunc{unsigned long length() const}{number of stored elements.}
|
||||
\lafunc{matel4<I,T> *getlist() const}{the head of the list.}
|
||||
\lafunc{void setsymmetry(fourindexsymtype s); fourindexsymtype getsymmetry() const}{symmetry type.}
|
||||
\lafunc{void setterminator(const I terminator0); I getterminator() const}{index value marking the end of a text list.}
|
||||
\lafunc{void setscaling(const bool doscaling0)}{whether \code{piterator} applies \code{symmetry_faktor}.}
|
||||
\lafunc{void add(const I i, const I j, const I k, const I l, const T elem); void add(const union packed_index<I> &index, const T elem); void add(const I (&index)[4], const T elem); void add(const matel4<I,T> &rhs); void add(const matel4stored<I,T> &rhs)}{prepend one element (no check for duplicates or canonical order). The iteration order is thus the reverse of the insertion order.}
|
||||
\lafunc{fourindex & operator+=(const fourindex &rhs)}{append (copy) all elements of \code{rhs}; dimensions must agree, symmetries are not checked.}
|
||||
\lafunc{fourindex & join(fourindex &rhs)}{move the list of an unshared \code{rhs} to the end of this list; \code{rhs} is emptied. Cheaper than \code{+=}.}
|
||||
\lafunc{void permuteme(const NRPerm<int> &p, const bool inverse=false)}{relabel all indices $i\to p_i$ (orbital permutation); \code{p.size()} must equal the dimension and the indices must be 1-based.}
|
||||
\lafunc{void swap(fourindex &rhs)}{exchange contents without copying.}
|
||||
\end{lareference}
|
||||
\code{+=} and \code{join()} require equal symmetries of the operands (\code{laerror} otherwise); an empty object with undefined symmetry adopts the symmetry of the right-hand side.
|
||||
|
||||
\subsection{Iterators}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{class iterator; iterator begin() const; iterator end() const}{forward iterator over the stored elements; \code{*it} and \code{it->} give \code{matel4<I,T>} (modifiable through a non-const iterator), pre- and postincrement, \code{==}, \code{!=}.}
|
||||
\lafunc{class piterator; piterator pbegin() const; piterator pend() const}{iterates over all permutations of all stored elements as defined by the symmetry type; \code{*p}/\code{p->} give a const \code{matel4<I,T>} with permuted indices and the element multiplied by the sign and, if scaling is on, by \code{symmetry_faktor} for repeated indices. Contributions of all generated elements can therefore simply be accumulated. Only preincrement; test the end by \code{p.notend()} or \code{p.end()} (comparing with \code{pend()} is less efficient).}
|
||||
\lafunc{class qiterator; qiterator qbegin(); qiterator qend() const}{iterates over the \emph{distinct} permutations of each stored element (duplicates arising from equal indices are skipped, no scaling); sign applied. Only preincrement; use \code{notend()}/\code{end()}.}
|
||||
\end{lareference}
|
||||
The symmetry must be set (not \code{undefined_symmetry}) for \code{piterator} and \code{qiterator}.
|
||||
|
||||
\subsection{Input and output}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{unsigned long put(int fd, bool withattr=true) const}{write all elements as \code{matel4stored} records, preceded (if \code{withattr}) by the symmetry and the dimension, each padded to the size of a union of \code{fourindexsymtype}, \code{I} and \code{T}; returns the number of elements.}
|
||||
\lafunc{unsigned long get(int fd, bool withattr=true)}{read the same format until end of file, \emph{adding} the elements to the list; returns their number.}
|
||||
\lafunc{template <class I, class T> std::ostream& operator<<(std::ostream &s, const fourindex<I,T> &x)}{text output: the dimension, lines \code{i j k l value}, and a final line with four terminators and 0.}
|
||||
\lafunc{template <class I, class T> std::istream& operator>>(std::istream &s, fourindex<I,T> &x)}{read the text format; \code{x} is resized to the read dimension (the symmetry is not set) and reading stops at the first line containing the terminator of \code{x} as an index.}
|
||||
\lafunc{void fscanf(FILE *f); void fprintf(FILE *f, char *format) const}{C-style text I/O, only for \code{fourindex<unsigned char,double>}; line format \code{value i j k l}. \code{fscanf} adds elements until a line in which any index equals the terminator (compared after conversion to the index type, so $-1$ in the file matches the default terminator 255); \code{fprintf} applies \code{format} (which must consume a \code{double} and four \code{int}s) to each element and ends with a line \code{0 t t t t} with the terminator $t$. With terminator 0 this is the FCIDUMP-like convention (blocks separated by zero lines, read by consecutive \code{fscanf} calls; a one-electron line $i\,j\,0\,0$ also ends a block).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Disc storage: \texttt{fourindex\_ext<I,T>}}\label{sec:fourindex:ext}
|
||||
|
||||
\code{fourindex_ext} accesses a file of \code{matel4stored<I,T>} records through an already opened file
|
||||
descriptor. It is not copyable (private default constructor, copy constructor and assignment) and is
|
||||
not reference counted. Reading and writing go through one internal page-aligned buffer whose size is
|
||||
a common multiple of the record size, the page size and the file system block size; the class tries to
|
||||
\code{mlock} the buffer (failure ignored). The buffer is shared by all iterators of an object, therefore
|
||||
only one iteration may be active at a time.
|
||||
|
||||
The file may be opened with \code{O_DIRECT} to avoid filling the page cache when a large file is read once
|
||||
sequentially; this is supported: the buffer is page-aligned and its size is a multiple of the page and filesystem
|
||||
block size, as \code{O_DIRECT} requires (tested on Linux 6.6 with ext4, also for a file size that is not a multiple of
|
||||
the block size; Linux 2.4 is not supported).
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{fourindex_ext(const int file, const fourindexsymtype s=undefined_symmetry, const I n=0, const unsigned int b=1024, const I terminator0= -1, const bool doscaling0= true)}{attach to the open descriptor \code{file}; \code{b} is the approximate buffer size in records.}
|
||||
\lafunc{~fourindex_ext()}{frees the buffer; it does \emph{not} flush pending output and does not close the file.}
|
||||
\lafunc{void setsymmetry(fourindexsymtype s); fourindexsymtype getsymmetry() const; void setterminator(const I terminator0); I getterminator() const; void setscaling(const bool doscaling0)}{as for \code{fourindex}.}
|
||||
\lafunc{void resize(I n); I size() const}{set/get the dimension (no effect on the file).}
|
||||
\lafunc{void rewind() const}{seek to the beginning of the file.}
|
||||
\lafunc{void put(const matel4stored<I,T> x); void put(I i, I j, I k, I l, const T &elem)}{buffered write of one record.}
|
||||
\lafunc{void flush()}{write the buffered records; must be called after the last \code{put()}.}
|
||||
\lafunc{class iterator; iterator begin() const; iterator end() const}{rewind and iterate over all records of the file; \code{*it}/\code{it->} give \code{const matel4stored<I,T>}; preincrement only.}
|
||||
\lafunc{class piterator; piterator pbegin(); piterator pend() const}{all permutations, as for \code{fourindex}.}
|
||||
\lafunc{class qiterator; qiterator qbegin(); qiterator qend() const}{distinct permutations, as for \code{fourindex}.}
|
||||
\lafunc{template <class I, class T> std::ostream& operator<<(std::ostream &s, const fourindex_ext<I,T> &x)}{text output of the whole file in the format of \code{fourindex}.}
|
||||
\lafunc{template <class I, class T> std::istream& operator>>(std::istream &s, fourindex_ext<I,T> &x)}{read the text format and append the elements to the file (with \code{flush()}).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Dense storage: \texttt{fourindex\_dense<S,T,I>}}\label{sec:fourindex:dense}
|
||||
|
||||
\begin{lstlisting}
|
||||
template<fourindexsymtype S, class T, class I> class fourindex_dense; // only specializations exist
|
||||
\end{lstlisting}
|
||||
The dense classes are derived publicly from \code{NRMat<T>} or \code{NRSMat<T>}, so the whole interface of
|
||||
the base class (arithmetic, \code{norm()}, \code{put()/get()}, \code{randomize()}, \code{|=},
|
||||
\code{copyonwrite()}, \dots, chapters~\ref{ch:mat} and~\ref{ch:smat}) is available, and they follow its
|
||||
reference counting and copy-on-demand scheme. The third template parameter \code{I} is the index type of
|
||||
the sparse classes and matters only for the constructors from \code{fourindex<I,T>} and
|
||||
\code{fourindex_ext<I,T>} (named \code{DUMMY} in some places). \code{LA_traits<fourindex_dense<S,T,I> >} defines
|
||||
\code{elementtype} and \code{normtype}.
|
||||
The four-index \code{operator()} returning a reference is an element access in the sense of
|
||||
section~\ref{sec:concepts:refcount}: call \code{copyonwrite()} before writing into a possibly shared object
|
||||
(a DEBUG build raises \code{laerror} for a shared object). The same holds by design for the element-level members
|
||||
\code{set()}, \code{add()} and \code{add_unique()}: they do not call \code{copyonwrite()}, the DEBUG count check
|
||||
reports a write into shared data.
|
||||
|
||||
All specializations use indices from 1.
|
||||
|
||||
\subsection{twoelectronrealmullikanAB}
|
||||
Base \code{NRMat<T>} of dimension $n(n+1)/2 \times n(n+1)/2$; row = symmetric pair $(ij)$, column = pair $(kl)$.
|
||||
\begin{lareference}
|
||||
\lafunc{fourindex_dense(); explicit fourindex_dense(const int n); fourindex_dense(const T &a, const int n); fourindex_dense(const T *a, const int n)}{empty; uninitialized; filled with \code{a}; copied from an array.}
|
||||
\lafunc{fourindex_dense(const NRMat<T> &rhs)}{reinterpret a matrix (shared data); also an implicit conversion from the base class.}
|
||||
\lafunc{fourindex_dense(const fourindex<I,T> &rhs); fourindex_dense(const fourindex_ext<I,T> &rhs)}{expand a sparse list of the same symmetry type (\code{laerror} otherwise); missing elements are zero.}
|
||||
\lafunc{T& operator()(unsigned int i, unsigned int j, unsigned int k, unsigned int l); const T& operator()(unsigned int i, unsigned int j, unsigned int k, unsigned int l) const}{element $(ij|kl)$.}
|
||||
\lafunc{void resize(const int n); int nbas() const}{resize for $n$ orbitals; the number of orbitals.}
|
||||
\lafunc{void putext(int f, T thr=1e-15)}{write the non-equivalent elements with $|x|>\mathtt{thr}$ as \code{matel4stored<I,T>} records to the file descriptor \code{f} (readable by \code{fourindex_ext} or \code{fourindex::get(fd,false)}).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{twoelectronrealmullikan}
|
||||
Base \code{NRSMat<T>} of dimension $n(n+1)/2$ (symmetric matrix of symmetric pairs), i.e.\ full 8-fold
|
||||
symmetry. The interface is identical to \code{twoelectronrealmullikanAB} with \code{NRSMat<T>} in place of
|
||||
\code{NRMat<T>}.
|
||||
|
||||
\subsection{nosymmetry}
|
||||
Base \code{NRMat<T>} of dimension $n^2\times n^2$; element $(i,j,a,b)$ is the matrix element
|
||||
$((j-1)n+i-1,\,(b-1)n+a-1)$.
|
||||
\begin{lareference}
|
||||
\lafunc{fourindex_dense(); explicit fourindex_dense(const int nnn); explicit fourindex_dense(const int nnn, const NRMat<T> &mat)}{empty; uninitialized; reinterpret \code{mat} (shared).}
|
||||
\lafunc{T& operator()(unsigned int i, unsigned int j, unsigned int a, unsigned int b); const T& operator()(...) const}{element access.}
|
||||
\lafunc{void resize(const int nnn); unsigned int nbas() const}{dimension.}
|
||||
\lafunc{void print(std::ostream &out) const}{print all elements as lines \code{i j a b value}.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{T2IjAb\_aces}
|
||||
Spin block $t_{Ij}^{Ab}$ of CC amplitudes in the ACES storage order; base \code{NRMat<T>} of dimension
|
||||
$(\mathtt{noca}\cdot\mathtt{nocb})\times(\mathtt{nvra}\cdot\mathtt{nvrb})$, element $(i,j,a,b)$ at
|
||||
$((j-1)\,\mathtt{noca}+i-1,\,(b-1)\,\mathtt{nvra}+a-1)$; $i,a$ are $\alpha$ and $j,b$ are $\beta$ indices.
|
||||
\begin{lareference}
|
||||
\lafunc{unsigned int noca, nocb, nvra, nvrb}{public dimensions (occupied/virtual, $\alpha$/$\beta$).}
|
||||
\lafunc{fourindex_dense(); explicit fourindex_dense(const int nocca, const int noccb, const int nvrta, const int nvrtb); explicit fourindex_dense(const int nocca, const int noccb, const int nvrta, const int nvrtb, const NRMat<T> &data)}{empty; uninitialized; reinterpret \code{data} (shared, dimensions checked).}
|
||||
\lafunc{void resize(const int nocca, const int noccb, const int nvrta, const int nvrtb)}{resize.}
|
||||
\lafunc{T& operator()(unsigned int i, unsigned int j, unsigned int a, unsigned int b); const T& operator()(...) const}{element access.}
|
||||
\lafunc{void print(std::ostream &out) const}{print all elements.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{T2ijab\_aces}
|
||||
Same-spin amplitudes $t_{ij}^{ab}$, antisymmetric in $i,j$ and in $a,b$; base \code{NRMat<T>} of dimension
|
||||
$\binom{\mathtt{nocc}}{2}\times\binom{\mathtt{nvrt}}{2}$ storing the elements with $i>j$, $a>b$.
|
||||
\begin{lareference}
|
||||
\lafunc{unsigned int nocc, nvrt}{public dimensions.}
|
||||
\lafunc{fourindex_dense(); explicit fourindex_dense(const int noc, const int nvr); explicit fourindex_dense(const int noc, const int nvr, const NRMat<T> &data)}{empty; uninitialized; reinterpret \code{data} (shared, dimensions checked).}
|
||||
\lafunc{void resize(const int noc, const int nvr)}{resize.}
|
||||
\lafunc{T operator()(unsigned int i, unsigned int j, unsigned int a, unsigned int b) const}{element value with the sign from the index order; 0 for $i=j$ or $a=b$. No l-value access because of the sign.}
|
||||
\lafunc{void set(unsigned int i, unsigned int j, unsigned int a, unsigned int b, T elem); void add(unsigned int i, unsigned int j, unsigned int a, unsigned int b, T elem)}{set or increment an element given in any index order (the sign of the permutation is applied); elements with $i=j$ or $a=b$ are identically zero and ignored (a nonzero value raises \code{laerror} in DEBUG builds).}
|
||||
\lafunc{void print(std::ostream &out) const}{print the stored elements.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{antisymtwoelectronrealdiracAB}
|
||||
Base \code{NRSMat<T>} of dimension $n^2$: symmetric matrix of ordered pairs; element $(i,j,a,b)$ is the
|
||||
matrix element $((j-1)n+i-1,\,(b-1)n+a-1)$, i.e.\ symmetric only under exchange of the pairs. Here $i,a$ are
|
||||
$\alpha$ and $j,b$ $\beta$ indices.
|
||||
\begin{lareference}
|
||||
\lafunc{fourindex_dense(); explicit fourindex_dense(const int n); explicit fourindex_dense(const int n, const NRSMat<T> &data)}{empty; uninitialized; reinterpret \code{data} (shared, dimension checked).}
|
||||
\lafunc{T& operator()(unsigned int i, unsigned int j, unsigned int a, unsigned int b); const T& operator()(...) const; void set(unsigned int i, unsigned int j, unsigned int k, unsigned int l, T elem)}{element access.}
|
||||
\lafunc{void resize(const int n); unsigned int nbas() const}{dimension.}
|
||||
\lafunc{void print(std::ostream &out) const}{print the non-equivalent elements.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{antisymtwoelectronrealdirac}
|
||||
Antisymmetrized integrals $\langle ij\|kl\rangle$ with the full 8-fold symmetry; base \code{NRSMat<T>} of
|
||||
dimension $\binom{n}{2}$ (symmetric matrix of antisymmetric pairs, \code{ASMat_index_1} of smat.h).
|
||||
\begin{lareference}
|
||||
\lafunc{fourindex_dense(); explicit fourindex_dense(const int n); explicit fourindex_dense(const int n, const NRSMat<T> &data); fourindex_dense(const T &a, const int n); fourindex_dense(const T *a, const int n)}{empty; uninitialized; reinterpret \code{data} (shared); filled with \code{a}; copied from an array.}
|
||||
\lafunc{fourindex_dense(const fourindex<I,T> &rhs); fourindex_dense(const fourindex_ext<I,T> &rhs)}{build $\langle pq\|rs\rangle=(pr|qs)-(ps|qr)$ from Mulliken integrals: \code{rhs} must have symmetry \code{twoelectronrealmullikan}.}
|
||||
\lafunc{const T operator()(unsigned int i, unsigned int j, unsigned int k, unsigned int l) const}{element value with sign, 0 for $i=j$ or $k=l$. No l-value access.}
|
||||
\lafunc{void set(unsigned int i, unsigned int j, unsigned int k, unsigned int l, T elem); void add(unsigned int i, unsigned int j, unsigned int k, unsigned int l, T elem)}{set/increment with the sign from the index order; elements with $i=j$ or $k=l$ are ignored, or raise \code{laerror} if the macro \code{FORBID_NONEXISTENT_ASSIGNMENTS} is defined.}
|
||||
\lafunc{void add_unique(unsigned int i, unsigned int j, unsigned int k, unsigned int l, T elem)}{increment only if $(i,j,k,l)$ is the canonical representative ($i>j$, $k>l$, pair $(ij)\ge(kl)$), otherwise do nothing.}
|
||||
\lafunc{void resize(const int n); int nbas() const}{dimension.}
|
||||
\lafunc{void print(std::ostream &out) const}{print the non-equivalent elements.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Other symmetry types}
|
||||
No dense specializations exist for \code{twoelectronrealdirac}, the \code{..._unitary} types, the
|
||||
\code{...reducedsym...} types and the complex types (source TODO: ``implement fourindex\_dense for unitary t2
|
||||
symmetry types'').
|
||||
|
||||
\section{Examples}\label{sec:fourindex:examples}
|
||||
|
||||
A sparse list of real Mulliken integrals, a contraction over all equivalent elements, and conversion to
|
||||
dense storage via a disc file:
|
||||
\begin{lstlisting}
|
||||
int n=3;
|
||||
fourindex<int,double> f(n,twoelectronrealmullikan);
|
||||
f.add(2,1,1,1,0.5); // (21|11)
|
||||
f.add(2,2,1,1,0.3); // (22|11)
|
||||
f.add(2,1,2,1,0.1); // (21|21)
|
||||
|
||||
// Coulomb-like matrix J_ij = sum_kl (ij|kl) D_kl over ALL elements
|
||||
NRSMat<double> D(1.,n);
|
||||
NRMat<double> J(0.,n,n);
|
||||
for(fourindex<int,double>::piterator p=f.pbegin(); p.notend(); ++p)
|
||||
J(p->index.indiv.i-1,p->index.indiv.j-1) +=
|
||||
p->elem * D(p->index.indiv.k-1,p->index.indiv.l-1);
|
||||
|
||||
// the distinct elements (here 10), unscaled
|
||||
int cnt=0;
|
||||
for(fourindex<int,double>::qiterator q=f.qbegin(); q.notend(); ++q) ++cnt;
|
||||
|
||||
// via a binary file to dense storage
|
||||
int fd=open("ints.bin",O_CREAT|O_TRUNC|O_RDWR,0644);
|
||||
f.put(fd,false); // raw records only
|
||||
fourindex_ext<int,double> fe(fd,twoelectronrealmullikan,n);
|
||||
fourindex_dense<twoelectronrealmullikan,double,int> fdense(fe);
|
||||
const fourindex_dense<twoelectronrealmullikan,double,int> &fc=fdense;
|
||||
cout << fc(1,2,2,1) << endl; // 0.1 = (21|21)
|
||||
close(fd);
|
||||
\end{lstlisting}
|
||||
|
||||
Writing a file through \code{fourindex_ext}:
|
||||
\begin{lstlisting}
|
||||
int fd2=open("ints2.bin",O_CREAT|O_TRUNC|O_RDWR,0644);
|
||||
{
|
||||
fourindex_ext<int,double> fo(fd2,twoelectronrealmullikan,n);
|
||||
fo.put(2,1,1,1,0.5);
|
||||
fo.put(2,2,1,1,0.3);
|
||||
fo.flush(); // not done by the destructor
|
||||
for(fourindex_ext<int,double>::iterator it=fo.begin(); it!=fo.end(); ++it)
|
||||
cout << it->elem << endl;
|
||||
}
|
||||
close(fd2);
|
||||
\end{lstlisting}
|
||||
See also the conversion examples \code{fourindex2tensor}/\code{tensor2fourindex} in chapter~\ref{ch:tensor}.
|
||||
|
||||
\section{Pitfalls}\label{sec:fourindex:pitfalls}
|
||||
\begin{itemize}
|
||||
\item \code{fourindex::size()} is the number of orbitals; the number of elements is \code{length()}.
|
||||
\item The sparse classes do not enforce canonical index order or uniqueness; adding two equivalent elements
|
||||
counts them twice in the iterators and in the dense constructors the later one wins.
|
||||
\item \code{piterator} scaling is not implemented for all symmetry types; use \code{setscaling(false)} if
|
||||
\code{laerror} reports it.
|
||||
\item \code{fourindex_ext} must be flushed explicitly after writing; only one iterator per object may be
|
||||
active.
|
||||
\item Constructors of the dense classes from \code{NRMat}/\code{NRSMat} share the data.
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,124 @@
|
||||
\chapter{Introduction}\label{ch:introduction}
|
||||
|
||||
\section{Purpose}
|
||||
|
||||
\LA{} is a C++ class library for numerical linear algebra and related tasks.
|
||||
Its core are the class templates
|
||||
\code{NRVec<T>} (vector), \code{NRMat<T>} (dense matrix) and \code{NRSMat<T>} (symmetric or hermitian
|
||||
matrix in packed storage), which provide a convenient and efficient interface to the BLAS and LAPACK
|
||||
libraries. Around them a number of further classes and algorithms has grown, mostly motivated by the
|
||||
needs of quantum chemistry: sparse matrices, tensors with arbitrary index symmetry, four-index quantities
|
||||
(two-electron integrals), iterative eigensolvers and linear solvers working with matrices that are never
|
||||
stored explicitly, convergence acceleration, numerical optimization, permutations and the symmetric group,
|
||||
polynomials, quaternions and others.
|
||||
|
||||
The design follows a few principles, which are explained in detail in chapter~\ref{ch:concepts}:
|
||||
\begin{itemize}
|
||||
\item \emph{Reference counting with copy on demand.} Copying and assigning objects is cheap, data are shared
|
||||
until they are modified. Objects can therefore be passed and returned by value. The price is that writing to
|
||||
individual elements requires an explicit \code{copyonwrite()} call (section~\ref{sec:concepts:refcount}).
|
||||
\item \emph{No expression templates.} The library does not optimize matrix expressions. Operators like
|
||||
\code{A*x+y} are available for convenience, but performance critical code should call the BLAS-like member
|
||||
functions (\code{gemv}, \code{gemm}, \code{axpy}, \dots) directly, which avoid temporaries.
|
||||
\item \emph{Generic algorithms.} Iterative methods (Davidson, Lanczos, conjugate gradients, GMRES, the matrix
|
||||
exponential, \dots) are templates which only need a few operations of the matrix class (typically
|
||||
\code{nrows()}, \code{ncols()}, \code{diagonalof()} and \code{gemv()}), so that they work with user classes
|
||||
which compute the matrix--vector product on the fly (``direct'' methods).
|
||||
\item \emph{Nesting.} The element type of the containers can itself be a container, e.g.\
|
||||
\code{NRVec<NRMat<double> >}; copy on demand and I/O work recursively (section~\ref{sec:concepts:traits}).
|
||||
\end{itemize}
|
||||
|
||||
The manual is written for users who know both C++ and linear algebra. It explains the concepts and
|
||||
gives a compact but complete reference of the public interface. Many further usage examples are in the
|
||||
test programs \header{t.cc}, \header{t2.cc} and \header{test.cc} of the distribution; the regression test of the
|
||||
optimization module is \header{testoptimize.cc}.
|
||||
|
||||
The class names \code{NRVec}, \code{NRMat} etc.\ are historical: the classes were first used together with
|
||||
the Numerical Recipes library. They can be used with it, but are completely independent of it.
|
||||
|
||||
\section{Overview}
|
||||
|
||||
\begin{longtable}{@{}p{0.33\linewidth}p{0.62\linewidth}@{}}
|
||||
\toprule
|
||||
\textbf{Header} & \textbf{Content} \\
|
||||
\midrule
|
||||
\endhead
|
||||
\header{la.h} & the single include file for the user \\
|
||||
\header{vec.h} & \code{NRVec<T>}, \code{NRVec_from1<T>} (chapter~\ref{ch:vec}) \\
|
||||
\header{mat.h} & \code{NRMat<T>}, \code{NRMat_from1<T>} (chapter~\ref{ch:mat}) \\
|
||||
\header{smat.h} & \code{NRSMat<T>}, \code{NRSMat_from1<T>} (chapter~\ref{ch:smat}) \\
|
||||
\header{sparsemat.h}, \header{sparsesmat.h} & sparse matrices \code{SparseMat<T>}, \code{SparseSMat<T>} (chapter~\ref{ch:sparse}) \\
|
||||
\header{csrmat.h} & compressed row storage sparse matrix (unfinished) \\
|
||||
\header{nonclass.h}, \header{matexp.h} & linear systems, eigenvalue problems, SVD, matrix functions (chapter~\ref{ch:nonclass}) \\
|
||||
\header{davidson.h}, \header{lanczos.h}, \header{conjgrad.h}, \header{gmres.h}, \header{diis.h}, \header{bisection.h} & iterative methods, DIIS convergence acceleration (chapter~\ref{ch:iterative}) \\
|
||||
\header{optimize.h} & numerical optimization (chapter~\ref{ch:optimize}) \\
|
||||
\header{tensor.h} & tensors with index symmetries (chapter~\ref{ch:tensor}) \\
|
||||
\header{fourindex.h} & four-index quantities, two-electron integrals (chapter~\ref{ch:fourindex}) \\
|
||||
\header{permutation.h}, \header{qsort.h} & permutations, partitions, Young tableaux, sorting (chapter~\ref{ch:permutation}) \\
|
||||
\header{polynomial.h}, \header{contfrac.h} & polynomials, rationals and continued fractions (chapter~\ref{ch:polynomial}) \\
|
||||
\header{vecmat3.h} & 3D vectors and matrices (chapter~\ref{ch:vecmat3}) \\
|
||||
\header{quaternion.h} & quaternions (chapter~\ref{ch:quaternion}) \\
|
||||
\header{bitvector.h} & bit vectors, polynomials over GF(2) (chapter~\ref{ch:bitvector}) \\
|
||||
\header{numbers.h}, \header{graph.h} & number theory, graph algorithms (chapter~\ref{ch:utilities}) \\
|
||||
\header{reg.h}, \header{regsurf.h} & regression (chapter~\ref{ch:regression}) \\
|
||||
\header{auxstorage.h}, \header{la_random.h}, \header{miscfunc.h}, \header{simple.h}, \header{laerror.h} & utilities (chapter~\ref{ch:utilities}) \\
|
||||
\header{la_traits.h} & traits classes, the basis of the generic programming (section~\ref{sec:concepts:traits}) \\
|
||||
\bottomrule
|
||||
\end{longtable}
|
||||
|
||||
Everything is declared in the namespace \code{LA}, with the exception of a few headers designed to be usable
|
||||
also without the rest of the library (\header{vecmat3.h}: namespace \code{LA_Vecmat3}, \header{quaternion.h}:
|
||||
namespace \code{LA_Quaternion}, \header{simple.h}). \header{la.h} includes all headers and contains
|
||||
\code{using namespace LA;} together with a few typedefs (\code{NRDVec}, \code{NRCVec}, \code{NRIVec},
|
||||
\code{NRDMat}, \code{NRCMat}, \code{NRIMat} for \code{double}, \code{std::complex<double>} and \code{int} elements).
|
||||
|
||||
\section{A minimal example}
|
||||
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
using namespace LA;
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
const int n=4;
|
||||
NRSMat<double> h(n);
|
||||
h.randomize(1.); //symmetric matrix with random elements
|
||||
NRSMat<double> hsave;
|
||||
hsave |= h; //deep copy, diagonalize() destroys its argument
|
||||
NRVec<double> e(n);
|
||||
NRMat<double> v(n,n);
|
||||
diagonalize(h,e,&v); //eigenvalues e (ascending), eigenvectors in columns of v
|
||||
NRMat<double> r = hsave*v; //residual H V - V diag(e)
|
||||
r.copyonwrite(); //always before writing via operator() or []
|
||||
for(int i=0; i<n; ++i) for(int j=0; j<n; ++j) r(i,j) -= v(i,j)*e[j];
|
||||
cout << e << "residual norm = " << r.norm() << endl;
|
||||
}
|
||||
\end{lstlisting}
|
||||
It is compiled e.g.\ by
|
||||
\begin{lstlisting}[language=bash]
|
||||
g++ -O3 -I$PREFIX/include example.cc -L$PREFIX/lib -lla -llapack -lcblas -lblas
|
||||
\end{lstlisting}
|
||||
with the same preprocessor options and libraries the library was configured with
|
||||
(chapter~\ref{ch:building}).
|
||||
|
||||
\section{Conventions used in this manual}
|
||||
|
||||
\begin{itemize}
|
||||
\item \code{T} denotes the element type of a class template. Unless said otherwise, BLAS and LAPACK backed
|
||||
operations are available for \code{double} and \code{std::complex<double>}; other element types (integer
|
||||
types, nested classes) support the generic operations.
|
||||
\item Indices run from 0, unless a \code{_from1} variant of a class is used.
|
||||
\item In the reference lists, ``this'' denotes the object the member function is called for.
|
||||
Functions that modify the object call \code{copyonwrite()} themselves unless stated otherwise.
|
||||
\item Boxes marked \textbf{Caution} point to common pitfalls; yellow boxes are open questions for the
|
||||
author, collected in the list at the beginning.
|
||||
\end{itemize}
|
||||
|
||||
\section{Authors and licence}
|
||||
|
||||
\LA{} is copyright \copyright{} 2008-- Ji\v{r}\'{\i} Pittner. The complex versions of the basic classes were
|
||||
written by Roman Curik, the CUDA interface was contributed by Miroslav Sulc; the regression module
|
||||
has its own author (chapter~\ref{ch:regression}). The library is free software, distributed under the
|
||||
GNU General Public License, version~3 or later.
|
||||
Since 2026, the author started to employ an AI assistant for code review, documentation, and some new implementations.
|
||||
@@ -0,0 +1,432 @@
|
||||
\chapter{Iterative methods}\label{ch:iterative}
|
||||
|
||||
\laheader{davidson.h, lanczos.h, conjgrad.h, gmres.h, diis.h, bisection.h}
|
||||
|
||||
\section{Purpose and main idea}\label{sec:iterative:purpose}
|
||||
|
||||
This chapter describes the header-only iterative algorithms of \LA{}:
|
||||
\begin{itemize}
|
||||
\item \code{davidson()} (\header{davidson.h}): a few lowest (or closest to a target) eigenpairs of a large real symmetric or
|
||||
complex hermitian matrix, or right eigenpairs with real eigenvalues of a real non-symmetric matrix;
|
||||
\item \code{lanczos()} (\header{lanczos.h}): a few lowest (or closest to a target) eigenpairs of a large hermitian matrix;
|
||||
\item \code{conjgrad()} (\header{conjgrad.h}) and \code{gmres()} (\header{gmres.h}): solution of large linear systems $Ax=b$,
|
||||
or of the normal equations $A\Tr Ax=A\Tr b$ for rectangular $A$ (least squares);
|
||||
\item the class \code{DIIS} (\header{diis.h}): Pulay's DIIS convergence acceleration of a general fixed-point iteration;
|
||||
\item \code{bisection_find()} and \code{interpolation_find()} (\header{bisection.h}): search in a sorted array.
|
||||
\end{itemize}
|
||||
Dense (LAPACK based) diagonalization, linear solvers and matrix functions are in chapter~\ref{ch:nonclass}; the small
|
||||
subspace problems of \code{davidson} and \code{lanczos} are solved by the routines described there
|
||||
(\code{diagonalize}, \code{gdiagonalize}, \code{diagonalize3}, \code{linear_solve}). The quasi-Newton GDIIS optimizer built on
|
||||
the \code{DIIS} class is in chapter~\ref{ch:optimize}.
|
||||
|
||||
All functions are templates defined in the headers (all except \header{bisection.h} are included by \header{la.h}) and are
|
||||
instantiated by the user's code. The matrix is a template parameter \code{Matrix} which is accessed only through a few member
|
||||
functions, so that the matrix need not be stored at all: the matrix-vector product may be computed ``directly'' from other
|
||||
data (direct CI in quantum chemistry, operators given by a formula, a product of several matrices, \dots). Any of the \LA{}
|
||||
matrix classes providing these functions (\code{NRMat}, \code{NRSMat}, \code{SparseMat}, \code{SparseSMat}, \code{CSRMat},
|
||||
\code{SemiSparseMat}, chapters~\ref{ch:mat}, \ref{ch:smat}, \ref{ch:sparse}) can also be used directly.
|
||||
|
||||
\section{Requirements on the matrix class}\label{sec:iterative:matrix}
|
||||
|
||||
The member functions used are listed in table~\ref{tab:iterative:matrix}; they must be \code{const}. Their semantics follow
|
||||
the \LA{} matrix classes:
|
||||
\begin{lareference}
|
||||
\lafunc{int nrows() const; int ncols() const}{Dimensions (any integer type). The eigensolvers require a square matrix
|
||||
(\code{laerror} otherwise).}
|
||||
\lafunc{bool issymmetric() const}{\code{true} if the matrix is real symmetric or complex hermitian. \code{davidson} chooses the
|
||||
symmetric or non-symmetric algorithm by it; \code{lanczos} calls \code{laerror} if it is \code{false}. Note that
|
||||
\code{NRMat::issymmetric()} always returns \code{false}, \code{NRSMat} and \code{SparseSMat} return \code{true}, \code{SparseMat}
|
||||
returns its symmetric flag.}
|
||||
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide=0, bool cache=false) const}{With \code{divide==false}: store the
|
||||
diagonal $A_{ii}$ into \code{r} (allocated by the caller with \code{nrows()} elements) and return a pointer to the $n$ diagonal
|
||||
elements, normally \code{&r[0]}. The eigensolvers call it with \code{cache=true}; a direct matrix may then compute the diagonal
|
||||
once, keep it, and return a pointer to its own copy (the comment in davidson.h recommends this ``for efficiency in a direct
|
||||
CI case''); the library classes ignore \code{cache}. With \code{divide==true}: divide \code{r} element-wise by the nonzero
|
||||
diagonal elements (Jacobi preconditioning) and return \code{NULL}. For a rectangular matrix ($m\times n$, $m\ne n$) the
|
||||
library classes use the diagonal of $A\Tr A$ (squared column norms, \code{r} with $n=$\code{ncols()} elements) instead, which is
|
||||
the right (Jacobi) preconditioner for the normal equations used by \code{conjgrad} and \code{gmres} in that case; user matrix
|
||||
classes should follow the same convention.}
|
||||
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x) const}{$r\leftarrow\beta r+
|
||||
\alpha\,\mathrm{op}(A)\,x$ with $\mathrm{op}(A)=A$ for \code{trans=='n'} and $A\Tr$ for \code{'t'}; \code{r} is allocated by
|
||||
the caller with the right size. With $\beta=0$ the previous contents of \code{r} must be ignored (it can be uninitialized).}
|
||||
\end{lareference}
|
||||
|
||||
\begin{table}[htb]
|
||||
\centering
|
||||
\begin{tabular}{@{}l>{\raggedright\arraybackslash}p{2.3cm}>{\raggedright\arraybackslash}p{3.1cm}>{\raggedright\arraybackslash}p{2.9cm}@{}}
|
||||
\toprule
|
||||
& \code{issymmetric} & \code{diagonalof} & \code{gemv} \\
|
||||
\midrule
|
||||
\code{davidson} & yes & \code{divide=false} & \code{'n'}, $\beta=0$ \\
|
||||
\code{lanczos} & yes (must be true) & \code{divide=false}, only without \code{initguess} & \code{'n'}, $\beta=0$ and $\beta=1$ \\
|
||||
\code{conjgrad} & -- & \code{divide=true}, if \code{precondition} & \code{'n'}; \code{'t'} if \code{doguess} or non-square \\
|
||||
\code{gmres} & -- & \code{divide=true}, if \code{precondition} & \code{'n'}; \code{'t'} if \code{doguess} or non-square \\
|
||||
\code{explicit_matrix} & -- & -- & \code{'n'}, $\beta=0$ \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Member functions of \code{Matrix} used by the iterative solvers; all of them also use \code{nrows()} and \code{ncols()}.}\label{tab:iterative:matrix}
|
||||
\end{table}
|
||||
|
||||
\paragraph{Copy on demand.} The solvers keep the Krylov vectors as shallow copies of their work vectors
|
||||
(sec.~\ref{sec:concepts:refcount}), so the vectors \code{r} passed to \code{gemv} and \code{diagonalof} frequently share their
|
||||
data with stored vectors. A user implementation must therefore not write elements of \code{r} through \code{operator[]} or a raw
|
||||
pointer before calling \code{r.copyonwrite()}; whole-object operations (\code{r=0.}, \code{r*=beta}, \code{r|=d}, \code{axpy},
|
||||
and the \code{gemv} of the library classes) call it themselves. Forgetting it silently corrupts the Krylov space (in DEBUG
|
||||
builds the non-const \code{operator[]} of a shared vector raises \code{laerror}). The input vector \code{x} is a const reference
|
||||
and can be read freely. The library classes satisfy this.
|
||||
|
||||
\paragraph{Out-of-core storage.} \code{davidson}, \code{lanczos}, \code{gmres} and \code{DIIS} can keep their vectors in
|
||||
scratch files instead of memory (parameter \code{incore}). They use \code{AuxStorage<T>} (\header{auxstorage.h},
|
||||
chapter~\ref{ch:utilities}): a binary file with fixed-length records of raw elements, created by \code{mkostemp} as
|
||||
\texttt{AUX\_XXXXXX} in \code{\$TMPDIR} (if set, otherwise in the current working directory) and unlinked at once, so no file
|
||||
remains, even after a crash or an exception. Only one $n$-vector (two in
|
||||
\code{davidson}) is then kept in memory per stored set besides the work vectors, at the price of reading all stored vectors in
|
||||
every iteration. If a solver is left by an exception, the storage objects themselves are not freed (raw pointers), but the
|
||||
disk space is released when the process ends.
|
||||
|
||||
\paragraph{Template argument deduction.} The element type \code{T} of \code{davidson} and \code{lanczos} is deduced from the
|
||||
vector arguments. A literal \code{NULL} passed for one of the pointer parameters depending on \code{T} (\code{eivecs},
|
||||
\code{initguess}, \code{target}) makes the deduction fail; either cast it (\code{(NRVec<double> *)NULL}, as in t.cc) or give
|
||||
\code{T} explicitly: \code{davidson<double>(a,e,NULL,NULL,2)} (verified). Arguments left at their default values are not a
|
||||
problem.
|
||||
|
||||
%-----------------------------------------------------------------------------------------------------------------------------
|
||||
\section{Davidson diagonalization}\label{sec:iterative:davidson}
|
||||
|
||||
\laheader{davidson.h}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T, typename Matrix> void davidson(const Matrix &bigmat, NRVec<T> &eivals, NRVec<T> *eivecs, const char *eivecsfile, int nroots=1, const bool verbose=0, const double eps=1e-6, const bool incore=1, int maxit=100, const int maxkrylov = 500, void (*initguess)(NRVec<T> &)=NULL, const typename LA_traits<T>::normtype *target=NULL)}{
|
||||
Davidson's method \cite{iterative:davidson1975} for the \code{nroots} lowest eigenvalues of \code{bigmat}.
|
||||
\begin{itemize}
|
||||
\item \code{eivals}: on return the eigenvalues in its first \code{nroots} elements (ascending, or by increasing distance from
|
||||
\code{*target}); it must have at least \code{nroots} elements (\code{laerror}), it is not resized.
|
||||
\item \code{eivecs}: \code{NULL} or an array of at least \code{nroots} vectors receiving the normalized eigenvectors (by deep
|
||||
copy, \code{|=}, so their previous size does not matter).
|
||||
\item \code{eivecsfile}: \code{NULL} or a file name; the eigenvectors are then also written into this file as binary records
|
||||
of $n$ elements of type \code{T}, record $i$ = root $i$ (an existing file is removed first), also if
|
||||
\code{eivecs==NULL}.
|
||||
\item \code{nroots}: number of roots; reduced silently to \code{maxkrylov-1} if larger.
|
||||
\item \code{verbose}: print the eigenvalue estimates in each iteration and the convergence of each root to \code{std::cout}.
|
||||
\item \code{eps}: convergence threshold (see below).
|
||||
\item \code{incore}: keep the Krylov vectors $v_k$ and $Av_k$ in memory (\code{true}) or in two \code{AuxStorage} scratch files.
|
||||
\item \code{maxit}: maximum number of iterations, i.e.\ of matrix-vector products after the initial one (restarts included).
|
||||
\item \code{maxkrylov}: maximum dimension of the subspace; the subspace matrices are allocated with this dimension. When it
|
||||
is reached, the method restarts (see below).
|
||||
\item \code{initguess}: function filling its argument (allocated with $n$ elements, uninitialized) with the start vector,
|
||||
which need not be normalized. By default the unit vector $e_j$ with $j$ the index of the lowest (real part of the) diagonal
|
||||
element is used.
|
||||
\item \code{target}: if not \code{NULL}, the subspace eigenpairs are sorted by $|\lambda-\mathtt{*target}|$ instead of
|
||||
ascending order, i.e.\ the roots closest to the target value are sought.
|
||||
\end{itemize}
|
||||
Throws \code{laerror} if not converged within \code{maxit} iterations.}
|
||||
\end{lareference}
|
||||
|
||||
\paragraph{Algorithm.} The method keeps the subspace vectors $v_k$ (not orthonormalized exactly, therefore the overlap matrix
|
||||
$S_{jk}=v_j\cdot v_k$ is kept too), the products $Av_k$, and the subspace matrix $H_{jk}=v_j\cdot Av_k$. In each iteration the
|
||||
generalized subspace eigenproblem $Hc=\lambda Sc$ is solved by \code{diagonalize} (symmetric case) or by \code{gdiagonalize}
|
||||
with \code{sorttype=2} (real eigenvalues first, in ascending order; non-symmetric case, right eigenvectors; the imaginary parts
|
||||
of complex eigenvalues are discarded). The roots are converged one after another: for the current root $\lambda$ with the
|
||||
Ritz vector $y=\sum_kc_kv_k$ the residual $\rho=\sum_kc_k(Av_k-\lambda v_k)$ is formed and the correction vector is
|
||||
$\delta_i=\rho_i/\mathrm{d}_i$ with $\mathrm{d}_i=A_{ii}-\lambda$, where $|\mathrm{d}_i|$ is limited from below by $0.1$
|
||||
(keeping its sign). The correction is normalized and orthogonalized by Gram-Schmidt to all previous subspace vectors (the
|
||||
projection on each $v_j$ is repeated while it removes more than 1\,\% of the norm) and appended to the subspace. Each iteration
|
||||
costs one \code{gemv} with \code{'n'} and one \code{diagonalof}.
|
||||
|
||||
\paragraph{Convergence.} A root is considered converged when the coefficient of the most recently added subspace vector in its
|
||||
subspace eigenvector, $|c_{\rm last}|$, is below \code{eps}; this is \emph{not} the residual norm, and the error of the
|
||||
eigenvalue is typically much smaller than \code{eps}. After each iteration the method continues with the lowest root not yet
|
||||
converged, and returns to a lower root if its $|c_{\rm last}|$ exceeds \code{eps} again. The method finishes successfully when
|
||||
all \code{nroots} roots are converged, or (with a warning on \code{std::cout}) when the new correction vector vanishes before or
|
||||
during the orthogonalization; in this case the current Ritz values are returned even if not all roots satisfy the criterion
|
||||
(intended for tiny matrices). If $|c_{\rm last}|>100$ for the current root, ``Divergence in Davidson'' is printed and
|
||||
\code{laerror} is called.
|
||||
|
||||
\paragraph{Restart.} When the subspace dimension reaches \code{maxkrylov}, the method restarts from the current approximation
|
||||
of the lowest root alone, but only if no root has converged yet; otherwise it fails with \code{laerror}. For several roots
|
||||
\code{maxkrylov} has therefore to be large enough to converge all of them without restart.
|
||||
|
||||
\paragraph{Element types.} \code{T=double} is the main case. For complex hermitian matrices (\code{T=std::complex<double>})
|
||||
the method works, but according to the source comment ``does not converge so well for more than 1 root''; non-hermitian complex
|
||||
matrices are not supported. The non-symmetric real case yields right eigenvectors belonging to real eigenvalues only.
|
||||
|
||||
The file has no header; it is read back with the \code{get} methods without dimensions, e.g.\ all roots as the rows of a matrix,
|
||||
\code{NRMat<T> V(nroots,n); V.get(fd,false);}, or root by root with \code{NRVec<T>::get(fd,false)} (\code{fd} from
|
||||
\code{open(eivecsfile,O_RDONLY)}). \code{AuxStorage(name)} cannot be used for reading, it recreates the file.
|
||||
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T, typename Matrix> NRMat<T> explicit_matrix(const Matrix &bigmat)}{The explicit dense matrix of an
|
||||
implicitly given one, built column by column by \code{gemv} on unit vectors (for debugging). \code{T} must be given:
|
||||
\code{explicit_matrix<double>(a)}. Same as \code{reconstructmatrix} in chapter~\ref{ch:nonclass}.}
|
||||
\end{lareference}
|
||||
|
||||
%-----------------------------------------------------------------------------------------------------------------------------
|
||||
\section{Lanczos diagonalization}\label{sec:iterative:lanczos}
|
||||
|
||||
\laheader{lanczos.h}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T, typename Matrix> void lanczos(const Matrix &bigmat, NRVec<T> &eivals, NRVec<T> *eivecs, const char *eivecsfile, int nroots=1, const bool verbose=0, const double eps=1e-6, const bool incore=1, int maxit=100, const int maxkrylov = 1000, void (*initguess)(NRVec<T> &)=NULL, const typename LA_traits<T>::normtype *target=NULL)}{
|
||||
Lanczos method for the \code{nroots} lowest (or closest to \code{*target}) eigenvalues of a hermitian matrix. The parameters
|
||||
have the same meaning as for \code{davidson}, with these differences: \code{bigmat.issymmetric()} must be \code{true}
|
||||
(\code{laerror} otherwise); the start vector is normalized by the function; \code{incore=false} keeps only the Lanczos vectors
|
||||
$v_k$ in one scratch file; the default \code{maxkrylov} is 1000.}
|
||||
\end{lareference}
|
||||
|
||||
\paragraph{Algorithm.} Plain three-term Lanczos recurrence $w=Av_j-\beta_{j-1}v_{j-1}$, $\alpha_j=v_j\cdot w$,
|
||||
$w\leftarrow w-\alpha_jv_j$, $\beta_j=|w|$, $v_{j+1}=w/\beta_j$, one \code{gemv} per iteration (with $\beta=1$ in the
|
||||
\code{gemv} sense, accumulating into $-\beta_{j-1}v_{j-1}$). The vectors are not reorthogonalized, so that in long runs
|
||||
copies of converged eigenvalues may appear (a known property of the method in finite precision). If $\beta_j=0$ (invariant
|
||||
subspace), a random vector orthonormalized to all previous $v_k$ is used to continue. The tridiagonal matrix is diagonalized
|
||||
in each iteration by \code{diagonalize3}. All $v_k$ are stored (in core or in the scratch file) to build the eigenvectors at the
|
||||
end. If the diagonal elements $v_j\cdot Av_j$ have an imaginary part larger than $10^{-6}$, \code{laerror} reports a
|
||||
non-hermitian matrix.
|
||||
|
||||
\paragraph{Convergence.} Converged when the changes of the \code{nroots} lowest (or closest to the target) eigenvalues
|
||||
between two iterations are all $\le$ \code{eps}; this is checked once the dimension exceeds \code{nroots}. There is no restart:
|
||||
the method fails with \code{laerror} when more than \code{maxit} iterations are needed or when the dimension reaches
|
||||
\code{maxkrylov} (the effective limit is $\min(\mathtt{maxit},\mathtt{maxkrylov}-1)$ iterations).
|
||||
|
||||
%-----------------------------------------------------------------------------------------------------------------------------
|
||||
\section{Conjugate gradients}\label{sec:iterative:conjgrad}
|
||||
|
||||
\laheader{conjgrad.h}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template<typename T, typename Matrix> bool conjgrad(const Matrix &bigmat, const NRVec<T> &b, NRVec<T> &x, const bool doguess=true, const double tol=1e-8, const int itmax=1000, const bool verbose=true, bool issquare=1, const bool precondition=1)}{
|
||||
Solves $Ax=b$ by the (Jacobi preconditioned) conjugate gradient method (the source refers to the book of Stoer and Bulirsch).
|
||||
Returns \code{true} if converged, \code{false} after \code{itmax}+1 iterations without convergence; \code{x} then holds the
|
||||
last iterate.
|
||||
\begin{itemize}
|
||||
\item \code{b}: right-hand side with \code{nrows()} elements; \code{x}: initial guess and solution, must have \code{ncols()}
|
||||
elements (\code{laerror} otherwise).
|
||||
\item \code{doguess}: overwrite \code{x} by the start vector $-A\Tr b$ (divided by the diagonal if \code{precondition}),
|
||||
normalized to 1. With \code{false} the contents of \code{x} are the start vector.
|
||||
\item \code{tol}: convergence threshold, see below.
|
||||
\item \code{itmax}: maximum number of iterations; \code{verbose}: print the error in each iteration (default \code{true}).
|
||||
\item \code{issquare}: \code{true} solves $Ax=b$ directly, which requires a symmetric positive definite matrix; \code{false}
|
||||
applies CG to the normal equations $A\Tr Ax=A\Tr b$ (two \code{gemv} per iteration), usable for non-symmetric matrices.
|
||||
It is set to \code{false} automatically for a rectangular matrix.
|
||||
\item \code{precondition}: divide the residual by the diagonal (\code{diagonalof(r,true)}) in each iteration. The diagonal must
|
||||
then be positive. For a square matrix with \code{issquare=false} the diagonal of $A$ (not of $A\Tr A$) is used, since
|
||||
\code{diagonalof} cannot know that the normal equations are solved; this is not the proper preconditioner for $A\Tr A$ (the
|
||||
Jacobi preconditioner would be the squared column norms) and may slow down or break the convergence if $A$ has non-positive
|
||||
diagonal elements; use \code{precondition=false} then.
|
||||
\end{itemize}}
|
||||
\end{lareference}
|
||||
|
||||
\paragraph{Convergence.} The criterion is $|p|\le$ \code{tol} for the current search direction $p$ (an absolute criterion;
|
||||
in the first iteration $p$ is the preconditioned residual). The function calls \code{laerror} (``singular matrix'') if
|
||||
$p\cdot Ap$ (or $|Ap|^2$) or the residual product $r\cdot z$ vanishes. Only real \code{T} is supported (the scalars are
|
||||
\code{double}).
|
||||
|
||||
%-----------------------------------------------------------------------------------------------------------------------------
|
||||
\section{GMRES}\label{sec:iterative:gmres}
|
||||
|
||||
\laheader{gmres.h}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template<typename T, typename Matrix> bool gmres(const Matrix &bigmat, const NRVec<T> &b, NRVec<T> &x, const bool doguess=1, const double eps=1e-7, const int MAXIT=50, const bool verbose=1, bool square=1, const bool precondition=1, int neustart=0, const int incore=1)}{
|
||||
Solves $Ax=b$ by restarted GMRES with Givens rotations (after H.~R.~Schwarz, original implementation by M.~Warken). Returns
|
||||
\code{true} if converged; otherwise \code{false}, with \code{x} holding the last iterate.
|
||||
\begin{itemize}
|
||||
\item \code{b}, \code{x}, \code{doguess}, \code{precondition}: as for \code{conjgrad}; the preconditioner $D^{-1}$ (Jacobi) is
|
||||
applied from the left, i.e.\ GMRES minimizes $|D^{-1}(Ax-b)|$.
|
||||
\item \code{eps}: relative threshold; converged when the estimated (preconditioned) residual norm is below
|
||||
\code{eps}$\cdot\beta_0$, $\beta_0$ being the preconditioned residual norm of the start vector.
|
||||
\item \code{MAXIT}: dimension of the Krylov space in one cycle; \code{MAXIT+1} vectors are stored.
|
||||
\item \code{verbose}: print the residual estimate in each step (default \code{true}).
|
||||
\item \code{square}: \code{false} solves the normal equations $A\Tr Ax=A\Tr b$; set automatically for a rectangular matrix.
|
||||
\item \code{neustart}: maximum number of cycles (restarts); 0 means \code{nrows()/10}, and at least 10 cycles are always allowed.
|
||||
\item \code{incore}: 0 stores the Krylov vectors in an \code{AuxStorage} scratch file.
|
||||
\end{itemize}
|
||||
On convergence the minimizer over the whole current Krylov space is returned. A breakdown ($|h_{k+1,k}|<0.01\,$\code{eps},
|
||||
the Krylov space is invariant) ends the current cycle with the solution in that space; if it is not converged (singular
|
||||
matrix), the next cycle starts from the new residual.
|
||||
Calls \code{laerror} for a $1\times1$ system (\code{ncols()==1}), for incompatible sizes and for an exactly singular triangular
|
||||
factor. The scalars are \code{double}, so only real \code{T} is supported.}
|
||||
\lafunc{template<class T> void gmres_backsubstitute(const NRMat<T> &R, NRVec<T> &c, const NRVec<T> &d, const int k)}{Auxiliary:
|
||||
solves the upper triangular system $R_{0..k,0..k}\,c=d$ (calls \code{c.copyonwrite()}; \code{c} must be allocated).}
|
||||
\lafunc{template<typename T> void gmres_update(NRMat<T> &H, const NRVec<T> &cci, const NRVec<T> &ssi, NRVec<T> &d, NRVec<T> &c, const int k, NRVec<T> *v, AuxStorage<T> *st, const int incore, NRVec<T> &x)}{Auxiliary:
|
||||
applies the Givens rotations $0..k$ to the Hessenberg matrix \code{H} (in place), solves $Rc=-d$ and adds $\sum_{i=0}^{k}c_iv_i$ to \code{x}.}
|
||||
\end{lareference}
|
||||
|
||||
%-----------------------------------------------------------------------------------------------------------------------------
|
||||
\section{DIIS convergence acceleration}\label{sec:iterative:diis}
|
||||
|
||||
\laheader{diis.h}
|
||||
|
||||
Direct inversion in the iterative subspace \cite{iterative:pulay1980,iterative:pulay1982}: given the last $m$ iterates $x_i$ of
|
||||
some iterative procedure and their error vectors $e_i$, the extrapolated vector is $\sum_ic_ix_i$ with the coefficients
|
||||
minimizing $|\sum_ic_ie_i|$ subject to $\sum_ic_i=1$, obtained from the linear system
|
||||
\begin{equation}\label{eq:iterative:diis}
|
||||
\begin{pmatrix}0&-1&\cdots&-1\\-1&B_{11}&\cdots&B_{1m}\\\vdots&\vdots&&\vdots\\-1&B_{m1}&\cdots&B_{mm}\end{pmatrix}
|
||||
\begin{pmatrix}\lambda\\c_1\\\vdots\\c_m\end{pmatrix}=\begin{pmatrix}-1\\0\\\vdots\\0\end{pmatrix},\qquad B_{ij}=e_i\cdot e_j .
|
||||
\end{equation}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template<typename T, typename U> class DIIS}{\code{T} is the type of the iterated quantity, \code{U} the type of the
|
||||
error vector, typically \code{NRVec}, \code{NRMat} or \code{NRSMat} (e.g.\ \code{T=NRSMat<double>} for a Fock or density
|
||||
matrix and \code{U=NRMat<double>} for the commutator error). Both template arguments must be given (there is no default).
|
||||
Requirements: \code{T} and \code{U} need \code{operator|=}, \code{clear()}, \code{axpy()}, \code{copyonwrite()} and the traits
|
||||
\code{LA_traits<>::elementtype} and \code{normtype}; \code{U} also \code{dot()} and \code{norm()}. The element types of
|
||||
\code{T} and \code{U} must be the same (the matrix of eq.~\eqref{eq:iterative:diis} is built over the element type of \code{U}
|
||||
and converted to that of \code{T}). For out-of-core storage \code{T} and \code{U} must be \code{NRVec}, \code{NRMat} or
|
||||
\code{NRSMat} of plain data (\code{AuxStorage}) and keep their size.}
|
||||
\lafunc{DIIS()}{Uninitialized object (for arrays of \code{DIIS}); \code{setup()} must be called before use, otherwise
|
||||
\code{extrapolate} calls \code{laerror}.}
|
||||
\lafunc{DIIS(const int n, const bool core=1)}{Subspace of at most \code{n} stored iterates, kept in memory (\code{core=true}) or
|
||||
in two \code{AuxStorage} scratch files.}
|
||||
\lafunc{void setup(const int n, const bool core=1)}{(Re)initialize as by the constructor; the previous storage is released and
|
||||
all stored vectors are forgotten. Can be called repeatedly.}
|
||||
\lafunc{void reset()}{Forget all stored vectors (the next \code{extrapolate} starts a new subspace) but keep the allocated storage
|
||||
and the dimension. Used to restart DIIS, e.g.\ after a step judged unreasonable (\code{GDIIS_Optimizer}, chapter~\ref{ch:optimize}).
|
||||
After \code{reset()} the object behaves exactly as a freshly constructed one (tested in testoptimize.cc).}
|
||||
\lafunc{typename LA_traits<U>::normtype extrapolate(T &vec, U &errvec, bool verbose=false, const Un diiseps=0, bool errvecout=false)}{
|
||||
Stores deep copies of \code{vec} and \code{errvec} as the newest pair (when \code{n} pairs are already stored, the oldest is
|
||||
dropped: circular buffer), updates $B$ with the products \code{errvec.dot(}$e_i$\code{)} and solves
|
||||
eq.~\eqref{eq:iterative:diis} for the current number $m$ of stored pairs (\code{linear_solve} for \code{NRSMat},
|
||||
chapter~\ref{ch:nonclass}). \code{vec} is overwritten by $\sum_ic_ix_i$, and with \code{errvecout=true} also \code{errvec} by
|
||||
$\sum_ic_ie_i$ (the estimated error of the extrapolated vector). \code{diiseps} (type \code{LA_traits<U>::normtype}) is added
|
||||
to the diagonal element $B_{mm}$ of the new vector as a regularization. \code{verbose} prints the solution vector
|
||||
$(\lambda,c_1,\dots,c_m)$. Returns $|\mathtt{errvec}|$ of the input error vector. On the first call after construction, \code{setup} or \code{reset} the pair is only stored, \code{vec} and
|
||||
\code{errvec} are unchanged, and the return value is 1.}
|
||||
\end{lareference}
|
||||
|
||||
Typical use: in each iteration compute the new iterate $x$ and its error $e$ (e.g.\ $e=x_{\rm new}-x_{\rm old}$ for a
|
||||
fixed-point iteration, or the commutator $FDS-SDF$ in SCF), call \code{diis.extrapolate(x,e)} and continue with the
|
||||
extrapolated $x$. The stored copies are independent of the user's objects (deep copies by \code{|=}), so the user may modify
|
||||
\code{vec} and \code{errvec} freely afterwards.
|
||||
|
||||
For complex \code{U} the matrix $B_{ij}=e_i^\dagger e_j$ is hermitian: \code{errvec.dot(}$e_i$\code{)}$=e_{\rm new}^\dagger e_i$ is
|
||||
stored in the lower triangle of the complex \code{NRSMat}, which is hermitian in arithmetic (chapter~\ref{ch:smat}), and the
|
||||
coefficients $c_i$ are complex (tested on a complex fixed-point iteration, in core and out of core).
|
||||
|
||||
%-----------------------------------------------------------------------------------------------------------------------------
|
||||
\section{Search in sorted arrays}\label{sec:iterative:bisection}
|
||||
|
||||
\laheader{bisection.h}
|
||||
|
||||
The header is included by \header{la.h}; included alone it needs \code{laerror} declared before (it does not include
|
||||
\header{laerror.h}).
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template<typename INDEX, typename COMPAR, typename SUBJECT> INDEX bisection_find(INDEX dm, INDEX hm, const SUBJECT *key, const SUBJECT *base, unsigned int lead_dimension_base, COMPAR (*cmp)(const SUBJECT *, const SUBJECT *))}{
|
||||
Bisection search of \code{*key} among the elements \code{base[i*lead_dimension_base]}, $i=\mathtt{dm},\dots,\mathtt{hm}$
|
||||
(inclusive), which must be sorted in ascending order with respect to \code{cmp}. \code{cmp(a,b)} returns 0 if equal, a positive
|
||||
value if \code{*a} $>$ \code{*b}, negative otherwise; it is always called with the array element as the first argument.
|
||||
\code{lead_dimension_base} allows searching e.g.\ the first column of a row-major matrix (\code{SUBJECT} is the element type, the
|
||||
key is compared with one element only). Returns the index $i$ of a matching element, or \code{dm-1} if not found (also for
|
||||
\code{dm>hm}). \code{INDEX} should be a signed type (for \code{dm=0} the failure value is $-1$).}
|
||||
\lafunc{template<typename INDEX, typename DISTANCE, typename SUBJECT> int interpolation_find(INDEX dm0, INDEX high, const SUBJECT *x, const SUBJECT *base, unsigned int lead_dimension_base, DISTANCE (*dist)(const SUBJECT *, const SUBJECT *))}{
|
||||
Interpolation search with the same layout and result convention (index or \code{dm0-1}). \code{dist(a,b)} is a signed distance
|
||||
$a-b$ (positive if \code{*a} $>$ \code{*b}, zero if equal); the next probe is placed by linear interpolation, so the speed
|
||||
depends on how well \code{dist} reflects the distribution of the keys. Calls \code{laerror} if an interpolated index falls
|
||||
outside the current interval (inconsistent \code{dist}).}
|
||||
\end{lareference}
|
||||
|
||||
%-----------------------------------------------------------------------------------------------------------------------------
|
||||
\section{Examples}\label{sec:iterative:examples}
|
||||
|
||||
\subsection{Davidson with a user-defined direct matrix}
|
||||
|
||||
The matrix $H_{ij}=d_i\delta_{ij}+c\,(\delta_{i,j+1}+\delta_{i+1,j})$ is never stored; the class provides only the functions
|
||||
of table~\ref{tab:iterative:matrix}. The program was compiled and run; the three eigenvalues agree with dense diagonalization
|
||||
and the residual norms $|Hv-\lambda v|$ are $10^{-12}$--$10^{-10}$ for \code{eps=1e-8}.
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
using namespace LA;
|
||||
|
||||
//H = diag(d) + c*(nearest neighbour coupling), never stored
|
||||
class Chain {
|
||||
NRVec<double> d;
|
||||
double c;
|
||||
public:
|
||||
Chain(int n, double cc) : d(n), c(cc) {for(int i=0; i<n; ++i) d[i]=i+0.1*i*i;}
|
||||
int nrows() const {return d.size();}
|
||||
int ncols() const {return d.size();}
|
||||
bool issymmetric() const {return true;}
|
||||
const double *diagonalof(NRVec<double> &r, const bool divide=false, bool cache=false) const
|
||||
{
|
||||
if(divide) {r.copyonwrite(); for(int i=0; i<r.size(); ++i) r[i]/=d[i]; return NULL;}
|
||||
r|=d; return &r[0]; //deep copy, r may share data with a stored Krylov vector
|
||||
}
|
||||
void gemv(const double beta, NRVec<double> &r, const char trans, const double alpha, const NRVec<double> &x) const
|
||||
{
|
||||
int n=d.size();
|
||||
if(beta==0) r=0.; else r*=beta;
|
||||
r.copyonwrite(); //before writing r[i]
|
||||
for(int i=0; i<n; ++i)
|
||||
{
|
||||
double t=d[i]*x[i];
|
||||
if(i>0) t+=c*x[i-1];
|
||||
if(i<n-1) t+=c*x[i+1];
|
||||
r[i]+=alpha*t;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
const int n=2000, nroots=3;
|
||||
Chain h(n,0.5);
|
||||
NRVec<double> e(nroots);
|
||||
NRVec<double> *v = new NRVec<double>[nroots];
|
||||
davidson(h,e,v,NULL,nroots,false,1e-8); //vectors in memory
|
||||
std::cout << "eigenvalues "<<e;
|
||||
davidson(h,e,v,NULL,nroots,false,1e-8,false); //vectors in scratch files
|
||||
lanczos(h,e,v,NULL,nroots,false,1e-10); //the same by Lanczos
|
||||
NRMat<double> hd = explicit_matrix<double>(h); //for checking by dense diagonalization
|
||||
delete[] v;
|
||||
}
|
||||
\end{lstlisting}
|
||||
The \code{gemv} above ignores \code{trans} since the matrix is symmetric; for \code{conjgrad} or \code{gmres} on a
|
||||
non-symmetric direct matrix, \code{'t'} has to be implemented as well (or \code{doguess=false} used with a square matrix).
|
||||
|
||||
\subsection{Linear systems and DIIS}
|
||||
|
||||
\begin{lstlisting}
|
||||
NRMat<double> a(n,n); ... //non-symmetric, diagonally dominant
|
||||
NRVec<double> b(n), x(n); ...
|
||||
bool ok=gmres(a,b,x,true,1e-10,50,false); //x: solution
|
||||
NRSMat<double> s(n); ... //symmetric positive definite
|
||||
ok=conjgrad(s,b,x,true,1e-10,1000,false);
|
||||
|
||||
//DIIS acceleration of a fixed-point iteration x=f(x) (from testoptimize.cc)
|
||||
DIIS<NRVec<double>,NRVec<double> > diis(4);
|
||||
NRVec<double> x(0.,n);
|
||||
for(int it=0; it<maxit; ++it)
|
||||
{
|
||||
NRVec<double> xn=f(x);
|
||||
NRVec<double> err=xn-x;
|
||||
diis.extrapolate(xn,err); //xn becomes the extrapolated vector
|
||||
x=xn;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
t.cc contains further calls: \code{davidson} on \code{NRMat}, \code{NRSMat}, \code{SparseMat} (symmetric and non-symmetric,
|
||||
out of core), \code{lanczos} and \code{davidson} compared with dense diagonalization, and \code{gmres} on dense and sparse
|
||||
matrices. testoptimize.cc tests the in-core and out-of-core \code{DIIS} and \code{reset()}.
|
||||
|
||||
%-----------------------------------------------------------------------------------------------------------------------------
|
||||
\section{Pitfalls}\label{sec:iterative:pitfalls}
|
||||
|
||||
\begin{itemize}
|
||||
\item A user \code{gemv} or \code{diagonalof} writing into \code{r} via \code{operator[]} without \code{r.copyonwrite()}
|
||||
corrupts the stored Krylov vectors (sec.~\ref{sec:iterative:matrix}).
|
||||
\item A literal \code{NULL} for \code{eivecs}, \code{initguess} or \code{target} breaks template argument deduction of
|
||||
\code{davidson}/\code{lanczos}; cast it or write \code{davidson<double>(...)}.
|
||||
\item \code{NRMat::issymmetric()} is \code{false}: \code{davidson} on an \code{NRMat} always uses the non-symmetric algorithm, and
|
||||
\code{lanczos} refuses it. Use \code{NRSMat} (or a \code{SparseMat} with the symmetric flag) for symmetric matrices.
|
||||
\item The Davidson threshold \code{eps} bounds the last expansion coefficient, the Lanczos one the change of the eigenvalues,
|
||||
the CG one the norm of the search direction (absolute) and the GMRES one the residual relative to the initial one.
|
||||
\item \code{davidson} with several roots fails if \code{maxkrylov} is reached after the first root has converged; \code{lanczos}
|
||||
has no restart at all.
|
||||
\item The diagonal used for preconditioning must have no small elements of the wrong sign (zero elements are skipped by the
|
||||
library classes); in \code{davidson} the denominators $|A_{ii}-\lambda|$ are bounded by 0.1 from below.
|
||||
\item \code{doguess=true} (the default of \code{conjgrad} and \code{gmres}) discards the contents of \code{x}.
|
||||
\item Scratch files of the out-of-core variants are created in the current directory and survive an exception.
|
||||
\item \code{DIIS} is not copyable (copy constructor and assignment are deleted, it owns raw storage); use references or pointers,
|
||||
or default-construct it in arrays (\code{NRVec<DIIS<...> >}) and call \code{setup()}.
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,65 @@
|
||||
%common macros and package setup for the LA library manual
|
||||
%all chapters use only these macros for code and reference entries, so that the layout can be changed in one place
|
||||
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage{lmodern}
|
||||
\usepackage{amsmath,amssymb}
|
||||
\usepackage{xcolor}
|
||||
\usepackage{listings}
|
||||
\usepackage{booktabs}
|
||||
\usepackage{longtable}
|
||||
\usepackage{array}
|
||||
\usepackage[colorinlistoftodos,textsize=small]{todonotes}
|
||||
\usepackage[hidelinks]{hyperref}
|
||||
|
||||
%code listings
|
||||
\lstdefinestyle{lacode}{
|
||||
language=C++,
|
||||
basicstyle=\ttfamily\small,
|
||||
keywordstyle=\bfseries,
|
||||
commentstyle=\itshape\color{black!60},
|
||||
columns=fullflexible,
|
||||
keepspaces=true,
|
||||
tabsize=8,
|
||||
breaklines=true,
|
||||
frame=leftline,
|
||||
xleftmargin=1.5em,
|
||||
framexleftmargin=0.5em,
|
||||
showstringspaces=false,
|
||||
escapechar=@
|
||||
}
|
||||
\lstset{style=lacode}
|
||||
|
||||
%inline code: \code{NRVec<double>}; underscores, &, <, > need no escaping (no % and no unbalanced braces)
|
||||
\newcommand{\code}[1]{\texttt{\detokenize{#1}}}
|
||||
|
||||
%name of a header file
|
||||
\newcommand{\header}[1]{\texttt{\detokenize{#1}}}
|
||||
|
||||
%header(s) where the described entities are declared, placed at the beginning of a chapter or section
|
||||
\newcommand{\laheader}[1]{\par\noindent\textbf{Header:} \texttt{\detokenize{#1}}\par\medskip}
|
||||
|
||||
%reference list of functions; usage:
|
||||
%\begin{lareference}
|
||||
%\lafunc{void axpy(const T alpha, const NRVec &x)}{this $\leftarrow$ this + alpha x}
|
||||
%\end{lareference}
|
||||
\newenvironment{lareference}
|
||||
{\begin{list}{}{\setlength{\leftmargin}{2em}\setlength{\itemindent}{-2em}\setlength{\itemsep}{3pt plus 1pt}\setlength{\parsep}{0pt}\setlength{\topsep}{4pt}}\raggedright}
|
||||
{\end{list}}
|
||||
\newcommand{\lafunc}[2]{\item{\ttfamily\small\detokenize{#1}}\par\nopagebreak\hspace*{2em}\begin{minipage}[t]{\dimexpr\linewidth-2em\relax}#2\end{minipage}}
|
||||
|
||||
%a remark visible in the text for the author to decide (unclear intended usage, possibly obsolete code, possible bug ...)
|
||||
%(the list of todos shows only the section number, since the text may contain \code, which cannot be moved)
|
||||
\newcommand{\latodo}[1]{\todo[inline,color=yellow!40,caption={Question in section \thesection}]{#1}}
|
||||
|
||||
%a warning paragraph (pitfalls)
|
||||
\newcommand{\lawarning}[1]{\par\smallskip\noindent\fbox{\parbox{\dimexpr\linewidth-2\fboxsep-2\fboxrule\relax}{\textbf{Caution:} #1}}\par\smallskip}
|
||||
|
||||
%notation
|
||||
\newcommand{\LA}{\textsf{LA}}
|
||||
\newcommand{\Tr}{^{\mathsf T}}
|
||||
\newcommand{\Hc}{^{\dagger}}
|
||||
|
||||
%long unbreakable code names: allow looser lines rather than overfull ones
|
||||
\setlength{\emergencystretch}{3em}
|
||||
Binary file not shown.
@@ -0,0 +1,39 @@
|
||||
%LA library manual - master file
|
||||
%compile: pdflatex manual; pdflatex manual (twice, for contents and references)
|
||||
%the visible \latodo notes are listed after the contents; to hide them, load todonotes with the option disable in lamacros.tex
|
||||
|
||||
\documentclass[11pt,a4paper]{report}
|
||||
\input{lamacros}
|
||||
|
||||
\title{\LA: a C++ linear algebra library\\[1ex]\large User manual}
|
||||
\author{Ji\v{r}\'{\i} Pittner}
|
||||
\date{\today}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
\tableofcontents
|
||||
\listoftodos[List of open questions for the author]
|
||||
|
||||
\include{introduction}
|
||||
\include{building}
|
||||
\include{concepts}
|
||||
\include{vec}
|
||||
\include{mat}
|
||||
\include{smat}
|
||||
\include{sparse}
|
||||
\include{nonclass}
|
||||
\include{iterative}
|
||||
\include{optimize}
|
||||
\include{tensor}
|
||||
\include{fourindex}
|
||||
\include{permutation}
|
||||
\include{polynomial}
|
||||
\include{vecmat3}
|
||||
\include{quaternion}
|
||||
\include{bitvector}
|
||||
\include{regression}
|
||||
\include{utilities}
|
||||
|
||||
\include{biblio}
|
||||
|
||||
\end{document}
|
||||
+327
@@ -0,0 +1,327 @@
|
||||
\chapter{Dense matrices: NRMat}\label{ch:mat}
|
||||
\laheader{mat.h, mat.cc, strassen.cc (sparse-related members: sparsemat.h, sparsemat.cc, sparsesmat.h, sparsesmat.cc)}
|
||||
|
||||
\section{Purpose}\label{sec:mat:purpose}
|
||||
\code{NRMat<T>} is the general dense rectangular matrix of the \LA{} library, the
|
||||
workhorse for everything that is passed to BLAS level~3 and LAPACK. It is a reference counted
|
||||
handle to a contiguous row-major array of $n\times m$ elements of type \code{T}.
|
||||
For symmetric/Hermitian matrices stored in packed form see \code{NRSMat} (chapter~\ref{ch:smat}),
|
||||
for sparse matrices chapter~\ref{ch:sparse}, for vectors \code{NRVec} (chapter~\ref{ch:vec}).
|
||||
Decompositions, eigenvalue problems, linear solvers and matrix functions (exponential etc.) are
|
||||
free functions described in chapter~\ref{ch:nonclass}.
|
||||
\code{NRMat_from1<T>} (section~\ref{sec:mat:from1}) is a thin derived class with 1-based
|
||||
\code{operator()}.
|
||||
|
||||
\section{Storage and class-specific concepts}\label{sec:mat:storage}
|
||||
|
||||
\paragraph{Layout.} Element $A_{ij}$ ($0\le i<n$, $0\le j<m$) is stored at offset $i\,m+j$ of one
|
||||
contiguous block, i.e.\ C row-major order (sec.~\ref{sec:concepts:storage}). BLAS calls use
|
||||
\code{CblasRowMajor}; where Fortran routines are called, the transposition implied by the
|
||||
column-major convention is handled internally. Dimensions are \code{int}; offsets are mostly
|
||||
computed in \code{size_t}.
|
||||
|
||||
\paragraph{Reference counting.} \code{NRMat} follows the general copy-on-demand scheme of
|
||||
section~\ref{sec:concepts:refcount}: the copy constructor and \code{operator=} share data,
|
||||
\code{|=} makes a deep copy (sec.~\ref{sec:concepts:assign}), all modifying member functions
|
||||
call \code{copyonwrite()} themselves, and an explicit \code{copyonwrite()} is needed only before writing
|
||||
through \code{operator[]}, \code{operator()} or the raw pointer \code{operator T*()}.
|
||||
In DEBUG builds the non-const \code{operator[]} and \code{operator()} raise \code{laerror} when the
|
||||
reference count is larger than one (controlled by \code{_LA_count_check}); read elements of a
|
||||
possibly shared matrix through a const reference.
|
||||
|
||||
\paragraph{Element types.} \code{mat.cc} explicitly instantiates \code{NRMat<T>} for
|
||||
\code{double}, \code{std::complex<double>}, and the integer types \code{char}, \code{short}, \code{int},
|
||||
\code{long}, \code{long long} and their unsigned variants. Many numerical members are only
|
||||
specialized (BLAS-backed) for \code{double} and \code{complex<double>} and have no generic
|
||||
definition, so using them with an integer type results in a link error. Table~\ref{tab:mat:types}
|
||||
summarizes this. Nested element types (e.g.\ \code{NRMat<NRVec<double> >}) are supported by the
|
||||
reference counting (type-nested \code{copyonwrite}, sec.~\ref{sec:concepts:traits}), but several
|
||||
members are restricted to plain data (\code{submatrix(int,int,int,int)},
|
||||
\code{storesubmatrix(int,int,const NRMat&)}, \code{operator&}).
|
||||
|
||||
\begin{table}[htbp]
|
||||
\centering\small
|
||||
\begin{tabular}{>{\raggedright\arraybackslash}p{0.55\linewidth}>{\raggedright\arraybackslash}p{0.35\linewidth}}
|
||||
\toprule
|
||||
Members & Available for \\
|
||||
\midrule
|
||||
\texttt{gemm}, \texttt{norm}, \texttt{axpy(T,const NRMat\&)}, \texttt{dot}, \texttt{transpose}, \texttt{diagmultl}, \texttt{diagmultr},
|
||||
\texttt{operator*(const NRSMat\&)}, \texttt{diagonalset}, \texttt{inverse}, \texttt{svdinverse}, \texttt{absadjoint},
|
||||
\texttt{randomize}, \texttt{amax}, \texttt{amin} & \texttt{double}, \texttt{complex<double>} \\
|
||||
\texttt{operator*(const NRMat\&)} & \texttt{double}, \texttt{complex<double>}, \texttt{int} \\
|
||||
\texttt{diagonalof}, \texttt{orthonormalize}, \texttt{strassen}, \texttt{gemm} with \texttt{SparseMat} & \texttt{double} only \\
|
||||
\texttt{nonhermiticity} & \texttt{complex<double>} only \\
|
||||
everything else & all instantiated types \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Availability of \texttt{NRMat<T>} members for the instantiated element types.}\label{tab:mat:types}
|
||||
\end{table}
|
||||
|
||||
\paragraph{The MATPTR representation.}\label{sec:mat:matptr}
|
||||
\code{configure --enable-matptr} adds \code{-DMATPTR}, which switches the internal pointer from
|
||||
\code{T *v} to \code{T **v}: an array of row pointers into the same single contiguous block
|
||||
(\code{v[0]} points to the beginning). For the user the interface is the same: \code{operator[](i)}
|
||||
returns a \code{T*} to the beginning of row $i$ in both cases, \code{operator()(i,j)} and
|
||||
\code{operator T*()} (pointer to the whole contiguous row-major block) behave identically,
|
||||
so code passing the matrix to BLAS is unaffected. Differences:
|
||||
\begin{itemize}
|
||||
\item \code{NRMat(const NRVec<T>&, n, m, offset)} and \code{NRVec(const NRMat<T>&)} make deep
|
||||
copies with MATPTR, whereas without it they share the data with the source.
|
||||
\item It is incompatible with CUDA (MATPTR is undefined when \code{CUDALA} is set).
|
||||
\end{itemize}
|
||||
MATPTR is rarely used and less tested than the default representation; the library compiles with it and passes the optimization
|
||||
regression test (\code{testoptimize}).
|
||||
|
||||
\paragraph{Strassen multiplication.}\label{sec:mat:strassen}
|
||||
Unless \code{NO_STRASSEN} is defined, \code{NRMat} declares \code{strassen()} and \code{s_cutoff()}
|
||||
(specialized for \code{double} only, in \code{strassen.cc}). They call the external Fortran-compatible
|
||||
routines \code{fmm} and \code{strassen_cutoff}, which are \emph{not} part of \LA{}
|
||||
(the test code in \code{t.cc} mentions linking \code{-lstrassen}). Strassen multiplication is never
|
||||
used implicitly: \code{operator*} and \code{gemm} always call BLAS \code{gemm}. Note that
|
||||
\code{Makefile.am} unconditionally adds \code{-DNO_STRASSEN}, so in the standard build these members do not exist.
|
||||
\lawarning{The Strassen interface is \emph{experimental}: it has not been used or tested for a long time. It is kept for possible future work,
|
||||
in particular if fast (sub-$O(n^3)$) matrix multiplication algorithms become practically useful.}
|
||||
|
||||
\section{Construction, assignment, conversion}\label{sec:mat:constr}
|
||||
\begin{lareference}
|
||||
\lafunc{NRMat()}{Empty matrix ($0\times 0$, no data, no reference counter).}
|
||||
\lafunc{NRMat(const int n, const int m, const GPUID loc = undefined)}{$n\times m$ matrix, elements \emph{not} initialized.}
|
||||
\lafunc{NRMat(const T &a, const int n, const int m); NRMat(const T &a, const int n, const int m, const GPUID loc)}{$n\times m$ matrix with \emph{all} elements equal to \code{a} (contrast with \code{operator=(const T&)}).}
|
||||
\lafunc{NRMat(const T *a, const int n, const int m)}{Deep copy of $nm$ elements from a row-major C array.}
|
||||
\lafunc{template<int R, int C> NRMat(const T (&a)[R][C])}{From a two-dimensional C array; also allows brace initialization \code{NRMat<int> x({{1,2,3},{4,5,6}});}.}
|
||||
\lafunc{NRMat(const Mat3<T> &rhs)}{$3\times3$ matrix from the small fixed-size class \code{Mat3} (chapter~\ref{ch:vecmat3}).}
|
||||
\lafunc{explicit NRMat(const int n) = delete}{A single integer argument is forbidden (it would otherwise silently create a $3\times3$ matrix
|
||||
through \code{Mat3}); use \code{NRMat(n,m)}.}
|
||||
\lafunc{NRMat(const NRMat &rhs)}{Shallow copy (shares data).}
|
||||
\lafunc{NRMat(const typename LA_traits_complex<T>::NRMat_Noncomplex_type &rhs, bool imagpart = false)}{Complexifying (non-explicit) constructor, defined for \code{T = complex<double>}: \code{rhs} (\code{NRMat<double>}) becomes the real part (or the imaginary part if \code{imagpart}), the other part is zero.}
|
||||
\lafunc{explicit NRMat(const NRMat<std::complex<T> > &rhs, bool imagpart = false)}{Decomplexifying constructor, defined for \code{T = double}: real part (or imaginary part if \code{imagpart}) of \code{rhs}.}
|
||||
\lafunc{explicit NRMat(const NRSMat<T> &rhs)}{Full square matrix from a packed symmetric one ($A_{ij}=A_{ji}$; for complex types hermitian, $A_{ij}=A_{ji}^*$).}
|
||||
\lafunc{explicit NRMat(const NRVec<T> &rhs, const int n, const int m, const int offset = 0)}{View of the vector data from position \code{offset} as an $n\times m$ matrix. Without MATPTR the data are \emph{shared} (reference count of the vector is incremented); with MATPTR they are copied. A nonzero offset gives a view into the middle of the vector without copying (a deliberate feature);
|
||||
its condition of use is that the vector outlives the matrix, see Pitfalls. The constructor prints a reminder to \code{std::cout}.}
|
||||
\lafunc{explicit NRMat(const NRPerm<int> &p, const bool direction, const bool parity = false)}{Permutation matrix: $A_{i,p(i)}=1$ if \code{direction}, else $A_{p(i),i}=1$ (permutation 1-based, matrix 0-based); if \code{parity}, the nonzero elements are the parity of $p$ instead of 1.}
|
||||
\lafunc{explicit NRMat(const WeightPermutation<int,T> &p, const bool direction)}{Weight times permutation matrix.}
|
||||
\lafunc{explicit NRMat(const PermutationAlgebra<int,T> &p, const bool direction, const int nforce = 0)}{Sum of weighted permutation matrices of the elements of a permutation algebra; size taken from the first element unless \code{nforce>0}. (The header remarks that the permutation-matrix representation of $S_n$ is reducible to only the irreps $[n]$ and $[n-1,1]$, so e.g.\ Young projectors cannot be represented this way.) See chapter~\ref{ch:permutation}.}
|
||||
\lafunc{explicit NRMat(const SparseMat<T> &rhs); explicit NRMat(const SparseSMat<T> &rhs); explicit NRMat(const SemiSparseMat<T> &rhs)}{Dense matrix from sparse ones (chapter~\ref{ch:sparse}).}
|
||||
\lafunc{explicit NRMat(const CSRMat<T> &rhs)}{Not supported: \code{CSRMat} is unfinished, the declaration is kept for possible future work (no definition, a link error if used).}
|
||||
\lafunc{~NRMat()}{Decrements the reference count, frees the data when it reaches zero.}
|
||||
\lafunc{NRMat & operator=(const NRMat &rhs)}{Shallow assignment (sec.~\ref{sec:concepts:assign}).}
|
||||
\lafunc{NRMat & operator|=(const NRMat &rhs)}{Deep copy.}
|
||||
\lafunc{NRMat & operator=(const T &a)}{Sets the matrix to $a\,\mathbf 1$ (diagonal $a$, off-diagonal 0); the matrix must already be square (checked in DEBUG only), it is not resized.}
|
||||
\lafunc{void identity()}{\code{*this = (T)1}.}
|
||||
\lafunc{void resize(int n, int m)}{Change dimensions; if either is 0 the matrix is deallocated. If the data are shared, the matrix is detached and new (uninitialized) storage allocated; if the size changes, the contents are lost (not preserved); if the size is unchanged and unshared, nothing happens.}
|
||||
\lafunc{void dealloc(void)}{\code{resize(0,0)}.}
|
||||
\lafunc{void copyonwrite(bool detachonly = false, bool deep = true)}{Make the data unshared (sec.~\ref{sec:concepts:refcount}). With \code{detachonly} new storage is allocated but the data are not copied (contents undefined); \code{deep} controls the recursive \code{copyonwrite} of nested element types.}
|
||||
\lafunc{int getcount() const}{Current reference count (0 for an empty matrix).}
|
||||
\lafunc{void swap(NRMat &rhs)}{Exchange contents of two matrices in $O(1)$ (pointers, sizes, counters).}
|
||||
\lafunc{GPUID getlocation() const; void moveto(const GPUID dest)}{GPU support, experimental, see sec.~\ref{sec:concepts:gpu}; without \code{CUDALA} they return \code{cpu} resp.\ do nothing.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Element access}\label{sec:mat:access}
|
||||
\begin{lareference}
|
||||
\lafunc{T* operator[](const int i); const T* operator[](const int i) const}{Pointer to the beginning of row $i$ (0-based), so that \code{a[i][j]} is $A_{ij}$. DEBUG: row range check, and the shared-data check for the non-const version (the column index is not checked).}
|
||||
\lafunc{T& operator()(const int i, const int j); const T& operator()(const int i, const int j) const}{Element $A_{ij}$, 0-based; DEBUG: range checks for both indices, and shared-data check for the non-const version. CPU only (\code{NOT_GPU}).}
|
||||
\lafunc{const T get_ij(const int i, const int j) const}{Element by value (works also for data in GPU memory).}
|
||||
\lafunc{operator T*(); operator const T*() const}{Pointer to the contiguous row-major data block (no reference count check even in DEBUG).}
|
||||
\lafunc{int nrows() const; int ncols() const; size_t size() const}{Number of rows, columns, elements.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Arithmetic and BLAS/LAPACK-backed operations}\label{sec:mat:arith}
|
||||
Unless stated otherwise, dimension mismatches are detected only in DEBUG builds.
|
||||
Note that scalar addition and subtraction act on the \emph{diagonal} only, i.e.\ they add $a\,\mathbf 1$.
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{NRMat & operator+=(const T &a); NRMat & operator-=(const T &a)}{$A\leftarrow A\pm a\,\mathbf 1$ (square matrices only).}
|
||||
\lafunc{inline const NRMat operator+(const T &a) const; inline const NRMat operator-(const T &a) const}{$A\pm a\,\mathbf 1$ by value (the scalar is added to the diagonal).}
|
||||
\lafunc{NRMat & operator*=(const T &a); inline const NRMat operator*(const T &a) const}{Scaling by a scalar (BLAS \code{scal} for double/complex).}
|
||||
\lafunc{const NRMat operator-() const}{Unary minus.}
|
||||
\lafunc{NRMat & operator+=(const NRMat &rhs); NRMat & operator-=(const NRMat &rhs)}{$A\leftarrow A\pm B$ (BLAS \code{axpy} for double/complex).}
|
||||
\lafunc{inline const NRMat operator+(const NRMat &rhs) const; inline const NRMat operator-(const NRMat &rhs) const}{$A\pm B$ by value.}
|
||||
\lafunc{NRMat & operator+=(const NRSMat<T> &rhs); NRMat & operator-=(const NRSMat<T> &rhs); inline const NRMat operator+(const NRSMat<T> &rhs) const; inline const NRMat operator-(const NRSMat<T> &rhs) const}{Addition/subtraction of a packed symmetric matrix (square $A$).}
|
||||
\lafunc{NRMat & operator^=(const NRMat<T> &rhs)}{Hadamard (element-wise) product $A_{ij}\leftarrow A_{ij}B_{ij}$.}
|
||||
\lafunc{void axpy(const T alpha, const NRMat &x)}{$A\leftarrow A+\alpha X$ (BLAS \code{axpy}).}
|
||||
\lafunc{const NRMat operator*(const NRMat &rhs) const}{Matrix product $AB$ (BLAS \code{gemm}; simple loops for \code{int}).}
|
||||
\lafunc{void gemm(const T &beta, const NRMat &a, const char transa, const NRMat &b, const char transb, const T &alpha)}{$C\leftarrow\alpha\,\mathrm{op}(A)\,\mathrm{op}(B)+\beta C$ with $C$ = \code{*this}, which must already have the correct dimensions (it is not resized; if $\beta=0$ its contents may be uninitialized). \code{trans}: \code{'n'} no transposition; for \code{double} any other character means transposition; for \code{complex<double>} \code{'t'} transposition and \code{'c'} conjugate transposition (case insensitive). Returns immediately when $\alpha=0$ and $\beta=1$. \code{*this} must not share storage with \code{a} or \code{b}.}
|
||||
\lafunc{void gemm(const T &beta, const SparseMat<T> &a, const char transa, const NRMat &b, const char transb, const T &alpha)}{The same with a sparse first factor; \code{double} only (the complex version calls \code{laerror("not implemented yet")}), see chapter~\ref{ch:sparse}.}
|
||||
\lafunc{const NRMat operator*(const NRSMat<T> &rhs) const}{$AS$ with packed symmetric (real) or hermitian (complex) $S$ (BLAS \code{dspmv}/\code{zhpmv} per row).}
|
||||
\lafunc{SparseSMat<T> operator*(const SparseSMat<T> &rhs) const}{Dense times sparse, the result is sparse (chapter~\ref{ch:sparse}).}
|
||||
\lafunc{const NRVec<T> operator*(const NRVec<T> &rhs) const; const NRVec<std::complex<T> > operator*(const NRVec<std::complex<T> > &rhs) const}{Matrix--vector product via \code{NRVec::gemv} (the second form: real matrix times complex vector).}
|
||||
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x) const; void gemv(const T beta, NRVec<std::complex<T> > &r, const char trans, const T alpha, const NRVec<std::complex<T> > &x) const}{$r\leftarrow\alpha\,\mathrm{op}(A)x+\beta r$; forwards to \code{r.gemv(beta,*this,trans,alpha,x)}, see chapter~\ref{ch:vec}.}
|
||||
\lafunc{const T dot(const NRMat &rhs) const}{$\sum_{ij}A_{ij}^*B_{ij}$ (Frobenius inner product, conjugating \code{*this}).}
|
||||
\lafunc{const NRSMat<T> transposedtimes() const}{$A\Tr A$ ($A\Hc A$ for complex) as packed matrix.}
|
||||
\lafunc{const NRSMat<T> timestransposed() const}{$AA\Tr$ ($AA\Hc$ for complex) as packed matrix. For complex matrices both results are hermitian (lower triangle stored).}
|
||||
\lafunc{void diagmultl(const NRVec<T> &rhs); void diagmultr(const NRVec<T> &rhs)}{$A\leftarrow DA$ (scale rows) resp.\ $A\leftarrow AD$ (scale columns), $D=\mathrm{diag}(\mathtt{rhs})$.}
|
||||
\lafunc{const NRMat operator&(const NRMat &rhs) const; const NRMat oplus(const NRMat &rhs) const}{Direct sum $A\oplus B$ (block-diagonal matrix). \code{oplus} returns the other operand when one is empty; \code{operator&} is plain-data only.}
|
||||
\lafunc{const NRMat operator|(const NRMat<T> &rhs) const}{Direct (Kronecker) product $A\otimes B$, $(A\otimes B)_{i n_B+k,\,j m_B+l}=A_{ij}B_{kl}$.}
|
||||
\lafunc{const NRMat otimes(const NRMat &rhs, bool reversecolumns = false) const}{Kronecker product as \code{operator|}; with \code{reversecolumns} the column index is ordered the other way, $R_{i n_B+k,\,l m_A+j}=A_{ij}B_{kl}$. Returns the empty operand when one is empty.}
|
||||
\lafunc{void strassen(const T beta, const NRMat &a, const char transa, const NRMat &b, const char transb, const T alpha); void s_cutoff(const int, const int, const int, const int) const}{Only without \code{NO_STRASSEN}, \code{double} only: Strassen multiplication with \code{gemm} semantics (dimensions always checked) and setting of the cutoffs of the external Strassen library (section~\ref{sec:mat:strassen}).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Other member functions}\label{sec:mat:other}
|
||||
|
||||
\subsection{Rows, columns, submatrices}
|
||||
\begin{lareference}
|
||||
\lafunc{const NRVec<T> row(const int i, int len = -1, int offset = 0) const}{Copy of \code{len} elements of row $i$ starting at column \code{offset} (\code{len<0}: \code{ncols()}).}
|
||||
\lafunc{void rowset(const NRVec<T> &r, const int i, int len = -1, int offset = 0)}{Inverse of \code{row}: stores \code{len} elements of \code{r} into row $i$ from column \code{offset}.}
|
||||
\lafunc{const NRVec<T> column(const int j, int l = -1) const; void columnset(const NRVec<T> &r, const int j, int l = -1)}{Copy of the first \code{l} (default all) elements of column $j$, resp.\ store them.}
|
||||
\lafunc{const NRMat submatrix(const int fromrow, const int torow, const int fromcol, const int tocol) const}{Copy of the block of rows \code{fromrow..torow} and columns \code{fromcol..tocol} (inclusive). Plain data only.}
|
||||
\lafunc{const NRMat submatrix(const NRVec<int> &rows, const NRVec<int> &cols) const}{Copy of the elements $A_{r_i c_j}$ for given (arbitrary) lists of row and column indices; indices are always range-checked.}
|
||||
\lafunc{void storesubmatrix(const int fromrow, const int fromcol, const NRMat &rhs)}{Store \code{rhs} as a block with the upper left corner at (\code{fromrow},\code{fromcol}). Plain data only.}
|
||||
\lafunc{void storesubmatrix(const NRVec<int> &rows, const NRVec<int> &cols, const NRMat &rhs)}{Scatter: $A_{r_i c_j}\leftarrow R_{ij}$.}
|
||||
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide = 0, bool cache = false) const}{\code{double} only. For a square matrix copy the diagonal into \code{r}, or with \code{divide} divide \code{r} element-wise by the (nonzero) diagonal elements. For a non-square matrix the diagonal of $A\Tr A$ (squared column norms) is used instead; \code{r} must have \code{ncols()} elements. Returns pointer to \code{r}'s data, or NULL if \code{divide}. \code{cache} is unused. Same interface as in other matrix classes, so that it can be used by iterative solvers as a preconditioner (chapter~\ref{ch:iterative}).}
|
||||
\lafunc{void diagonalset(const NRVec<T> &r)}{Set the diagonal; \code{r} has $\min(n,m)$ elements.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Transposition and conjugation}
|
||||
\begin{lareference}
|
||||
\lafunc{const NRMat transpose(bool conj = false) const}{$A\Tr$, or $A\Hc$ if \code{conj} (complex). \code{double} and \code{complex<double>} only.}
|
||||
\lafunc{NRMat& transposeme(const int n = 0)}{In-place transposition of a square matrix (no conjugation); with \code{n>0} only the leading $n\times n$ block is transposed (this also works for a non-square matrix with $n\le\min(\text{rows},\text{cols})$). Dimensions are not changed, so a non-square matrix cannot be transposed in place.}
|
||||
\lafunc{NRMat& conjugateme(); const NRMat conjugate() const}{Complex conjugation in place resp.\ by value; no-op for real types.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Reordering, permutations, rotations, scaling}
|
||||
\begin{lareference}
|
||||
\lafunc{NRMat & swap_rows(); NRMat & swap_cols()}{Reverse the order of rows (columns).}
|
||||
\lafunc{NRMat & swap_rows_cols()}{$A_{ij}\leftarrow A_{n-1-i,\,m-1-j}$ (reverse both, i.e.\ the whole element array).}
|
||||
\lafunc{NRMat & swap_rows(const int i, const int j); NRMat & swap_cols(const int i, const int j)}{Exchange two rows (columns).}
|
||||
\lafunc{NRMat & rotate_rows(const int i, const int j, const T phi); NRMat & rotate_cols(const int i, const int j, const T phi)}{Plane rotation of rows (columns) $i,j$: $x_i\leftarrow c\,x_i+s\,x_j$, $x_j\leftarrow c\,x_j-s\,x_i$ with $c=\cos\phi$, $s=\sin\phi$ (evaluated in type \code{T}).}
|
||||
\lafunc{void scale_row(const int i, const T f); void scale_col(const int i, const T f)}{Multiply row (column) $i$ by $f$.}
|
||||
\lafunc{const NRMat permuted_rows(const NRPerm<int> &p, const bool inverse = false) const}{Row $i$ of $A$ becomes row $p(i)$ of the result (indices of $p$ are 1-based); with \code{inverse} row $i$ of the result is row $p(i)$ of $A$.}
|
||||
\lafunc{const NRMat permuted_cols(const NRPerm<int> &p, const bool inverse = false) const; const NRMat permuted_both(const NRPerm<int> &p, const NRPerm<int> &q, const bool inverse = false) const}{The same for columns, resp.\ rows by $p$ and columns by $q$ simultaneously.}
|
||||
\lafunc{void permuteme_rows(const CyclePerm<int> &p); void permuteme_cols(const CyclePerm<int> &p)}{In-place permutation given in cycle notation: in each cycle $(c_1\,c_2\ldots c_L)$ row (column) $c_k$ moves to $c_{k+1}$ and $c_L$ to $c_1$.}
|
||||
\lafunc{void axpy(const T alpha, const NRPerm<int> &p, const bool direction)}{Add $\alpha$ times the permutation matrix (same convention as the constructor from \code{NRPerm}).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Reductions, norms, properties}
|
||||
\begin{lareference}
|
||||
\lafunc{const typename LA_traits<T>::normtype norm(const T scalar = (T)0) const}{Frobenius norm $\|A-s\,\mathbf 1\|_F$ (BLAS \code{nrm2} when $s=0$).}
|
||||
\lafunc{const T trace() const}{$\sum_i A_{ii}$ (square matrices).}
|
||||
\lafunc{const T sum() const; const NRVec<T> rsum() const; const NRVec<T> csum() const}{Sum of all elements; vector of row sums (length \code{nrows()}, sum over the column index); vector of column sums (length \code{ncols()}).}
|
||||
\lafunc{inline const T amax() const; inline const T amin() const}{The element (not its index) with the largest resp.\ smallest absolute value; for complex the measure is $|\mathrm{Re}|+|\mathrm{Im}|$ (BLAS \code{i?amax} convention).}
|
||||
\lafunc{const typename LA_traits<T>::normtype nonsymmetry() const}{$\sum_{i>j}|A_{ij}-A_{ji}|^2$ (square of the norm, no square root).}
|
||||
\lafunc{const typename LA_traits<T>::normtype nonhermiticity() const}{\code{complex<double>} only: $\sum_{i\ge j}|A_{ij}-A_{ji}^*|^2$.}
|
||||
\lafunc{NRMat<typename LA_traits<T>::normtype> abs() const}{Matrix of element-wise absolute values.}
|
||||
\lafunc{NRMat<typename LA_traits<T>::normtype> diffabs(const NRMat &rhs) const}{Element-wise $|A_{ij}|-|B_{ij}|$.}
|
||||
\lafunc{const bool operator==(const NRMat &rhs) const; const bool operator!=(const NRMat &rhs) const}{Equality of dimensions and contents; for plain types a bitwise (\code{memcmp}) comparison, element-wise for nested types.}
|
||||
\lafunc{bool issymmetric() const; inline void simplify()}{Always \code{false} resp.\ no-op; they exist for interface compatibility with the sparse matrix classes, so that generic code can use \code{NRMat} in their place.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Orthonormalization, inverse, random fill}
|
||||
\begin{lareference}
|
||||
\lafunc{void orthonormalize(const bool rowcol, const NRSMat<T> *metric = NULL)}{\code{double} only. Modified Gram--Schmidt orthonormalization of the rows (\code{rowcol==true}) or columns (\code{false}) of the matrix in place, optionally with respect to the inner product $x\Tr S y$ given by \code{metric}. A zero vector (or non-positive metric) raises \code{laerror}.}
|
||||
\lafunc{NRMat inverse() const}{$A^{-1}$ via \code{calcinverse} (LAPACK linear solve, chapter~\ref{ch:nonclass}).}
|
||||
\lafunc{NRMat svdinverse(const typename LA_traits<T>::normtype thr = 0) const}{Pseudo-inverse via SVD, singular values below \code{thr} are dropped.}
|
||||
\lafunc{NRMat absadjoint(const typename LA_traits<T>::normtype thr = 0) const}{Adjugate (transposed cofactor) matrix computed via SVD up to the factor $\det U\det V$ (a sign, or a phase in the complex case), cf.\ \code{calcsvdinverse} in chapter~\ref{ch:nonclass}. \code{thr} is not used in this mode.}
|
||||
\lafunc{void randomize(const typename LA_traits<T>::normtype &x)}{Fill with uniform pseudorandom numbers from $[-x,x)$ (\code{RANDDOUBLESIGNED}); for complex, real and imaginary parts independently. (The source comment saying $[0,x]$ is incorrect.)}
|
||||
\lafunc{void clear()}{Set all elements to zero (detaches shared data without copying).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Input/output}
|
||||
See section~\ref{sec:concepts:io} for the general conventions.
|
||||
\begin{lareference}
|
||||
\lafunc{void put(int fd, bool dimensions = 1, bool transposed = false, bool orcaformat = false) const}{Binary output to a file descriptor: optionally the two dimensions (as \code{int}), then the elements row by row. With \code{transposed} the matrix is written as its transpose (dimensions swapped, element by element). With \code{orcaformat} \code{sizeof(T)} is written as a third \code{int} after the dimensions (format of the ORCA program).}
|
||||
\lafunc{void get(int fd, bool dimensions = 1, bool transposed = false, bool orcaformat = false)}{Binary input, counterpart of \code{put}; with \code{dimensions} the matrix is resized, otherwise it must already have the right size. With \code{orcaformat} the stored element size is checked.}
|
||||
\lafunc{void fprintf(FILE *f, const char *format, const int modulo) const}{Formatted output via \code{lawritemat} with a printf-like \code{format}.}
|
||||
\lafunc{void fscanf(FILE *f, const char *format)}{Formatted input: reads the two dimensions, resizes, then the elements with the scanf-like \code{format}.}
|
||||
\lafunc{void printsorted(std::ostream &s, int direction = 1, bool absvalue = false, bool stable = false, typename LA_traits<T>::normtype thr = 0, int topmax = 0) const}{Print the elements sorted by value (or absolute value), each line containing row index, column index and value; delegates to \code{NRVec::printsorted} (chapter~\ref{ch:vec}), where \code{direction}, \code{stable}, threshold \code{thr} and maximum count \code{topmax} are described.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Sparse matrix interoperability}
|
||||
Besides the constructors, \code{operator*(const SparseSMat<T>&)} and \code{gemm} with a \code{SparseMat}
|
||||
factor listed above, \code{NRMat} declares
|
||||
\begin{lareference}
|
||||
\lafunc{NRMat & operator+=(const SparseMat<T> &rhs); NRMat & operator-=(const SparseMat<T> &rhs)}{Add/subtract the sparse matrix (defined in \code{sparsemat.h}; a symmetric \code{SparseMat} is unfolded, without conjugation).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Related free functions}\label{sec:mat:free}
|
||||
\begin{lareference}
|
||||
\lafunc{template<class T> const NRMat<T> operator+(const T &a, const NRMat<T> &rhs); template<class T> const NRMat<T> operator*(const T &a, const NRMat<T> &rhs)}{$a\,\mathbf 1+A$ and $aA$.}
|
||||
\lafunc{template<class T> const NRMat<T> operator-(const T &a, const NRMat<T> &rhs) = delete}{Deliberately not available (compile error); write \code{-A+a} for $a\,\mathbf 1-A$.}
|
||||
\lafunc{template<typename T> NRMat<std::complex<T> > complexify(const NRMat<T> &rhs)}{Complex matrix with real part \code{rhs}.}
|
||||
\lafunc{template<typename T> NRMat<std::complex<T> > complexify(const NRMat<T> &rhsr, const NRMat<T> &rhsi)}{Complex matrix from real and imaginary parts.}
|
||||
\lafunc{template <typename T, typename S> void NRMat_convert(NRMat<T> &a, const NRMat<S> &b)}{Element type conversion: resizes \code{a} and assigns \code{(T)b(i,j)}.}
|
||||
\lafunc{template <typename T> std::ostream& operator<<(std::ostream &s, const NRMat<T> &x)}{Text output: a line with the dimensions, then one line per row.}
|
||||
\lafunc{template <typename T> std::istream& operator>>(std::istream &s, NRMat<T> &x)}{Text input in the same format (resizes \code{x}).}
|
||||
\end{lareference}
|
||||
|
||||
\section{NRMat\_from1: indexing from 1}\label{sec:mat:from1}
|
||||
\code{NRMat_from1<T>} is derived publicly from \code{NRMat<T>} and differs only in
|
||||
\code{operator()} and \code{get_ij}, which take 1-based indices ($1\le i\le n$, $1\le j\le m$);
|
||||
it is meant for transcribing algorithms written with Fortran-like indexing. Everything else is
|
||||
inherited unchanged, in particular \code{operator[]} remains 0-based (\code{a[i][j]} is element
|
||||
$(i{+}1,j{+}1)$) and member functions taking indices (\code{row}, \code{submatrix}, \ldots) use 0-based
|
||||
indices. Inherited members return \code{NRMat<T>}, which converts back implicitly.
|
||||
\begin{lareference}
|
||||
\lafunc{NRMat_from1(); NRMat_from1(const int n, const int m); NRMat_from1(const T &a, const int n, const int m); NRMat_from1(const T *a, const int n, const int m)}{As the corresponding \code{NRMat} constructors.}
|
||||
\lafunc{template<int R, int C> explicit NRMat_from1(const T (&a)[R][C])}{From a two-dimensional C array / brace initializer.}
|
||||
\lafunc{NRMat_from1(const NRMat<T> &rhs)}{Implicit conversion from the base class (shallow, shares data).}
|
||||
\lafunc{explicit NRMat_from1(const NRSMat_from1<T> &rhs)}{Full matrix from a packed symmetric one.}
|
||||
\lafunc{explicit NRMat_from1(const int n) = delete}{A single integer argument is forbidden (as for \code{NRMat} and \code{Mat3}); use \code{NRMat_from1(n,m)}.}
|
||||
\lafunc{inline T& operator()(const int i, const int j); inline const T& operator()(const int i, const int j) const; inline const T get_ij(const int i, const int j) const}{1-based element access, with the same DEBUG checks as in \code{NRMat}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Examples}\label{sec:mat:examples}
|
||||
Products, reference counting, orthonormalization and submatrices:
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
using namespace LA;
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
NRMat<double> a(4,3), b(3,5);
|
||||
a.randomize(1.); b.randomize(1.);
|
||||
|
||||
NRMat<double> c = a*b; // dgemm
|
||||
NRMat<double> d(3,3); // result must have the right shape
|
||||
d.gemm(0., a, 't', a, 'n', 1.); // d = a^T a
|
||||
NRSMat<double> s = a.transposedtimes();// the same, packed symmetric
|
||||
cout << (d - NRMat<double>(s)).norm() << endl;
|
||||
|
||||
NRMat<double> e = c; // shares data with c
|
||||
e.copyonwrite(); // needed: we write through operator()
|
||||
e(0,0) = 1.;
|
||||
const NRMat<double> &cc = c; // read-only access via const reference
|
||||
cout << cc(0,0) << " " << e(0,0) << endl;
|
||||
|
||||
NRMat<double> u(3,3); u.randomize(1.);
|
||||
u.orthonormalize(false); // orthonormal columns
|
||||
cout << (u.transpose()*u - 1.).norm() << endl; // "- 1." subtracts the unit matrix
|
||||
|
||||
NRMat<double> blk = c.submatrix(1,2,0,4); // rows 1..2, all columns
|
||||
NRVec<double> r = c.row(3);
|
||||
NRMat<double> big(0., 6, 6);
|
||||
big.storesubmatrix(2, 1, blk);
|
||||
cout << blk << r << big.trace() << endl;
|
||||
return 0;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
Complex matrices, 1-based indexing, Kronecker product and direct sum:
|
||||
\begin{lstlisting}
|
||||
NRMat_from1<int> m({{1,2,3},{4,5,6}}); // 2x3, indices from 1
|
||||
const NRMat_from1<int> &mc = m;
|
||||
cout << mc(2,3) << " " << mc[1][2] << endl; // both print 6: operator[] stays 0-based
|
||||
|
||||
NRMat<double> re(2,2), im(2,2);
|
||||
re.randomize(1.); im.randomize(1.);
|
||||
NRMat<complex<double> > z = complexify(re, im);
|
||||
NRMat<complex<double> > h(2,2);
|
||||
h.gemm(0., z, 'c', z, 'n', 1.); // h = z^dagger z
|
||||
cout << h.nonhermiticity() << endl; // 0
|
||||
NRMat<double> rp(z); // real part (explicit constructor)
|
||||
NRMat<double> ip(z, true); // imaginary part
|
||||
|
||||
NRMat<double> k = re.otimes(im); // Kronecker product, 4x4
|
||||
NRMat<double> ds = re.oplus(im); // direct sum, 4x4 block diagonal
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}\label{sec:mat:pitfalls}
|
||||
\lawarning{Scalars mean multiples of the unit matrix: \code{A = 1.} makes a unit matrix (of the current, square size), \code{A + a} and \code{A -= a} change only the diagonal. In contrast \code{NRMat<double> A(a, n, m)} fills \emph{all} elements with \code{a}. \code{a - A} does not compile; write \code{-A + a}.}
|
||||
\lawarning{\code{NRMat<T>(n,m)} does not initialize the elements; call \code{clear()} or use the fill constructor. Likewise \code{resize()} does not preserve the contents when the size changes.}
|
||||
\lawarning{\code{gemm} does not resize the target; allocate it with the correct dimensions first. The target must not share storage with an operand (e.g.\ \code{c.gemm(0.,c,'n',b,'n',1.)} is wrong); after \code{c = a} the call \code{c.gemm(...,a,...)} is fine because \code{gemm} first detaches \code{c}.}
|
||||
\lawarning{Writing through \code{operator[]}, \code{operator()} or \code{operator T*()} into a matrix obtained by copy construction or \code{=} modifies all sharing matrices unless \code{copyonwrite()} is called first. In DEBUG builds even \emph{reading} through the non-const operators of a shared matrix raises an error; use a const reference.}
|
||||
\lawarning{\code{NRMat(const NRVec<T>&, n, m, offset)} shares the vector data (without MATPTR). With a nonzero offset, if the matrix happens to be the last owner of the data, it will \code{delete[]} a pointer that is not the beginning of the allocated block; the constructor therefore prints a warning. This is by design (a view without copying, avoiding the cost of a copy); the condition of use is: keep the vector alive longer than the
|
||||
matrix, or make an independent copy (\code{|=}) if the matrix has to outlive it.}
|
||||
\lawarning{Many numerical members (\code{gemm}, \code{transpose}, \code{norm}, \ldots) exist only for \code{double} and \code{complex<double>} (Table~\ref{tab:mat:types}); with other element types the program fails to link.}
|
||||
\lawarning{\code{transposeme()} does not conjugate and works only on square matrices (or square leading blocks); use \code{transpose(true)} for $A\Hc$ or for a non-square matrix.}
|
||||
\lawarning{In the non-DEBUG build no dimension checks are done in most operations (\code{operator+=}, \code{gemm}, \code{operator=(const T&)}, \ldots); mismatches lead to silent memory corruption.}
|
||||
@@ -0,0 +1,377 @@
|
||||
\chapter{Dense linear algebra functions}\label{ch:nonclass}
|
||||
\laheader{nonclass.h, nonclass.cc, matexp.h}
|
||||
|
||||
\section{Purpose and general conventions}\label{sec:nonclass:purpose}
|
||||
This chapter describes the free (non-member) functions operating on the dense classes
|
||||
\code{NRVec}, \code{NRMat} and \code{NRSMat}: linear systems, determinants, inverses, Cholesky
|
||||
decomposition, symmetric/hermitian and general eigenvalue problems, singular value decomposition,
|
||||
matrix functions, and a few auxiliary products and conversions. Most of them are thin wrappers of
|
||||
LAPACK routines (\header{nonclass.cc}); they exist for \code{T}${}={}$\code{double} and
|
||||
\code{std::complex<double>} only. The templates in \header{matexp.h} (matrix polynomials, matrix
|
||||
exponential, $\exp(A)\,x$, BCH expansion) are generic: they work for every class that provides the few
|
||||
operations listed in section~\ref{sec:nonclass:matexp}, including sparse matrices
|
||||
(chapter~\ref{ch:sparse}) and user-defined matrix classes which only implement \code{gemv}.
|
||||
Iterative (Krylov) methods for large matrices are in chapter~\ref{ch:iterative}.
|
||||
|
||||
\paragraph{Destroyed arguments.} LAPACK works in place. All functions taking a matrix by non-const
|
||||
reference (\code{linear_solve}, \code{diagonalize}, \code{gdiagonalize}, \code{singular_decomposition},
|
||||
\code{cholesky}, \code{determinant_destroy}, \dots) overwrite it, and the generalized eigenvalue
|
||||
routines overwrite also the metric \code{b}. These functions call \code{copyonwrite()} on every object they
|
||||
modify (except where noted as a bug below), so passing a shallow copy is enough to preserve the
|
||||
original (sec.~\ref{sec:concepts:refcount}):
|
||||
\begin{lstlisting}
|
||||
NRMat<double> work = a; // shallow copy, no data copied yet
|
||||
diagonalize(work, w); // copyonwrite() inside detaches work; a is intact
|
||||
\end{lstlisting}
|
||||
Functions which take the matrix by value (\code{calcinverse}, \code{calcsvdinverse}, \code{determinant},
|
||||
\code{matrixfunction}, \dots) do this themselves. Output vectors and matrices must be allocated by the
|
||||
caller with the correct dimensions (they are checked, not resized), with the exceptions noted.
|
||||
|
||||
\paragraph{C versus Fortran order.} \LA{} matrices are row-major (sec.~\ref{sec:concepts:storage}), LAPACK
|
||||
is column-major, so a Fortran routine called on the raw data sees the transposed matrix. The wrappers
|
||||
take this into account; the visible consequence is the parameter \code{corder}, which selects whether
|
||||
eigenvectors are returned in the \emph{columns} (\code{corder=1}, the default, the usual mathematical
|
||||
convention $A V = V\Lambda$) or in the \emph{rows} (\code{corder=0}, the natural Fortran result, saves a
|
||||
transposition). For complex matrices the row form is the ``bra'' form, i.e.\ the rows are the complex
|
||||
conjugated eigenvectors; table~\ref{tab:nonclass:eivec} summarizes the conventions as verified numerically.
|
||||
|
||||
\paragraph{Active dimension.} Several routines have an argument \code{n} (sometimes \code{m}): if
|
||||
positive, only the leading $n\times n$ submatrix of the (larger) arrays is processed, otherwise the
|
||||
whole matrix. This is used by the iterative methods, which keep a large work matrix and diagonalize its
|
||||
growing leading block.
|
||||
|
||||
\paragraph{Errors.} LAPACK error codes ($\mathrm{info}\ne0$) are turned into \code{laerror}
|
||||
(sec.~\ref{sec:concepts:errors}), e.g.\ ``singular matrix'' or ``convergence problem''. Exceptions:
|
||||
\code{linear_solve_x} and \code{multiply_by_inverse} return \code{info}.
|
||||
|
||||
\paragraph{Element types and availability.} The functions are declared for \code{double} and
|
||||
\code{std::complex<double>} by the macro \code{declare_la}, but not all declared complex variants are
|
||||
defined in \header{nonclass.cc}. Calling an undefined one gives a link error
|
||||
(table~\ref{tab:nonclass:avail}).
|
||||
\begin{table}[htbp]
|
||||
\centering\small
|
||||
\begin{tabular}{>{\raggedright\arraybackslash}p{0.5\linewidth}>{\raggedright\arraybackslash}p{0.4\linewidth}}
|
||||
\toprule
|
||||
Function & Available for \\
|
||||
\midrule
|
||||
\texttt{linear\_solve(NRMat\&, NRMat*, ...)} & \texttt{double}, \texttt{complex<double>} \\
|
||||
\texttt{linear\_solve(NRMat\&, NRVec\&, ...)}, \texttt{linear\_solve(NRSMat\&, ...)} & \texttt{double}, \texttt{complex<double>} (complex \texttt{NRSMat}: hermitian) \\
|
||||
\texttt{determinant}, \texttt{determinant\_destroy} & \texttt{NRMat}, \texttt{NRSMat}, real and complex \\
|
||||
\texttt{diagonalize}, \texttt{singular\_decomposition}, \texttt{cholesky}, \texttt{diagofproduct},
|
||||
\texttt{trace2(NRMat,NRMat)}, \texttt{linear\_solve\_x}, \texttt{multiply\_by\_inverse}, \texttt{MatrixNorm}, \texttt{CondNumber} & \texttt{double}, \texttt{complex<double>} \\
|
||||
\texttt{gdiagonalize} & real matrix (real or complex eigenvector storage), complex matrix \\
|
||||
\texttt{trace2} with \texttt{NRSMat} & \texttt{double}, \texttt{complex<double>} (hermitian) \\
|
||||
\texttt{diagonalize3}, \texttt{realmatrixfunction}, \texttt{complexmatrixfunction},
|
||||
\texttt{adjustphases}, \texttt{xcopy}, \texttt{xaxpy}, \texttt{xdot} & \texttt{double} only \\
|
||||
templates of \texttt{matexp.h}, \texttt{power}, \texttt{commutator}, \texttt{twoside\_transform}, \dots & any type providing the needed operations \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Element types for which the functions of this chapter are available.}\label{tab:nonclass:avail}
|
||||
\end{table}
|
||||
|
||||
\begin{table}[htbp]
|
||||
\centering\small
|
||||
\begin{tabular}{>{\raggedright\arraybackslash}p{0.33\linewidth}>{\raggedright\arraybackslash}p{0.28\linewidth}>{\raggedright\arraybackslash}p{0.3\linewidth}}
|
||||
\toprule
|
||||
Call & \texttt{corder=1} (default) & \texttt{corder=0} \\
|
||||
\midrule
|
||||
\texttt{diagonalize(NRMat\&,...)}, real & columns of \texttt{a}: $AV=V\Lambda$ & rows of \texttt{a} \\
|
||||
\texttt{diagonalize(NRMat\&,...)}, complex & columns of \texttt{a}: $AV=V\Lambda$ & rows of \texttt{a} are $v_k\Hc$: $V\Hc A=\Lambda V\Hc$ \\
|
||||
\texttt{diagonalize(NRSMat\&,...)}, real & columns of \texttt{*v} & rows of \texttt{*v} \\
|
||||
\texttt{diagonalize(NRSMat\&,...)}, complex & columns of \texttt{*v}: $HV=V\Lambda$ & rows of \texttt{*v} are $v_k\Hc$ \\
|
||||
\texttt{diagonalize3} & columns of \texttt{*v} & rows of \texttt{*v} \\
|
||||
\texttt{gdiagonalize}, complex matrix & columns of \texttt{*vr}: $Ax=\lambda x$; columns of \texttt{*vl}: $y\Hc A=\lambda y\Hc$ (LAPACK convention) & rows of \texttt{*vr} are $\bar x$; rows of \texttt{*vl} are $\bar y$, i.e.\ $\bar y\Tr A=\lambda\bar y\Tr$ \\
|
||||
\texttt{gdiagonalize}, real matrix, complex storage & as for a complex matrix & as for a complex matrix \\
|
||||
\texttt{gdiagonalize}, real storage & columns; for a complex pair $(k,k+1)$ the eigenvector of $w_k$ is column $k$ + i column $k+1$ (as \texttt{dgeev}) & rows, analogously \\
|
||||
\texttt{singular\_decomposition} & \multicolumn{2}{p{0.58\linewidth}}{\raggedright $A=U\,\mathrm{diag}(s)\,V\Hc$; \texttt{*u}${}=U$ (columns = left singular vectors), \texttt{*v}${}=V\Hc$ (rows), or $V$ if \texttt{vnotdagger}} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Storage of eigenvectors and singular vectors (verified numerically). $x$ denotes right, $y$ left eigenvectors, bar complex conjugation.}\label{tab:nonclass:eivec}
|
||||
\end{table}
|
||||
|
||||
\section{Linear systems, inverse, determinant}\label{sec:nonclass:linsolve}
|
||||
The basic solver uses the LU decomposition with partial pivoting (\code{dgesv}/\code{zgesv}) for
|
||||
\code{NRMat} and the Bunch--Kaufman factorization (\code{dspsv}) for packed symmetric \code{NRSMat}.
|
||||
\textbf{The right-hand sides are the rows of \code{B}}: \code{B} has dimension $n_{\mathrm{rhs}}\times n$ and
|
||||
each row $b$ is overwritten by the solution of $A x=b$ (i.e.\ $A X\Tr=B\Tr$). \code{A} is overwritten
|
||||
by its factorization. With \code{B==NULL} only the factorization (and determinant) is computed.
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{void linear_solve(NRMat<T> &a, NRMat<T> *b, T *det=0, int n=0)}{Solve $A x=b$ for all rows $b$ of \code{*b}
|
||||
(\code{b->ncols()} must be $n$). If \code{det} is non-NULL, the determinant of $A$ is returned there (0 for a singular matrix,
|
||||
then no error is raised if \code{b==NULL}); a singular matrix with \code{b!=NULL} raises \code{laerror}.
|
||||
\code{n>0} restricts the problem to the leading $n\times n$ block of \code{a} and the first $n$ columns of \code{b}. Prints a warning when \code{b} is \code{NULL} (some LAPACK libraries skip the factorization for
|
||||
$n_{\mathrm{rhs}}=0$).}
|
||||
\lafunc{void linear_solve(NRMat<T> &a, NRVec<T> &b, T *det=0, int n=0)}{The same with a single right-hand side \code{b}.}
|
||||
\lafunc{void linear_solve(NRSMat<T> &a, NRMat<T> *b, T *det=0, int n=0); void linear_solve(NRSMat<T> &a, NRVec<T> &b, T *det=0, int n=0)}{Symmetric (real) or hermitian (complex), possibly indefinite $A$ in packed storage, \code{dspsv}/\code{zhpsv}
|
||||
(Bunch--Kaufman factorization; the determinant is computed from its $1\times1$ and $2\times2$ blocks and is real in the hermitian case).
|
||||
\code{n} selects a leading block, which is contiguous in the packed storage.}
|
||||
\lafunc{int linear_solve_x(NRMat<T> &A, T *B, const int rhsCount, const int eqCount, const bool eq, const bool saveA, double *rcond)}{Expert solver
|
||||
(\code{dgesvx}/\code{zgesvx}): solves $A x=b$ for \code{rhsCount} right-hand sides stored consecutively (each of length \code{eqCount}) in the raw
|
||||
array \code{B}, using the leading \code{eqCount}${}\times{}$\code{eqCount} block of \code{A}. \code{eq}: equilibrate $A$ (for badly conditioned matrices);
|
||||
then \code{A} is overwritten unless \code{saveA}. The reciprocal condition number is stored in \code{*rcond} if non-NULL. Returns LAPACK \code{info}
|
||||
(0 success, $>0$ singular or ill-conditioned). The solution replaces \code{B}.}
|
||||
\lafunc{int multiply_by_inverse(NRMat<T> &A, NRMat<T> &B, bool useEq=false, double *rcond=NULL)}{$B\leftarrow A B^{-1}$ for square $A$, $B$ via
|
||||
\code{dgesvx}/\code{zgesvx}; \code{useEq} as \code{eq} above. \code{A} is not modified (with \code{useEq} a temporary copy is
|
||||
scaled). Returns \code{info}.}
|
||||
\lafunc{const NRMat<T> calcinverse(NRMat<T> a, T *det=NULL)}{$A^{-1}$ by \code{linear_solve} with unit right-hand sides; optionally the determinant.
|
||||
\code{NRMat::inverse()} (chapter~\ref{ch:mat}) calls it.}
|
||||
\lafunc{const NRMat<T> calcsvdinverse(NRMat<T> a, double thr=0, bool do_absadjoint=false)}{Inverse via SVD, $V\,\mathrm{diag}(1/s_i)\,U\Hc$,
|
||||
singular values $s_i<$\code{thr} are treated as zero (pseudoinverse). With \code{do_absadjoint} returns
|
||||
$V\,\mathrm{diag}(\prod_{j\ne i}s_j)\,U\Hc$, the adjugate matrix up to the phase $\det U\det V$. Square matrices only.}
|
||||
\lafunc{NRVec<T> svd_solve(NRMat<T> &a, const NRVec<T> &b, double thres=1e-12, int *nullity=NULL); NRMat<T> svd_solve(NRMat<T> &a, const NRMat<T> &b, double thres=1e-12, int *nullity=NULL)}{Least-squares
|
||||
solution $x=V\,\mathrm{diag}(1/s_i)\,U\Hc b$ discarding singular values $\le$\code{thres} (no contamination by the null space);
|
||||
the number of discarded ones is stored in \code{*nullity}. Here (unlike \code{linear_solve}) the right-hand sides are the
|
||||
\emph{columns} of the matrix \code{b} ($m\times n_{\mathrm{rhs}}$); \code{a} ($m\times n$, $m\ge n$) is destroyed.}
|
||||
\lafunc{const LA_traits<MAT>::elementtype determinant(MAT a)}{Determinant by LU (\code{NRMat}) or Bunch--Kaufman (\code{NRSMat}) factorization of a copy.}
|
||||
\lafunc{const LA_traits<MAT>::elementtype determinant_destroy(MAT &a)}{The same, overwriting \code{a}. Unless \code{HAS_MKL} is defined, a dummy right-hand side
|
||||
is passed to \code{linear_solve} (work-around for OpenBLAS, which skips the factorization without right-hand sides).}
|
||||
\lafunc{void cholesky(NRMat<double> &a, bool upper=1); void cholesky(NRMat<std::complex<double> > &a, bool upper=1)}{Cholesky factorization of a
|
||||
positive definite matrix in place (\code{dpotrf}/\code{zpotrf}). \code{upper=1}: \code{a} becomes the upper triangular $U$ with
|
||||
$A=U\Hc U$; \code{upper=0}: lower triangular $L$ with $A=L L\Hc$. The other triangle is zeroed. Raises \code{laerror} if $A$ is not positive definite.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Eigenvalue problems}\label{sec:nonclass:eigen}
|
||||
|
||||
\subsection{Symmetric and hermitian matrices}
|
||||
Eigenvalues are always returned in \textbf{ascending order} (LAPACK). Only the \textbf{lower triangle}
|
||||
(row index ${}\ge{}$ column index) of an \code{NRMat} is referenced (verified); \code{NRSMat} stores only that
|
||||
triangle and is interpreted as hermitian (sec.~\ref{sec:smat:hermitian}).
|
||||
\begin{lareference}
|
||||
\lafunc{void diagonalize(NRMat<T> &a, NRVec<double> &w, const bool eivec=1, const bool corder=1, int n=0, NRMat<T> *b=NULL, const int itype=1)}{Eigenvalues
|
||||
\code{w} (size must equal \code{a.nrows()}) and, if \code{eivec}, eigenvectors stored in \code{a} (columns for \code{corder=1}, see table~\ref{tab:nonclass:eivec})
|
||||
of a real symmetric or complex hermitian matrix (\code{dsyev}/\code{zheev}). If \code{b} is given, the generalized problem with positive definite $B$
|
||||
is solved (\code{dsygv}/\code{zhegv}): \code{itype=1}: $A x=\lambda B x$, \code{itype=2}: $A B x=\lambda x$, \code{itype=3}: $B A x=\lambda x$;
|
||||
eigenvectors are $B$-normalized ($x\Hc B x=1$ for \code{itype} 1, 2), \code{b} is overwritten by its Cholesky factor.}
|
||||
\lafunc{void diagonalize(NRSMat<T> &a, NRVec<double> &w, NRMat<T> *v, const bool corder=1, int n=0, NRSMat<T> *b=NULL, const int itype=1)}{The same for packed
|
||||
storage (\code{dspev}/\code{zhpev}, \code{dspgv}/\code{zhpgv}); eigenvectors go to \code{*v} (square, at least $n\times n$) or are not computed if \code{v==NULL}.
|
||||
\code{a} (and \code{b}) are destroyed.}
|
||||
\lafunc{void diagonalize3(NRVec<double> &d, NRVec<double> &d1, NRMat<double> *v, const bool corder=1, int n0=0)}{Real symmetric tridiagonal matrix
|
||||
(\code{dsteqr}) with diagonal \code{d} and sub-diagonal \code{d1} (size ${}\ge n-1$). On return \code{d} holds the eigenvalues (ascending), \code{d1} is destroyed,
|
||||
\code{*v} (exactly $n\times n$) the eigenvectors. \code{n0>0} uses only the first \code{n0} elements. Used by \code{lanczos} (chapter~\ref{ch:iterative}).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{General (non-symmetric) matrices}
|
||||
\code{gdiagonalize} wraps \code{dgeev}/\code{zgeev} and, if \code{b} and \code{beta} are given, the generalized
|
||||
problem $A x=\lambda B x$ (\code{dggev}/\code{zggev}), where the eigenvalues are $\lambda_i=w_i/\beta_i$ (\code{beta}
|
||||
may contain zeros for infinite eigenvalues). Right (\code{vr}) and left (\code{vl}) eigenvectors are computed only if the
|
||||
pointers are non-NULL; storage according to table~\ref{tab:nonclass:eivec}. Without sorting, the eigenvalues come in
|
||||
LAPACK order (unsorted, complex conjugate pairs adjacent with positive imaginary part first).
|
||||
\begin{lareference}
|
||||
\lafunc{void gdiagonalize(NRMat<double> &a, NRVec<double> &wr, NRVec<double> &wi, NRMat<double> *vl=NULL, NRMat<double> *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat<double> *b=NULL, NRVec<double> *beta=NULL)}{Real
|
||||
matrix, eigenvalues $w_r+\mathrm{i}\,w_i$, eigenvectors in the real storage of \code{dgeev}: for a real eigenvalue the vector is real; for a complex pair
|
||||
$(j,j{+}1)$ the vectors $j$ and $j{+}1$ hold the real and imaginary part. Verified: the right eigenvector belonging to
|
||||
$w_{r,j}+\mathrm{i}w_{i,j}$ ($w_{i,j}>0$) is $\mathrm{Re}-\mathrm{i}\,\mathrm{Im}$, and $\mathrm{Re}+\mathrm{i}\,\mathrm{Im}$ of \code{vl} satisfies $y\Tr A=\lambda y\Tr$ --
|
||||
note that this sign is opposite to the \code{dgeev} documentation.
|
||||
\code{sorttype}: 0 no sorting; 1 ascending real part (for equal real parts descending imaginary part; with \code{beta}, by $w/\beta$, infinite eigenvalues last);
|
||||
2 real eigenvalues first, then complex ones, each group ordered as for 1. Eigenvectors are permuted accordingly.
|
||||
\code{biorthonormalize} (needs \code{vl} and \code{vr}, not with \code{b}): 1 scales the left, 2 the right eigenvectors so that $y_k\Tr x_k=1$;
|
||||
complex pairs are rotated to biorthonormality. \code{a} is destroyed (and used as scratch space).}
|
||||
\lafunc{void gdiagonalize(NRMat<T> &a, NRVec<std::complex<double> > &w, NRMat<std::complex<double> > *vl=NULL, NRMat<std::complex<double> > *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat<T> *b=NULL, NRVec<T> *beta=NULL)}{Template
|
||||
specialized for \code{T=double} (calls the previous routine and converts to complex eigenvalues and eigenvectors) and \code{T=complex<double>} (\code{zgeev}/\code{zggev}).
|
||||
For the complex matrix, \code{sorttype>0} raises \code{laerror} (not implemented) and \code{biorthonormalize} scales so that $\bar y_k\Tr x_k=1$ in the row storage.}
|
||||
\lafunc{void gdiagonalize(NRMat<std::complex<double> > &a, NRVec<double> &wr, NRVec<double> &wi, NRMat<std::complex<double> > *vl=NULL, NRMat<std::complex<double> > *vr=NULL, const bool corder=1, int n=0, const int sorttype=0, const int biorthonormalize=0, NRMat<std::complex<double> > *b=NULL, NRVec<std::complex<double> > *beta=NULL)}{Complex
|
||||
matrix with real and imaginary parts of the eigenvalues in separate vectors; exists to make \code{davidson} compile for complex \code{T}.}
|
||||
\end{lareference}
|
||||
\section{Singular value decomposition}\label{sec:nonclass:svd}
|
||||
\begin{lareference}
|
||||
\lafunc{void singular_decomposition(NRMat<T> &a, NRMat<T> *u, NRVec<double> &s, NRMat<T> *v, const bool vnotdagger=0, int m=0, int n=0)}{SVD
|
||||
$A=U\,\mathrm{diag}(s)\,V\Hc$ of the $m\times n$ matrix \code{a} (\code{dgesvd}/\code{zgesvd}; \code{m}, \code{n} select a leading block, default whole matrix).
|
||||
\code{s} (size ${}\ge\min(m,n)$) receives the singular values in descending order. \code{u}: \code{NULL} (not computed), $m\times m$ (all columns) or
|
||||
$m\times\min(m,n)$ (economy size, chosen by \code{u} being non-square). \code{v}: \code{NULL}, $n\times n$ or $\min(m,n)\times n$, receives $V\Hc$
|
||||
(right singular vectors as conjugated rows), or $V$ if \code{vnotdagger} (square \code{v} only). \code{a} is destroyed.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Matrix functions}\label{sec:nonclass:matfunc}
|
||||
Functions of matrices computed via the eigendecomposition $f(A)=V f(\Lambda) V^{-1}$. For general matrices
|
||||
the eigenvalue problem is solved in complex arithmetic and the result converted back to the element type of the
|
||||
argument; for a real argument the imaginary part is \emph{silently dropped} (its size is not checked). The matrix must be
|
||||
diagonalizable; nothing is done about defective or ill-conditioned eigenvector matrices.
|
||||
\begin{lareference}
|
||||
\lafunc{NRMat<T> matrixfunction(NRSMat<T> a, double (*f)(double))}{$f(A)$ for a real symmetric or complex hermitian packed matrix, real function \code{f}
|
||||
of the (real) eigenvalues.}
|
||||
\lafunc{NRMat<T> matrixfunction(NRMat<T> a, std::complex<double> (*f)(const std::complex<double> &))}{$f(A)$ for a general real or complex
|
||||
matrix by \code{gdiagonalize}, $f(A)=\sum_k f(\lambda_k)\,x_k y_k\Tr/(y_k\Tr x_k)$.}
|
||||
\lafunc{NRMat<double> realmatrixfunction(NRMat<double> a, double (*f)(double))}{$f(A)$ for a real symmetric matrix in full storage (only the lower triangle is used).}
|
||||
\lafunc{NRMat<std::complex<double> > complexmatrixfunction(NRMat<double> a, double (*fre)(double), double (*fim)(double))}{Complex function
|
||||
$f=f_{\mathrm{re}}+\mathrm{i} f_{\mathrm{im}}$ of a real symmetric matrix, e.g.\ $\exp(\mathrm{i}A)$ with \code{cos}, \code{sin}.}
|
||||
\lafunc{NRMat<double> sqrt(const NRSMat<double> &a); NRMat<double> sqrtinv(const NRSMat<double> &a)}{$A^{1/2}$, $A^{-1/2}$ of a symmetric matrix (e.g.\ symmetric orthogonalization).}
|
||||
\lafunc{NRMat<double> realsqrt(const NRMat<double> &a); NRMat<double> realsqrtinv(const NRMat<double> &a)}{The same for a symmetric matrix in full storage.}
|
||||
\lafunc{NRMat<double> log(const NRSMat<double> &a)}{Logarithm of a symmetric positive definite matrix.}
|
||||
\lafunc{NRMat<double> log(const NRMat<double> &a); NRMat<std::complex<double> > log(const NRMat<std::complex<double> > &a)}{Principal logarithm of a
|
||||
general matrix via complex eigendecomposition (for a real matrix the imaginary part of the result is dropped).}
|
||||
\lafunc{NRMat<double> exp0(const NRMat<double> &a); NRMat<std::complex<double> > exp0(const NRMat<std::complex<double> > &a)}{$\exp(A)$ via
|
||||
eigendecomposition, an alternative to the Taylor-series \code{exp()} of section~\ref{sec:nonclass:matexp}.}
|
||||
\lafunc{NRMat<double> copytest(const NRMat<double> &a); NRMat<std::complex<double> > copytest(const NRMat<std::complex<double> > &a)}{\code{matrixfunction} with
|
||||
the identity function, i.e.\ reconstructs $A$ from its eigendecomposition (a check of \code{gdiagonalize} available to users).}
|
||||
\lafunc{double sqrtinv(const double); std::complex<double> sqrtinv(const std::complex<double> &)}{Scalar $1/\sqrt{x}$ (used as function argument above).}
|
||||
\lafunc{void adjustphases(NRMat<double> &v)}{Changes signs of rows of an orthogonal matrix (eigenvectors in rows) so that the diagonal elements are positive
|
||||
where possible and $\det v=+1$, to obtain a real logarithm of a rotation. The source comments say ``not rigorous yet''.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Matrix polynomials and exponentials (matexp.h)}\label{sec:nonclass:matexp}
|
||||
\laheader{matexp.h}
|
||||
These templates use only generic operations and therefore work for \code{NRMat}, \code{NRSMat},
|
||||
\code{SparseMat}, \code{SparseSMat} and user classes. \code{exp} and the polynomials need for the matrix type
|
||||
\code{T}: copy and assignment, \code{T*T}, \code{T*scalar}, \code{T+scalar} and \code{T+=T} where adding a scalar
|
||||
means adding it to the \emph{diagonal} (this is how \LA{} matrix classes define it), \code{norm()}, and the traits
|
||||
\code{LA_traits<T>::elementtype}, \code{normtype}, \code{deallocate} and \code{axpy}
|
||||
(sec.~\ref{sec:concepts:traits}). The ``\code{times}'' variants never form the matrix: they need only
|
||||
\code{nrows()}, \code{ncols()}, \code{norm()} and
|
||||
\code{gemv(beta, NRVec &r, char trans, alpha, const NRVec &x)} computing $r\leftarrow\beta r+\alpha\,\mathrm{op}(A)x$,
|
||||
so they can be used with implicitly defined (``direct'') matrices as in chapter~\ref{ch:iterative}. The vector type \code{V}
|
||||
needs \code{size()}, \code{operator*=}, \code{axpy} and \code{resize()}.
|
||||
|
||||
The exponential uses scaling and squaring: the power $p$ is chosen so that $\|A\|/2^p\lesssim 0.75$
|
||||
(\code{mynextpow2}), the Taylor series of the scaled argument is truncated when the next term would be below $10^{-14}$
|
||||
relative to the norm, and the result is squared $p$ times. The scaling is put into the Taylor coefficients,
|
||||
so the argument itself is not copied. \code{maxpower} (if ${}\ge0$) limits $p$, \code{maxtaylor} (if ${}\ge0$) limits
|
||||
the order of the Taylor polynomial, which is useful and exact for nilpotent matrices (e.g.\ coupled-cluster excitation
|
||||
operators).
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{const T polynom0(const T &x, const NRVec<R> &c)}{$\sum_{i=0}^{N} c_i x^i$ by the Horner scheme ($N$=\code{c.size()-1}).}
|
||||
\lafunc{const T polynom(const T &x, const NRVec<R> &c)}{The same with fewer matrix multiplications at the cost of storing powers $x,\dots,x^m$
|
||||
(block summation of Paterson and Stockmeyer, $\sum_i s_i\,(x^m)^i$ with $s_i=\sum_{j<m}c_{im+j}x^j$): $m$ is chosen to minimize the number
|
||||
$m-2+2\lfloor N/m\rfloor-[m\,|\,N]$ of multiplications, e.g.\ 5, 8, 13 instead of 8, 16, 32 for $N=8,16,32$; for $N\le4$ calls \code{polynom0}.}
|
||||
\lafunc{const T exp(const T &x, bool horner=false, int maxpower= -1, int maxtaylor= -1)}{$\exp(x)$ by scaling and squaring; \code{horner} selects \code{polynom0}
|
||||
or \code{polynom} (default) for the Taylor polynomial. Both give the same accuracy; \code{polynom} is 1.3--1.5 times faster for dense
|
||||
matrices (tests with $n=100$--$800$), while for sparse matrices \code{horner=true} is faster, since the stored powers fill in.}
|
||||
\lafunc{void sincos(T &s, T &c, const T &x, bool horner=false, int maxpower= -1, int maxtaylor= -1)}{$s=\sin x$, $c=\cos x$ simultaneously in real arithmetic
|
||||
(doubling formulas instead of squaring); \code{T} also needs \code{operator*=(double)}.}
|
||||
\lafunc{NRMat<std::complex<T> > expi(const NRMat<T> &x)}{$\exp(\mathrm{i}x)=\cos x+\mathrm{i}\sin x$ of a real matrix via \code{sincos}.}
|
||||
\lafunc{const T ipow(const T &x, int i)}{$x^i$, $i\ge0$, by binary powering; $x^0$ is the unit matrix of the size of $x$.}
|
||||
\lafunc{const V exptimes(const M &mat, V rhs, bool transpose=false, const LA_traits<V>::elementtype scale=1., int maxpower= -1, int maxtaylor= -1, bool mat_is_0=false)}{$\exp(s\,\mathrm{op}(A))\,\mathtt{rhs}$
|
||||
with $\mathrm{op}(A)=A$ or $A\Tr$ (\code{transpose}), using only \code{mat.gemv}. The scaled Taylor polynomial is applied $2^p$ times instead of squaring.
|
||||
\code{scale} $s$ may be complex for a real matrix and complex vectors (e.g.\ time evolution $\exp(-\mathrm{i}Ht)x$). \code{mat_is_0} short-cuts to a (deep) copy of \code{rhs}.
|
||||
Raises \code{laerror} for an unrealistically large $p$ (use \code{maxpower}).}
|
||||
\lafunc{const V exptimesreal(const M &mat, V rhs, bool transpose=false, const LA_traits<V>::normtype scale=1., int maxpower= -1, int maxtaylor= -1, bool mat_is_0=false)}{The
|
||||
same with a real \code{scale} for a complex vector (overload resolution helper).}
|
||||
\lafunc{void exptimesdestructive(const M &mat, V &result, V &rhs, bool transpose, const MEL scale, int maxpower= -1, int maxtaylor= -1, bool mat_is_0=false)}{Worker of
|
||||
\code{exptimes}: \code{result}${}=\exp(s\,\mathrm{op}(A))\,\mathtt{rhs}$, \code{rhs} is used as scratch and destroyed.}
|
||||
\lafunc{void sincostimes(const M &mat, V &si, V &co, const V &rhs, bool transpose=false, const LA_traits<V>::normtype scale=1., int maxpower= -1, int maxtaylor= -1, bool mat_is_0=false)}{\code{si}${}=\sin(sA)\,\mathtt{rhs}$,
|
||||
\code{co}${}=\cos(sA)\,\mathtt{rhs}$ with \code{gemv} only. The doubling is done by recursion with $3^p$ evaluations; the source comment recommends complex \code{exptimes} instead.}
|
||||
\lafunc{const T ncommutator(const T &x, const T &y, int nest=1, const bool right=1); const T nanticommutator(const T &x, const T &y, int nest=1, const bool right=1)}{Nested
|
||||
(anti)commutator: \code{right}: $[\dots[[x,y],y]\dots,y]$, otherwise $[x,[x,\dots[x,y]]]$, \code{nest} levels.}
|
||||
\lafunc{const T BCHexpansion(const T &h, const T &t, const int nmax, const bool verbose=0, const bool right=1)}{Truncated Baker--Campbell--Hausdorff series
|
||||
$\sum_{n=0}^{n_{\max}}\frac1{n!}[\dots[h,t],\dots,t]_n=e^{-t}h\,e^{t}$ (\code{right}) or $e^{t}h\,e^{-t}$ (\code{right=0}); \code{verbose} prints the norm of each order to \code{std::cerr}.}
|
||||
\lafunc{const V BCHtimes(const M1 &H, const char transH, const M2 &T, const char transT, const V &x, const int nmax, const bool right=1)}{$e^{-T}He^{T}x$ (\code{right})
|
||||
or $e^{T}He^{-T}x$ truncated at order \code{nmax}, using only \code{gemv} of $H$ and $T$ (\code{transH}, \code{transT} select $\mathrm{op}$): $H T^j x$ is precomputed for $j\le n_{\max}$
|
||||
and the nested commutators are built recursively in place, so $n_{\max}+1$ vectors are stored.}
|
||||
\end{lareference}
|
||||
The helpers \code{exp_aux}, \code{sincos_aux}, \code{sincostimes_aux}, \code{sincostimes_simple}, \code{mynextpow2} and the coefficient table
|
||||
\code{exptaylor} are internal.
|
||||
|
||||
\section{Products, traces, transformations}\label{sec:nonclass:products}
|
||||
\begin{lareference}
|
||||
\lafunc{const NRVec<T> diagofproduct(const NRMat<T> &a, const NRMat<T> &b, bool trb=0, bool conjb=0)}{Diagonal of $AB$ without forming the product;
|
||||
\code{trb}: of $AB\Tr$ (or $AB\Hc$ with \code{conjb}, complex only).}
|
||||
\lafunc{T trace2(const NRMat<T> &a, const NRMat<T> &b, bool trb=0)}{$\mathrm{Tr}(AB)$, or $\mathrm{Tr}(AB\Tr)=\sum_{ij}a_{ij}b_{ij}$ with \code{trb}
|
||||
(complex: $\mathrm{Tr}(AB\Hc)$).}
|
||||
\lafunc{T trace2(const NRSMat<T> &a, const NRSMat<T> &b, const bool diagscaled=0)}{$\mathrm{Tr}(AB)=\sum_{ij}a_{ij}b_{ij}$ of symmetric matrices,
|
||||
$\sum_{ij}a_{ij}\bar b_{ij}$ (real) of hermitian ones.
|
||||
\code{diagscaled}: returns $2\sum_{i\ge j}a_{ij}b_{ij}$, i.e.\ the correct trace if the diagonal of one argument has been pre-multiplied by $1/2$.}
|
||||
\lafunc{T trace2(const NRSMat<T> &a, const NRMat<T> &b, const bool diagscaled=0); T trace2(const NRMat<T> &a, const NRSMat<T> &b, const bool diagscaled=0)}{$\mathrm{Tr}(AB)$
|
||||
of a symmetric (hermitian for complex) and a general matrix; \code{diagscaled} counts the diagonal twice.}
|
||||
\lafunc{const NRSMat<T> twoside_transform(const NRSMat<T> &S, const NRMat<T> &C, bool transp=0); const NRMat<T> twoside_transform(const NRMat<T> &S, const NRMat<T> &C, bool transp=0)}{$C\Hc S C$,
|
||||
or $C S C\Hc$ with \code{transp} (similarity/congruence transformation).}
|
||||
\lafunc{const NRMat<T> diagonalmatrix(const NRVec<T> &x)}{Square matrix with diagonal \code{x}.}
|
||||
\lafunc{const NRMat<T> commutator(const NRMat<T> &x, const NRMat<T> &y, const bool trx=0, const bool tryy=0); const NRMat<T> anticommutator(...)}{$\mathrm{op}(x)\mathrm{op}(y)\mp\mathrm{op}(y)\mathrm{op}(x)$
|
||||
with two \code{gemm} calls; \code{trx}, \code{tryy} transpose the factors (no conjugation).}
|
||||
\lafunc{T positive_power(const T &x, const int n)}{$x^n$, $n\ge1$, by binary powering (any type with \code{operator*}).}
|
||||
\lafunc{T power(const T &x, const int n)}{$x^n$ for any integer $n$; needs \code{identity()} for $n=0$ and \code{inverse()} for $n<0$ (e.g.\ \code{NRMat}, permutations).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Norms and condition number}\label{sec:nonclass:norms}
|
||||
\begin{lareference}
|
||||
\lafunc{typename LA_traits<MAT>::normtype MatrixNorm(const MAT &A, const char norm)}{Matrix norm by \code{dlange}/\code{zlange}, implemented for
|
||||
\code{NRMat<double>} and \code{NRMat<std::complex<double> >} (explicit specializations of the template, which can be added for other types).
|
||||
\code{'1'} or \code{'O'}: 1-norm (maximum column sum); \code{'I'}: $\infty$-norm (maximum row sum); \code{'F'} or \code{'E'}: Frobenius norm;
|
||||
\code{'M'}: largest absolute value of an element (lower case accepted). Works for non-square matrices.}
|
||||
\lafunc{typename LA_traits<MAT>::normtype CondNumber(const MAT &A, const char norm)}{Condition number $\|A\|\,\|A^{-1}\|$ of a square matrix in the 1-norm
|
||||
(\code{'1'}, \code{'O'}) or $\infty$-norm (\code{'I'}), from the LU factorization of a copy of \code{A} (\code{dgetrf}/\code{zgetrf}) and \code{dgecon}/\code{zgecon};
|
||||
infinity for a singular matrix. It is LAPACK's estimate, a lower bound that is usually exact. Same types as \code{MatrixNorm}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Real and imaginary parts}\label{sec:nonclass:complex}
|
||||
\begin{lareference}
|
||||
\lafunc{const LA_traits<T>::realtype realpart(const T &a); const LA_traits<T>::realtype imagpart(const T &a)}{Real or imaginary part as a real object;
|
||||
specialized for \code{NRVec}, \code{NRMat}, \code{NRSMat} and \code{SparseMat} over \code{complex<double>}.}
|
||||
\lafunc{const LA_traits<T>::complextype realmatrix(const T &a); const LA_traits<T>::complextype imagmatrix(const T &a)}{Complex object $A$ resp.\ $\mathrm{i}A$ from a real one;
|
||||
for \code{NRMat<double>} and \code{SparseSMat<double>}.}
|
||||
\lafunc{const LA_traits<T>::complextype complexmatrix(const T &re, const T &im)}{Complex object $\mathrm{re}+\mathrm{i}\,\mathrm{im}$; for \code{NRMat<double>} and \code{SparseSMat<double>}.}
|
||||
\end{lareference}
|
||||
For the same purpose the classes provide \code{complexify()} (chapters~\ref{ch:vec}, \ref{ch:mat}).
|
||||
The real and imaginary part functions are CPU only (sec.~\ref{sec:concepts:gpu}).
|
||||
|
||||
\section{Miscellaneous}\label{sec:nonclass:misc}
|
||||
\begin{lareference}
|
||||
\lafunc{const NRMat<LA_traits<MAT>::elementtype> submatrix(const MAT a, const int nrows, const INDEX rows, const int ncols, const INDEX cols, int indexshift=0, bool ignoresign=false, bool equalsigns=false)}{General
|
||||
(non-contiguous) submatrix $r_{ij}=a(\mathtt{rows}[i]+s,\ \mathtt{cols}[j]+s)$, $s$=\code{indexshift}; \code{INDEX} is anything indexable (\code{NRVec<int>}, \code{int*}).
|
||||
\code{ignoresign}: use $|\mathtt{rows}[i]|$, $|\mathtt{cols}[j]|$; \code{equalsigns}: $r_{ij}=0$ if the two indices have opposite signs.
|
||||
Application from the source: overlap of Slater determinants with signed spin-orbital indices (\code{indexshift=-1}, both flags true). \code{MAT} needs only
|
||||
\code{operator()(int,int)}.}
|
||||
\lafunc{NRMat<T> reconstructmatrix<M,T>(const M &implicitmat)}{Explicit matrix of an implicit one (applying \code{gemv} to unit vectors), for debugging;
|
||||
both template arguments must be given. Equivalent to \code{explicit_matrix<T>} of \header{davidson.h} (chapter~\ref{ch:iterative}).}
|
||||
\lafunc{void xcopy(int n, const T *x, int incx, T *y, int incy); void xaxpy(int n, const T &a, const T *x, int incx, T *y, int incy); T xdot(int n, const T *x, int incx, const T *y, int incy)}{Type-generic
|
||||
names of the CBLAS routines (to ease switching between \code{float}/\code{double}/complex in user code); at present specialized only for \code{double}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Examples}\label{sec:nonclass:examples}
|
||||
Symmetric eigenproblem, eigenvectors in columns, and a linear system with two right-hand sides (rows of \code{b}):
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
using namespace LA;
|
||||
|
||||
int main()
|
||||
{
|
||||
const int n=4;
|
||||
NRSMat<double> s(n);
|
||||
for(int i=0; i<n; ++i) for(int j=0; j<=i; ++j) s(i,j) = (i==j? i+2. : 0.5/(1+i+j));
|
||||
|
||||
NRSMat<double> work = s; //shallow copy, diagonalize detaches it
|
||||
NRVec<double> w(n);
|
||||
NRMat<double> v(n,n);
|
||||
diagonalize(work, w, &v); //w ascending, columns of v are eigenvectors
|
||||
NRMat<double> sv = NRMat<double>(s)*v;
|
||||
for(int k=0; k<n; ++k) std::cout << (sv.column(k) - v.column(k)*w[k]).norm() << std::endl;
|
||||
|
||||
NRMat<double> a(s), b(2,n); //solve a x = b_i for the 2 rows of b
|
||||
for(int i=0; i<2; ++i) for(int j=0; j<n; ++j) b(i,j) = i+j;
|
||||
double det;
|
||||
linear_solve(a, &b, &det); //a now holds the LU factors, rows of b the solutions
|
||||
NRMat<double> s2 = sqrtinv(s); //S^{-1/2}
|
||||
std::cout << det << std::endl << s2;
|
||||
}
|
||||
\end{lstlisting}
|
||||
Exponential of a sparse matrix and its action on a vector (\code{t.cc} contains similar tests):
|
||||
\begin{lstlisting}
|
||||
SparseMat<double> h(n,n); //... filled with h.add(i,j,x)
|
||||
NRMat<double> e = exp(NRMat<double>(h)); //dense exponential
|
||||
SparseMat<double> es = exp(h); //sparse arithmetics
|
||||
NRVec<double> y = exptimes(h, x); //exp(h) x, only gemv used
|
||||
NRVec<std::complex<double> > xc(complexify(x));
|
||||
NRVec<std::complex<double> > psi = exptimes(h, xc, false, std::complex<double>(0,-0.1)); //exp(-0.1 i h) x
|
||||
\end{lstlisting}
|
||||
General matrix: left and right eigenvectors in complex storage, rows for \code{corder=0}:
|
||||
\begin{lstlisting}
|
||||
NRMat<double> g(n,n); //... filled
|
||||
NRMat<double> scratch = g;
|
||||
NRVec<std::complex<double> > lambda(n);
|
||||
NRMat<std::complex<double> > vl(n,n), vr(n,n);
|
||||
gdiagonalize(scratch, lambda, &vl, &vr, false, 0, 0, 0, (NRMat<double>*)NULL, (NRVec<double>*)NULL);
|
||||
//rows: vl[k]^T g = lambda[k] vl[k]^T, g conj(vr[k]) = lambda[k] conj(vr[k])
|
||||
\end{lstlisting}
|
||||
In the last call the null pointers must be cast, otherwise the template argument of \code{b} cannot be deduced.
|
||||
|
||||
\section{Pitfalls}\label{sec:nonclass:pitfalls}
|
||||
\begin{itemize}
|
||||
\item Right-hand sides of \code{linear_solve} are the \emph{rows} of \code{B} ($n_{\mathrm{rhs}}\times n$), those of \code{svd_solve} the \emph{columns}.
|
||||
\item Eigenvectors are in columns only for \code{corder=1}; in row storage (\code{corder=0}) complex eigenvectors are conjugated
|
||||
(table~\ref{tab:nonclass:eivec}).
|
||||
\item \code{diagonalize(NRMat&, ...)} uses only the lower triangle; a non-symmetric \code{NRMat} is silently treated as symmetric.
|
||||
\item Unsorted output of \code{gdiagonalize} unless \code{sorttype} is set (real matrices only).
|
||||
\item \code{matrixfunction}, \code{log} etc.\ of a real general matrix drop the imaginary part of the result without any check.
|
||||
\item The arguments passed by non-const reference are destroyed; keep a shallow copy if the original is needed (it is sufficient because of internal \code{copyonwrite()}).
|
||||
\item Several declared complex variants are missing (link errors), see section~\ref{sec:nonclass:purpose}.
|
||||
\end{itemize}
|
||||
+1005
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,425 @@
|
||||
\chapter{Permutations and the symmetric group}\label{ch:permutation}
|
||||
|
||||
\laheader{permutation.h, permutation.cc, qsort.h}
|
||||
|
||||
This chapter describes the classes for permutations (in the one-line and in the cycle notation),
|
||||
for linear combinations of permutations (elements of the group algebra of $S_n$), for partitions and
|
||||
Young tableaux, the character table of $S_n$, and the sorting templates which return the parity of the
|
||||
sorting permutation. All class templates are explicitly instantiated in \header{permutation.cc} for
|
||||
\code{T = int} and \code{T = unsigned int}; \code{WeightPermutation<T,R>} and \code{PermutationAlgebra<T,R>}
|
||||
for \code{T = int} with \code{R = int}, \code{double}, \code{std::complex<double>}.
|
||||
Ranks, counts of generated objects and class sizes have the type
|
||||
\begin{lstlisting}
|
||||
typedef unsigned long long PERM_RANK_TYPE;
|
||||
\end{lstlisting}
|
||||
(declared outside of namespace \code{LA}); $n!$ overflows it for $n>20$.
|
||||
|
||||
\section{Conventions}\label{sec:permutation:conventions}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Permutations are numbered from 1.} \code{NRPerm<T>}, \code{CyclePerm<T>}, \code{Partition<T>},
|
||||
\code{CompressedPartition<T>} and \code{YoungTableaux<T>} are derived from \code{NRVec_from1}
|
||||
(chapter~\ref{ch:vec}), i.e.\ \code{p[1]} \ldots\ \code{p[n]}. When a permutation is applied to a vector
|
||||
or matrix indexed from 0, the offset 1 is subtracted internally (section~\ref{sec:permutation:apply}).
|
||||
\item The one-line notation stores the images: \code{p[i]} $=p(i)$. A cycle $(c_1\,c_2\ldots c_l)$ maps
|
||||
$c_1\mapsto c_2\mapsto\cdots\mapsto c_l\mapsto c_1$.
|
||||
\item Product: $(pq)(i)=p(q(i))$, i.e.\ in \code{p*q} the right operand \code{q} is applied first.
|
||||
\item Parity is returned as $+1$ (even) or $-1$ (odd) by the \code{parity()} members; the sorting
|
||||
templates of section~\ref{sec:permutation:sort} return the parity bit 0 (even) or 1 (odd).
|
||||
\item The generators (\code{generate_all}, \ldots) call a user callback for each generated object and
|
||||
return their number. They keep their state in file-static variables, so they are neither reentrant nor
|
||||
thread safe; do not start another generator of the same kind from inside the callback.
|
||||
\end{itemize}
|
||||
|
||||
\paragraph{Copy on demand.} All classes of this chapter except \code{WeightPermutation}, \code{Sn_characters},
|
||||
\code{CycleIndex} (which are plain aggregates of such objects) are derived from \code{NRVec}/\code{NRVec_from1}
|
||||
and are reference counted as described in section~\ref{sec:concepts:refcount}: copy construction and
|
||||
\code{=} share the data, \code{|=} copies; before writing through \code{operator[]} the user has to call
|
||||
\code{copyonwrite()}. Nested types (\code{CyclePerm}, \code{YoungTableaux}, \code{PermutationAlgebra})
|
||||
are vectors of vectors; their \code{copyonwrite()} is deep (it also detaches the inner vectors).
|
||||
Most modifying members call \code{copyonwrite()}; the exceptions found are listed in the pitfalls
|
||||
(section~\ref{sec:permutation:pitfalls}).
|
||||
|
||||
\section{NRPerm: permutations in one-line notation}\label{sec:permutation:nrperm}
|
||||
|
||||
\code{template <typename T> class NRPerm : public NRVec_from1<T>} stores a permutation of $\{1,\ldots,n\}$ as the
|
||||
vector of images. Relational operators \code{== != < >} (lexicographic) and the I/O operators \code{<<}, \code{>>}
|
||||
(the size followed by the elements) are inherited from \code{NRVec} (section~\ref{sec:concepts:io}).
|
||||
Many members check \code{is_valid()} of their operands only in DEBUG builds.
|
||||
|
||||
\subsection{Construction and conversion}
|
||||
\begin{lareference}
|
||||
\lafunc{NRPerm(); NRPerm(const int n)}{empty; size $n$, elements uninitialized (call \code{identity()}).}
|
||||
\lafunc{template<int SIZE> explicit NRPerm(const T (&a)[SIZE])}{from an array or brace list, e.g.\ \code{NRPerm<int> p({1,4,2,3})}.}
|
||||
\lafunc{NRPerm(const NRVec_from1<T> &rhs); NRPerm(const T *a, const int n)}{from a vector (shares data) or from $n$ elements at \code{a} (copy).}
|
||||
\lafunc{explicit NRPerm(const CyclePerm<T> &rhs, const int n=0)}{from the cycle notation; size \code{n}, or \code{rhs.max()} if \code{n==0}
|
||||
(fixed points beyond the largest moved element are then lost).}
|
||||
\lafunc{explicit NRPerm(const int type, const NRVec_from1<T> &inversions)}{reconstruct from an inversion table of the given \code{type}
|
||||
(see \code{inversions()}).}
|
||||
\lafunc{explicit NRPerm(const int n, const PERM_RANK_TYPE rank)}{unranking: the permutation of size $n$ with the lexicographic rank
|
||||
\code{rank} $\in[0,n!-1]$ (checked in DEBUG).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Basic operations}
|
||||
\begin{lareference}
|
||||
\lafunc{void resize(int n); int size() const}{as for \code{NRVec}.}
|
||||
\lafunc{void identity(); void identity(int n)}{set to the identity (of size $n$); calls copyonwrite.}
|
||||
\lafunc{bool is_valid() const}{true if the elements are a permutation of $1\ldots n$.}
|
||||
\lafunc{bool is_identity() const}{true for the identity (also for size 0).}
|
||||
\lafunc{int parity() const}{$+1$/$-1$; returns 0 for an invalid permutation. Counts inversions for $n<10$, sorts a copy otherwise.}
|
||||
\lafunc{NRPerm inverse() const}{$p^{-1}$.}
|
||||
\lafunc{void inverseme()}{in-place inversion, $O(n^2)$ cycle-leader algorithm (cf.\ arXiv:1901.01926); detaches shared data first.}
|
||||
\lafunc{void reverse_cycle(int i); int cycle_leader(int i) const}{auxiliary for \code{inverseme()}: reverse in place the cycle containing $i$
|
||||
(no \code{copyonwrite()}, the caller detaches first); return the smallest element of that cycle.}
|
||||
\lafunc{NRPerm reverse() const}{the sequence in backward order: $r(i)=p(n-i+1)$.}
|
||||
\lafunc{NRPerm operator*(const NRPerm &q) const}{product $pq$, $(pq)(i)=p(q(i))$; sizes must agree.}
|
||||
\lafunc{NRPerm multiply(const NRPerm<T> &q, bool inverse) const}{$pq$, or $pq^{-1}$ if \code{inverse} (without forming $q^{-1}$).}
|
||||
\lafunc{NRPerm operator*(const CyclePerm<T> &r) const}{product with a permutation in cycle notation, converted to size \code{size()}.}
|
||||
\lafunc{template<typename R> PermutationAlgebra<T,R> operator*(const PermutationAlgebra<T,R> &pa) const}{left multiplication of each term of \code{pa} by $p$.}
|
||||
\lafunc{NRPerm conjugated_by(const NRPerm &q, bool reverse=false) const}{$q^{-1}pq$, or $qpq^{-1}$ if \code{reverse}.}
|
||||
\lafunc{NRPerm commutator(const NRPerm &q, bool inverse=false) const}{$p^{-1}q^{-1}pq$, or $q^{-1}p^{-1}qp$ if \code{inverse}.}
|
||||
\lafunc{NRPerm pow(const int n) const}{$p^n$, also for $n\le 0$ (by repeated squaring, \code{power()} of \header{nonclass.h}).}
|
||||
\lafunc{CompressedPartition<T> cycles() const}{cycle type: number of cycles of length $1,2,\ldots,n$ (fixed points included).}
|
||||
\lafunc{NRPerm operator&(const NRPerm &rhs) const}{direct sum: $r=(p(1),\ldots,p(n),\,n+q(1),\ldots,n+q(m))$ with \code{q=rhs}, $m$ = \code{rhs.size()}.}
|
||||
\lafunc{NRPerm operator|(const NRPerm &rhs) const}{$r=(n+q(1),\ldots,n+q(m),\,p(1),\ldots,p(n))$, i.e.\ the renumbered \code{rhs} comes first.}
|
||||
\lafunc{NRPerm cutleft(int n) const}{inverse of \code{&} with an identity on the left: requires $p(i)=i$ for $i\le n$ (laerror otherwise), returns the remaining part renumbered to $1\ldots$\code{size()}$-n$.}
|
||||
\lafunc{NRPerm cutright(int n) const}{requires the last $n$ elements to be fixed points and removes them.}
|
||||
\lafunc{void randomize(void)}{uniformly random permutation (Fisher--Yates shuffle with \code{RANDINT32()}); the size must be set.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Ranking and inversion tables}
|
||||
\begin{lareference}
|
||||
\lafunc{PERM_RANK_TYPE rank() const}{lexicographic rank, $0$ for the identity and $n!-1$ for $(n,\ldots,1)$.}
|
||||
\lafunc{NRVec_from1<T> inversions(const int type, PERM_RANK_TYPE *prank=NULL) const}{inversion table $I$ of size $n$:
|
||||
\code{type} 0: $I_j$ = number of elements greater than $j$ standing left of the value $j$ (Knuth's inversion table);
|
||||
1: number of elements smaller than $j$ standing right of the value $j$;
|
||||
2: $I_j=|\{k<j: p(k)>p(j)\}|$;
|
||||
3: $I_j=|\{k>j: p(k)<p(j)\}|$ (Lehmer code; $\mathrm{rank}=\sum_j I_j\,(n-j)!$).
|
||||
Inverse: \code{NRPerm(type,I)}. If \code{prank} is given (only for type 3, otherwise \code{laerror}), \code{*prank} is set to the rank computed
|
||||
from the Lehmer code, the same value as \code{rank()}.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Generating all permutations}
|
||||
The generators overwrite \code{*this}, which is passed to the callback \code{void callback(const NRPerm<T>&)}; a \code{NULL} callback only counts.
|
||||
\begin{lareference}
|
||||
\lafunc{bool next()}{replace by the lexicographically next permutation (works also for repeated elements); returns false (and leaves \code{*this} unchanged) for the last one. Calls copyonwrite.}
|
||||
\lafunc{PERM_RANK_TYPE generate_all_lex(void (*callback)(const NRPerm<T>&))}{all $n!$ permutations in lexicographic order, starting from the identity, using \code{next()}.}
|
||||
\lafunc{PERM_RANK_TYPE generate_all(void (*callback)(const NRPerm<T>&), int parity_select=0)}{all permutations by adjacent transpositions
|
||||
(plain changes, Steinhaus--Johnson--Trotter order \cite{permutation:knuth4a}), so the parity alternates; fastest but not lexicographic.
|
||||
This is Knuth's Algorithm~P. \code{parity_select}: 0 all, $+1$ (positive) only even, $-1$ (negative) only odd permutations; then $n!/2$ is returned.}
|
||||
\lafunc{PERM_RANK_TYPE generate_all2(void (*callback)(const NRPerm<T>&))}{recursive generation, not lexicographic.}
|
||||
\lafunc{PERM_RANK_TYPE generate_all_multi(void (*callback)(const NRPerm<T>&))}{all distinct permutations of a multiset: \code{*this} must be filled
|
||||
beforehand with the (possibly repeated) elements in any order; they are sorted and permuted in lexicographic order (Knuth's
|
||||
algorithm for multisets). The result is not a permutation in the strict sense.}
|
||||
\lafunc{PERM_RANK_TYPE generate_restricted(void (*callback)(const NRPerm<T>&), const NRVec_from1<T> &classes, int restriction_type=0)}{permutations
|
||||
restricted with respect to a partitioning of $1\ldots n$ into classes (\code{classes[i]} is the class label of $i$, size $n$):
|
||||
\code{restriction_type} 0: all; 1: only $p$ with \code{classes[p(i)]==classes[i]} (permutations within classes);
|
||||
$-1$: only $p$ with \code{classes[p(i)]!=classes[i]} for all $i$; $-2$: only permutations keeping the elements of each class in increasing order
|
||||
(``shuffles'', the $n!/\prod_c n_c!$ coset representatives used in antisymmetrizers like $P(ij/kl)$).}
|
||||
\lafunc{PermutationAlgebra<T,T> list_all(int parity_select=0)}{all permutations (order of \code{generate_all}) as a \code{PermutationAlgebra} with weights $\pm1$ = parity,
|
||||
i.e.\ the unnormalized antisymmetrizer; with \code{parity_select} $\pm1$ only the even/odd permutations, all with weight \code{parity_select}.}
|
||||
\lafunc{PermutationAlgebra<T,T> list_all_lex()}{all permutations in lexicographic order (term $k$ has rank $k$), all with weight 1, i.e.\ the unnormalized symmetrizer.}
|
||||
\lafunc{PermutationAlgebra<T,T> list_restricted(const NRVec_from1<T> &classes, int restriction_type=0, bool inverted=false)}{the permutations of
|
||||
\code{generate_restricted}, weight = parity; with \code{inverted} the inverse permutations are stored.}
|
||||
\end{lareference}
|
||||
|
||||
\section{CyclePerm: cycle notation}\label{sec:permutation:cycleperm}
|
||||
|
||||
\code{template <typename T> class CyclePerm}, derived from \code{NRVec_from1< NRVec_from1<T> >}, is a list of cycles. The representation is not
|
||||
unique (order of cycles, starting element, presence of cycles of length 1); \code{CyclePerm(const NRPerm&)} omits fixed points.
|
||||
The size of the permuted set is not stored: wherever it matters it is \code{max()} unless given explicitly.
|
||||
\begin{lareference}
|
||||
\lafunc{CyclePerm(); template<int SIZE> explicit CyclePerm(const NRVec_from1<T> (&a)[SIZE])}{empty (the identity); from an array of cycles, e.g.\ \code{CyclePerm<int> c({NRVec_from1<int>({1,2}),NRVec_from1<int>({3,4,5})})}.}
|
||||
\lafunc{explicit CyclePerm(const NRPerm<T> &rhs)}{cycle decomposition, nontrivial cycles only.}
|
||||
\lafunc{bool is_valid() const}{no empty cycle, positive elements, no element repeated.}
|
||||
\lafunc{bool is_identity() const; void identity()}{test for no cycle longer than 1; set to the identity (no cycles).}
|
||||
\lafunc{CyclePerm inverse() const}{reverse all cycles.}
|
||||
\lafunc{int parity() const}{$-1$ for an odd number of even-length cycles.}
|
||||
\lafunc{T max() const}{largest element occurring.}
|
||||
\lafunc{CompressedPartition<T> cycles(T n = 0) const}{cycle type for size $n$ (default \code{max()}), fixed points counted as 1-cycles.}
|
||||
\lafunc{PERM_RANK_TYPE order() const}{order in the group, the lcm of the cycle lengths.}
|
||||
\lafunc{CyclePerm operator*(const CyclePerm &q) const}{product (\code{q} applied first), computed through \code{NRPerm} of the size of the larger maximum of the two operands.}
|
||||
\lafunc{NRPerm<T> operator*(const NRPerm<T> &r) const}{mixed product, result of size \code{r.size()}.}
|
||||
\lafunc{CyclePerm conjugated_by(const CyclePerm &q) const}{$q^{-1}pq$.}
|
||||
\lafunc{bool operator==(const CyclePerm &rhs) const}{compares via conversion to \code{NRPerm} (so independent of the cycle representation).}
|
||||
\lafunc{void simplify(bool keep1=false)}{remove cycles of length 0 and 1 (with \code{keep1} only empty ones); detaches shared data first.}
|
||||
\lafunc{CyclePerm pow(const int n, const bool keep1=false) const}{$p^n$ (also $n<0$) computed cycle by cycle (a cycle of length $l$ splits into $\gcd(n,l)$ cycles);
|
||||
with \code{keep1} the resulting fixed points are kept as 1-cycles.}
|
||||
\lafunc{CyclePerm pow_simple(const int n) const}{$p^n$ through \code{NRPerm::pow}.}
|
||||
\lafunc{void readfrom(const std::string &line)}{parse cycles like \code{(1 2 3)(4,5)}; elements separated by blanks or commas, empty cycles skipped.}
|
||||
\lafunc{std::istream & operator>>(std::istream &s, CyclePerm<T> &x)}{reads one line and calls \code{readfrom}.}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const CyclePerm<T> &x)}{writes \code{(1 2 3)(4 5)} (no newline).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Weighted permutations and the group algebra}\label{sec:permutation:algebra}
|
||||
|
||||
\subsection{WeightPermutation}
|
||||
\code{template <typename T, typename R> class WeightPermutation} is a pair of public members \code{R weight; NRPerm<T> perm;}, i.e.\ the term
|
||||
$w\,p$ of the group algebra $\mathbb{R}S_n$ (or with coefficients of type \code{R}). The \code{LA_traits} specialization
|
||||
(section~\ref{sec:concepts:traits}) declares it non-plain with \code{coefficienttype} and \code{elementtype} \code{R}, so that \code{NRVec}
|
||||
sorting and \code{NRVec_simplify} work on vectors of it.
|
||||
\begin{lareference}
|
||||
\lafunc{WeightPermutation(); explicit WeightPermutation(const NRPerm<T> &p, const R w=1)}{weight 0 and empty permutation; $w\,p$.}
|
||||
\lafunc{int size() const}{size of the permutation.}
|
||||
\lafunc{bool is_zero() const; bool is_scaledidentity() const; bool is_identity() const}{$w=0$; $p$ = identity; $w=1$ and $p$ = identity.}
|
||||
\lafunc{bool is_plaindata() const; void copyonwrite()}{returns false; \code{perm.copyonwrite()}.}
|
||||
\lafunc{void identity(); void identity(int n)}{$w=1$, $p$ = identity (of size $n$).}
|
||||
\lafunc{WeightPermutation operator&(const WeightPermutation &rhs) const; ... operator|(...) const}{direct sums of the permutations (as for \code{NRPerm}), weights multiplied.}
|
||||
\lafunc{WeightPermutation operator*(const WeightPermutation &rhs) const; WeightPermutation operator*(const R &x) const}{product of terms; scaling of the weight.}
|
||||
\lafunc{bool operator==(const WeightPermutation &rhs) const; != < > <= >=}{compare \emph{only the permutations}, ignoring the weights (needed for sorting and simplification).}
|
||||
\lafunc{WeightPermutation & operator=(const WeightPermutation &rhs)}{memberwise (the permutation is shared).}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const WeightPermutation<T,R> &x); std::istream & operator>>(...)}{weight followed by the permutation.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{PermutationAlgebra}
|
||||
\code{template <typename T, typename R> class PermutationAlgebra : public NRVec<WeightPermutation<T,R> >} represents
|
||||
$\sum_k w_k p_k$ as a vector of terms indexed \emph{from 0}. Products and sums simplify the result; a canonical form (sorted by permutation,
|
||||
equal permutations merged, zero terms removed) is obtained by \code{simplify()}. Such objects are used e.g.\ by
|
||||
\code{Tensor::apply_permutation_algebra} (chapter~\ref{ch:tensor}) and by the \code{NRMat} constructor of section~\ref{sec:permutation:apply}.
|
||||
\begin{lareference}
|
||||
\lafunc{PermutationAlgebra(); PermutationAlgebra(int n); PermutationAlgebra(const NRVec<WeightPermutation<T,R> > &x)}{empty; $n$ uninitialized terms; from a vector of terms.}
|
||||
\lafunc{template <typename U> PermutationAlgebra(const PermutationAlgebra<T,U> &rhs)}{conversion of the weight type (cast \code{(R)}).}
|
||||
\lafunc{int size() const; void resize(int n); void copyonwrite()}{as for \code{NRVec}.}
|
||||
\lafunc{int sort(int direction = 0, int from = 0, int to = -1, int *permut = NULL, bool stable=false)}{\code{NRVec::sort} of the terms by their permutations.}
|
||||
\lafunc{void simplify(const typename LA_traits<R>::normtype thr=0)}{sort, add weights of equal permutations, drop zero terms (\code{NRVec_simplify}; terms
|
||||
are dropped if $w=0$ or if \code{abs2(w)<thr}, i.e.\ \code{thr} is compared with $|w|^2$).}
|
||||
\lafunc{PermutationAlgebra operator*(const PermutationAlgebra &rhs) const}{algebra product (all pairs of terms), simplified; permutation sizes must agree.}
|
||||
\lafunc{PermutationAlgebra operator+(const PermutationAlgebra &rhs) const}{sum (concatenation), simplified.}
|
||||
\lafunc{PermutationAlgebra operator&(const PermutationAlgebra &rhs) const; ... operator|(...) const}{all pairs of terms combined by \code{&} or \code{|}
|
||||
(tensor product of algebras acting on disjoint sets); not simplified.}
|
||||
\lafunc{PermutationAlgebra operator*(const NRPerm<T> &rhs) const; operator&(const NRPerm<T> &rhs) const; operator|(const NRPerm<T> &rhs) const}{applied to all terms: $\sum_k w_k\,p_k q$, $p_k\,\&\,q$, $p_k\,|\,q$.}
|
||||
\lafunc{PermutationAlgebra cutleft(int n) const; PermutationAlgebra cutright(int n) const}{\code{NRPerm::cutleft/cutright} of all terms.}
|
||||
\lafunc{PermutationAlgebra conjugated_by(const NRPerm<T> &q, bool reverse=false) const; PermutationAlgebra commutator(const NRPerm<T> &q, bool inverse=false) const}{applied to all terms.}
|
||||
\lafunc{PermutationAlgebra &operator*=(const R &x); PermutationAlgebra operator*(const R &x) const}{scale all weights.}
|
||||
\lafunc{bool operator==(PermutationAlgebra &rhs)}{simplifies \emph{both} operands (hence non-const) and compares terms including weights.}
|
||||
\lafunc{bool is_zero() const; bool is_scaled_identity() const; bool is_identity() const}{tests valid for simplified objects only: no term; single term with identity permutation (and weight 1).}
|
||||
\lafunc{void identity(int n)}{the single term $1\cdot\mathrm{id}_n$.}
|
||||
\end{lareference}
|
||||
Inherited \code{NRVec} I/O prints the number of terms followed by the terms.
|
||||
|
||||
\paragraph{Related free functions.}
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T> PermutationAlgebra<T,T> symmetrizer(int n); ... antisymmetrizer(int n)}{$\sum_{p\in S_n} p$ and $\sum_{p\in S_n}\mathrm{sgn}(p)\,p$
|
||||
(unnormalized), built as Young operators of a single row/column.}
|
||||
\lafunc{template<typename T> PermutationAlgebra<T,T> general_antisymmetrizer(const NRVec<NRVec_from1<T> > &classgroups, int restriction_type=0, bool inverted=false)}{direct
|
||||
product (by \code{&}) of \code{list_restricted(classgroups[i], restriction_type, inverted)} for all groups; group $i$ acts on the next
|
||||
\code{classgroups[i].size()} elements. Used for antisymmetrizers in coupled-cluster-like expressions (see the tensor chapter).}
|
||||
\lafunc{template <typename T, typename R, typename U> void cast_permutation_algebra(PermutationAlgebra<T,R> &lhs, const PermutationAlgebra<T,U> &rhs)}{conversion of the weight type into an existing object.}
|
||||
\lafunc{template<typename T> NRMat<PERM_RANK_TYPE> Multable(T n)}{multiplication table of $S_n$ ($n!\times n!$) in the basis of permutations ordered by lexicographic rank:
|
||||
\code{r(i,j)} = rank of $p_ip_j$. Consistency (Latin square) is checked in DEBUG.}
|
||||
\lafunc{template<typename T, typename R> NRMat<R> RegularRepresentation(const PermutationAlgebra<T,R> &a, const NRMat<PERM_RANK_TYPE> &mtable)}{matrix of left multiplication by
|
||||
\code{a} in the regular representation: $R_{\mathrm{rank}(p_kp_j),\,j} \mathrel{+}= w_k$; \code{mtable} from \code{Multable(n)}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Partitions}\label{sec:permutation:partitions}
|
||||
|
||||
Two representations of a partition $\lambda\vdash n$ are used, both as vectors of size $n$ (indexed from 1):
|
||||
\code{Partition<T>} stores the parts in non-increasing order, padded with zeros ($[3,1]$ of 4 is \code{3 1 0 0});
|
||||
\code{CompressedPartition<T>} stores the multiplicities $m_i$ of parts of size $i$ ($[3,1]$ is \code{1 0 1 0}), printed as \code{3,1};
|
||||
$[2,1^2]$ is printed \code{2,1^2}. Partitions label the conjugacy classes of $S_n$ (cycle types) and its irreducible representations.
|
||||
|
||||
\subsection{CompressedPartition}
|
||||
\begin{lareference}
|
||||
\lafunc{CompressedPartition(); CompressedPartition(const int n); template<int SIZE> explicit CompressedPartition(const T (&a)[SIZE])}{empty; size $n$ uninitialized; from an array.}
|
||||
\lafunc{explicit CompressedPartition(const Partition<T> &rhs)}{conversion.}
|
||||
\lafunc{T sum() const; T nparts() const; T nclasses() const}{$\sum_i i\,m_i$; number of parts $\sum_i m_i$; number of distinct part sizes.}
|
||||
\lafunc{bool is_valid() const}{\code{size()==sum()}.}
|
||||
\lafunc{PERM_RANK_TYPE Sn_class_size() const}{size of the conjugacy class of $S_n$ with this cycle type, $n!/\prod_i i^{m_i}m_i!$.}
|
||||
\lafunc{int parity() const}{parity of permutations with this cycle type.}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const CompressedPartition<T> &x)}{e.g.\ \code{3,2^2,1}.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Partition}
|
||||
\begin{lareference}
|
||||
\lafunc{Partition(); Partition(const int n); template<int SIZE> explicit Partition(const T (&a)[SIZE])}{empty; size $n$ uninitialized; from an array (zero-padded to $n$ by the user).}
|
||||
\lafunc{explicit Partition(const CompressedPartition<T> &rhs); explicit Partition(const YoungTableaux<T> &x)}{conversion; the shape of a Young tableau.}
|
||||
\lafunc{T nparts() const; bool is_valid() const}{number of nonzero parts; \code{size()==sum()} and non-increasing.}
|
||||
\lafunc{Partition adjoint() const}{conjugate (transposed) partition $\lambda'$.}
|
||||
\lafunc{PERM_RANK_TYPE Sn_irrep_dim() const}{dimension of the irrep $[\lambda]$ of $S_n$ (hook length formula).}
|
||||
\lafunc{PERM_RANK_TYPE Un_irrep_dim(const int n) const}{dimension of the irrep of $U(n)$ with the highest weight $\lambda$ \cite[eq.~10-25]{permutation:hamermesh}; 0 if $\lambda$ has more than $n$ parts.}
|
||||
\lafunc{int parity() const}{parity of permutations with the cycle type $\lambda$.}
|
||||
\lafunc{PERM_RANK_TYPE generate_all(void (*callback)(const Partition<T>&), int nparts=0)}{all partitions of \code{size()} in reverse lexicographic order
|
||||
($[n]$ first, $[1^n]$ last); \code{nparts}$>0$: exactly \code{nparts} parts, $<0$: at most $-$\code{nparts} parts, 0: all. Overwrites \code{*this}; returns the count.}
|
||||
\end{lareference}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{PERM_RANK_TYPE partitions(int n, int k= -1)}{number of partitions of $n$ into exactly $k$ parts, or the total number $p(n)$ for $k=-1$ \cite{permutation:aigner};
|
||||
tabulated recursively up to $n=260$ (larger $n$ only for $k\ge n/2$).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Young tableaux}\label{sec:permutation:young}
|
||||
|
||||
\code{template <typename T> class YoungTableaux}, derived from \code{NRVec_from1< NRVec_from1<T> >}, stores the rows of a Young diagram (row $i$ has
|
||||
length $\lambda_i$), each box holding a number (0 = empty).
|
||||
\begin{lareference}
|
||||
\lafunc{YoungTableaux(); explicit YoungTableaux(const Partition<T> &frame)}{empty; empty (zero-filled) diagram of the shape \code{frame}.}
|
||||
\lafunc{template<int SIZE> explicit YoungTableaux(const NRVec_from1<T> (&a)[SIZE])}{from an array of rows.}
|
||||
\lafunc{bool is_valid() const}{nonempty rows of non-increasing length.}
|
||||
\lafunc{int nrows() const; int ncols() const}{number of rows; length of the first row.}
|
||||
\lafunc{bool is_standard() const}{numbers non-decreasing along rows and columns (repeated numbers allowed).}
|
||||
\lafunc{T sum() const; T max() const}{number of boxes $n$; largest number filled in.}
|
||||
\lafunc{NRVec_from1<T> yamanouchi() const}{Yamanouchi symbol of a standard tableau: element $n-k+1$ is the row containing the number $k$.}
|
||||
\lafunc{PERM_RANK_TYPE generate_all_standard(void (*callback)(const YoungTableaux<T>&))}{fill \code{*this} in all standard ways with $1\ldots n$, call the callback for each; returns their number
|
||||
($=$ \code{Sn_irrep_dim()} of the shape).}
|
||||
\lafunc{PermutationAlgebra<T,T> young_operator() const}{Young operator of a standard tableau, $Y=\sum_{a\in C}\sum_{s\in R}\mathrm{sgn}(a)\,a\,s$ ($R$, $C$: row and column groups), unnormalized,
|
||||
$\prod_i\lambda_i!\,\prod_j\lambda'_j!$ terms.}
|
||||
\lafunc{T character_contribution(int ncyc=0) const}{auxiliary for \code{Sn_character}: sign contribution of a tableau filled by a regular application of \code{ncyc} cycles (default \code{max()}).}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const YoungTableaux<T> &x)}{one row per line.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Characters of the symmetric group}\label{sec:permutation:characters}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T> T Sn_character(const Partition<T> &irrep, const Partition<T> &cclass)}{character $\chi^{[\lambda]}(\mu)$ of the irrep
|
||||
\code{irrep} on the class of cycle type \code{cclass}, computed by enumerating the regular applications of the cycles to the Young diagram
|
||||
(Murnaghan--Nakayama type rule, \cite{permutation:hamermesh}).}
|
||||
\lafunc{T Sn_character(const CompressedPartition<T> &irrep, const CompressedPartition<T> &cclass)}{the same for compressed partitions.}
|
||||
\end{lareference}
|
||||
|
||||
\code{template <typename T> class Sn_characters} is the whole character table of $S_n$ with public members
|
||||
\begin{lstlisting}
|
||||
T n;
|
||||
NRVec_from1<CompressedPartition<T> > classes; //cycle types
|
||||
NRVec_from1<CompressedPartition<T> > irreps; //irrep labels
|
||||
NRVec_from1<PERM_RANK_TYPE> classsizes;
|
||||
NRMat_from1<T> chi; //chi(irrep,class)
|
||||
\end{lstlisting}
|
||||
The irreps are ordered as in \code{Partition::generate_all} ($[n]$ first, $[1^n]$ last) and \code{classes[i]} is the conjugate of \code{irreps[i]},
|
||||
so class 1 is the identity and \code{chi(i,1)} the dimension.
|
||||
\begin{lareference}
|
||||
\lafunc{Sn_characters(const int n0)}{compute the table (cost grows quickly with $n$).}
|
||||
\lafunc{bool is_valid() const}{consistency checks: dimensions vs.\ hook formula, $\sum d^2=n!$, class sizes, trivial and sign irreps, orthogonality.}
|
||||
\lafunc{T irrepdim(T i) const; T sumirrepdims() const}{dimension of irrep $i$; $\sum_i d_i$ (number of standard tableaux of size $n$).}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const Sn_characters<T> &c)}{prints class sizes, class labels \code{(..)}, and rows \code{[irrep] chi}.}
|
||||
\end{lareference}
|
||||
|
||||
\code{template <typename T> class CycleIndex} (public \code{classes}, \code{classsizes}) is the cycle index of $S_n$ for P\'olya counting:
|
||||
\begin{lareference}
|
||||
\lafunc{CycleIndex(const Sn_characters<T> &rhs); bool is_valid() const}{take the classes from a character table; check that all classes are partitions of the same $n$.}
|
||||
\lafunc{Polynomial<T> substitute(const Polynomial<T> &p, PERM_RANK_TYPE *denom) const}{$\sum_{\mu}|C_\mu|\prod_j p(x^j)^{m_j(\mu)}$ and \code{*denom} $=\sum_\mu|C_\mu|=n!$;
|
||||
the generating function of inequivalent colorings is the result divided by \code{*denom}. See chapter~\ref{ch:polynomial} for \code{Polynomial}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Applying permutations to vectors and matrices}\label{sec:permutation:apply}
|
||||
|
||||
The following members are declared in the vector and matrix headers (chapters~\ref{ch:vec}, \ref{ch:mat}, \ref{ch:smat}, \ref{ch:sparse}) and take
|
||||
\code{NRPerm<int>} or \code{CyclePerm<int>}; the permutation is numbered from 1 while \code{NRVec} and \code{NRMat} are indexed from 0.
|
||||
With $p$ = \code{p}, $v$ the source and $r$ the result (indices from 1):
|
||||
\begin{center}\small
|
||||
\begin{tabular}{@{}>{\raggedright}p{0.47\linewidth}>{\raggedright\arraybackslash}p{0.47\linewidth}@{}}
|
||||
\toprule
|
||||
call & result \\
|
||||
\midrule
|
||||
\code{r=v.permuted(p)} & $r_{p(i)}=v_i$ \\
|
||||
\code{r=v.permuted(p,true)} & $r_i=v_{p(i)}$ \\
|
||||
\code{v.permuteme(p,inverse)} & in place, as \code{permuted} \\
|
||||
\code{v.permuteme(c)} (\code{CyclePerm}) & in place, $v_{c(i)}\leftarrow v_i$ \\
|
||||
\code{applypermutation(p,v)} (\code{NRVec_from1}) & $r_{p(i)}=v_i$, as \code{permuted} \\
|
||||
\code{applypermutation(p,v,true)} & $r_i=v_{p(i)}$ \\
|
||||
\code{A.permuted_rows(p)}, \code{permuted_cols}, \code{permuted_both(p,q)} & $r_{p(i),j}=A_{ij}$ etc.; with \code{true}: $r_{ij}=A_{p(i),j}$ \\
|
||||
\code{A.permuteme_rows(c)}, \code{permuteme_cols(c)} (\code{CyclePerm}) & in place, as \code{permuted_rows(NRPerm(c))} \\
|
||||
\code{S.permuted(p)} (\code{NRSMat}) & $r_{p(i)p(j)}=S_{ij}$; with \code{true}: $r_{ij}=S_{p(i)p(j)}$ \\
|
||||
\code{NRMat<T>(p, direction, parity=false)} & permutation matrix: $A_{i,p(i)}=1$ if \code{direction}, else $A_{p(i),i}=1$ (times \code{p.parity()} if \code{parity}) \\
|
||||
\code{NRMat<T>(wp,direction)}, \code{NRMat<T>(pa,direction,nforce=0)} & $\sum_k w_kP_k$ for a \code{WeightPermutation} / \code{PermutationAlgebra} \\
|
||||
\code{A.axpy(alpha,p,direction)} & $A\mathrel{+}=\alpha P$ \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T, typename X> NRVec_from1<X> applypermutation(const NRPerm<T> &p, const NRVec_from1<X> &set, bool inverse=false)}{a free function
|
||||
(element type independent of \code{T}), same convention as \code{NRVec::permuted}.}
|
||||
\lafunc{int NRVec<T>::sort(int direction, NRPerm<int> &perm, bool stable=false)}{sort the vector (0 ascending, 1 descending), return the parity bit;
|
||||
afterwards \code{sorted == original.permuted(perm,true)}.}
|
||||
\end{lareference}
|
||||
For the matrix representation of $S_n$ by permutation matrices note that it is reducible ($[n]\oplus[n-1,1]$), so e.g.\ Young projectors are not
|
||||
faithfully represented; use \code{RegularRepresentation} instead.
|
||||
|
||||
\section{Small number-theoretic helpers}
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T> T gcd(T big, T small)}{greatest common divisor by Euclid's algorithm; laerror if both are 0. For negative arguments the sign of the result is not normalized.}
|
||||
\lafunc{template <typename T> inline T lcm(T a, T b)}{$(a/\gcd(a,b))\,b$.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Sorting}\label{sec:permutation:sort}
|
||||
\laheader{qsort.h}
|
||||
|
||||
The sorting templates are used by \code{NRVec::sort} and by other classes and return the parity bit (0 even, 1 odd) of the permutation
|
||||
performed, which is needed e.g.\ for signs of determinants or of fermionic operator strings. The quicksorts use median-of-3 pivoting and recurse
|
||||
first into the shorter part; they are not stable (except \code{memqsortstable}).
|
||||
\begin{lareference}
|
||||
\lafunc{template<typename INDEX, typename COMPAR> int genqsort(INDEX l, INDEX r, COMPAR (*cmp)(const INDEX, const INDEX), void (*swap)(const INDEX,const INDEX))}{sort
|
||||
the abstract range $[l,r]$ (inclusive) in ascending order; \code{cmp(i,j)} returns negative/zero/positive like \code{strcmp}, \code{swap(i,j)} exchanges the items.}
|
||||
\lafunc{template<int type, typename SORTABLE, typename INDEX, typename PERMINDEX> int memqsort(SORTABLE &object, PERMINDEX *perm, INDEX l, INDEX r)}{sort
|
||||
elements $l\ldots r$ of \code{object}, ascending for \code{type=0}, descending for \code{type=1}; the comparisons go through \code{LA_sort_traits<SORTABLE,INDEX,type>},
|
||||
which call \code{object.bigger(i,j)} / \code{object.smaller(i,j)}; \code{object.swap(i,j)} exchanges. If \code{perm!=NULL}, \code{perm[l..r]} is permuted in the same way.}
|
||||
\lafunc{template<int type, typename SORTABLE, typename INDEX, typename PERMINDEX> int memqsortstable(SORTABLE &object, PERMINDEX *perm, INDEX l, INDEX r)}{stable
|
||||
variant (original position as secondary key; requires also \code{object[i]==object[j]}), uses \code{memqsortaux}.}
|
||||
\lafunc{template<int type, typename SORTABLE, typename INDEX, typename PERMINDEX> int memqsortaux(SORTABLE &object, PERMINDEX *perm, INDEX l, INDEX r, INDEX *aux)}{worker of \code{memqsortstable} with the auxiliary key array \code{aux[l..r]}.}
|
||||
\lafunc{template<typename S, typename PERMINDEX> int ptrqsortup(S *l, S *r, PERMINDEX *perm=NULL); ... ptrqsortdown(...)}{sort the array $[l,r]$ (pointers, inclusive) ascending/descending
|
||||
by \code{operator<}, \code{operator>}; \code{perm[0..r-l]} is permuted along.}
|
||||
\lafunc{template<typename INDEX, typename DATA> void myheapsort(INDEX n, DATA *ra, int typ=1)}{heapsort of \code{ra[0..n-1]}, ascending for \code{typ>0}, descending otherwise; no parity.}
|
||||
\lafunc{template<typename T> int netsort(const int n, T *data)}{ascending sort of a short array by optimal sorting networks for $n\le 8$ (\code{netsort_0} \ldots\ \code{netsort_8}),
|
||||
\code{ptrqsortup} for longer; returns the parity bit.}
|
||||
\end{lareference}
|
||||
To make a class sortable by \code{memqsort} it has to provide \code{bool bigger(int i, int j) const}, \code{bool smaller(int i, int j) const},
|
||||
\code{void swap(int i, int j)} (and \code{operator[]} for the stable variant), as \code{NRVec} does.
|
||||
|
||||
\section{Examples}\label{sec:permutation:examples}
|
||||
|
||||
Basic operations, ranking and conversion to cycles:
|
||||
\begin{lstlisting}
|
||||
NRPerm<int> p({2,3,1});
|
||||
NRPerm<int> q=p*p; // (3 1 2)
|
||||
std::cout << p.rank() << ' ' << p.parity() << std::endl; // 3 1
|
||||
NRPerm<int> r(3,p.rank()); // unranking, r==p
|
||||
CyclePerm<int> c(p); // (1 2 3)
|
||||
std::cout << c << ' ' << c.cycles() << std::endl;
|
||||
NRVec_from1<double> v({10.,20.,30.});
|
||||
std::cout << v.permuted(p) << applypermutation(p,v); // 30 10 20 / 30 10 20
|
||||
\end{lstlisting}
|
||||
Generating permutations with a callback (store copies with \code{|=}, cf.\ the pitfalls):
|
||||
\begin{lstlisting}
|
||||
static void printme(const NRPerm<int> &p) {std::cout << p.parity() << ' ' << p;}
|
||||
...
|
||||
NRPerm<int> p(4);
|
||||
PERM_RANK_TYPE n=p.generate_all_lex(printme); // 24
|
||||
NRVec_from1<int> classes({1,1,2,2});
|
||||
PermutationAlgebra<int,int> a=p.list_restricted(classes,-2); // 6 shuffles with signs
|
||||
\end{lstlisting}
|
||||
Character table, Young operators and their regular representation (cf.\ \header{t.cc}):
|
||||
\begin{lstlisting}
|
||||
static NRMat<PERM_RANK_TYPE> mtable;
|
||||
|
||||
static void ycallback(const YoungTableaux<int> &y)
|
||||
{
|
||||
PermutationAlgebra<int,int> Y = y.young_operator();
|
||||
NRMat<int> R = RegularRepresentation(Y,mtable);
|
||||
std::cout << y << "Y = " << Y << "trace of R = " << R.trace() << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
Sn_characters<int> Sn(4);
|
||||
std::cout << Sn;
|
||||
if(!Sn.is_valid()) laerror("internal error");
|
||||
mtable = Multable(4);
|
||||
Partition<int> lambda({2,1,1,0});
|
||||
YoungTableaux<int> y(lambda);
|
||||
PERM_RANK_TYPE dim = y.generate_all_standard(ycallback); // 3 == lambda.Sn_irrep_dim()
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}\label{sec:permutation:pitfalls}
|
||||
|
||||
\begin{itemize}
|
||||
\item Permutations are numbered from 1, \code{PermutationAlgebra} terms from 0; vectors and matrices from 0 (the library subtracts the offset).
|
||||
\item The two conventions of applying a permutation to a vector (table in section~\ref{sec:permutation:apply}) differ; check with a small example.
|
||||
\item \code{CyclePerm} does not know the size of the permuted set; pass \code{n} to \code{NRPerm(const CyclePerm&,n)} and \code{cycles(n)}.
|
||||
\item The \code{operator==} of \code{WeightPermutation} ignores the weights; that of \code{PermutationAlgebra} simplifies its operands.
|
||||
\item In the callbacks of the generators the argument refers to the working object of the generator. All generators
|
||||
detach it after the callback, so a shallow copy stored by the callback stays intact.
|
||||
\item Copy on demand: the auxiliary \code{NRPerm::reverse_cycle()} writes through \code{operator[]} without
|
||||
\code{copyonwrite()}; call \code{copyonwrite()} before using it directly (\code{inverseme()} does so).
|
||||
\item Generators and \code{Sn_character} use static variables: not reentrant, not thread safe.
|
||||
\item $n!$ and class sizes are \code{unsigned long long}: overflow for $n>20$.
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,207 @@
|
||||
\chapter{Polynomials and continued fractions}\label{ch:polynomial}
|
||||
|
||||
This chapter describes the polynomial class \code{Polynomial<T>} and the exact rational and continued-fraction arithmetics
|
||||
(\code{Rational<T>}, \code{ContFrac<T>} with Gosper's algorithms).
|
||||
|
||||
\section{Polynomial}\label{sec:polynomial:polynomial}
|
||||
\laheader{polynomial.h, polynomial.cc}
|
||||
|
||||
\subsection{Purpose and storage}
|
||||
\code{template <typename T> class Polynomial : public NRVec<T>} represents $p(x)=\sum_{i=0}^{d}c_ix^i$; the coefficient $c_i$ is the vector element
|
||||
\code{p[i]} (indexed from 0, i.e.\ by the power) and the vector has $d+1$ elements. The member \code{size()} is made private to prevent confusion; use
|
||||
\code{degree()}. The leading coefficient may be zero (e.g.\ after a subtraction); \code{simplify()} removes such terms. The class is explicitly
|
||||
instantiated for \code{int}, \code{unsigned int}, \code{double}, \code{std::complex<double>}; root finding and SVD-based gcd require a floating point type.
|
||||
|
||||
All inherited \code{NRVec} functionality (norms, \code{randomize}, \code{clear}, I/O operators printing the number of coefficients followed by the
|
||||
coefficients, \ldots) is available. \code{Polynomial} follows the copy-on-demand scheme of section~\ref{sec:concepts:refcount}: its modifying members call
|
||||
\code{copyonwrite()} or \code{resize()}; writing coefficients through \code{operator[]} requires an explicit \code{copyonwrite()} as for \code{NRVec}.
|
||||
The operations are CPU only (\code{NOT_GPU}, section~\ref{sec:concepts:gpu}).
|
||||
|
||||
\subsection{Construction}
|
||||
\begin{lareference}
|
||||
\lafunc{Polynomial()}{empty (no coefficients).}
|
||||
\lafunc{Polynomial(const int n)}{degree $n$, coefficients uninitialized. Note that \code{Polynomial<int>(3)} is \emph{not} the constant 3.}
|
||||
\lafunc{Polynomial(const T &a, const int n)}{degree $n$ with $c_0=a$ and all other coefficients zero.}
|
||||
\lafunc{template<int SIZE> Polynomial(const T (&a)[SIZE])}{from coefficients $c_0,c_1,\ldots$, e.g.\ \code{Polynomial<double> p({1,2,3})} is $1+2x+3x^2$.}
|
||||
\lafunc{Polynomial(const NRVec<T> &v)}{implicit conversion from a vector of coefficients (shares data).}
|
||||
\lafunc{void binomial(const int n)}{set to $(1+x)^n$.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Arithmetic}
|
||||
\begin{lareference}
|
||||
\lafunc{int degree() const}{the degree $d$ (number of coefficients minus 1).}
|
||||
\lafunc{void resize(const int n, const bool preserve=true)}{set the degree to $n$; added coefficients are zero when preserving.}
|
||||
\lafunc{Polynomial& operator+=(const T &a); Polynomial& operator-=(const T &a); Polynomial operator+(const T &a) const; Polynomial operator-(const T &a) const}{add/subtract a constant.}
|
||||
\lafunc{Polynomial operator-() const}{negation.}
|
||||
\lafunc{Polynomial operator*(const T &a) const; Polynomial operator/(const T &a) const; Polynomial& operator*=(const T &a); Polynomial& operator/=(const T &a)}{scaling.}
|
||||
\lafunc{Polynomial& operator+=(const Polynomial &rhs); Polynomial& operator-=(const Polynomial &rhs); Polynomial operator+(const Polynomial &rhs) const; Polynomial operator-(const Polynomial &rhs) const}{sum
|
||||
and difference; the degree is the maximum of both (no simplification).}
|
||||
\lafunc{Polynomial operator*(const Polynomial &rhs) const; Polynomial& operator*=(const Polynomial &rhs)}{product, naive $O(d_1d_2)$ algorithm (no FFT).
|
||||
It hides the inherited \code{NRVec} scalar product.}
|
||||
\lafunc{void polydiv(const Polynomial &rhs, Polynomial &q, Polynomial &r) const}{division with remainder, $p=q\cdot\mathrm{rhs}+r$, $\deg r<\deg\mathrm{rhs}$ (the remainder
|
||||
has the degree $\deg\mathrm{rhs}-1$ formally); laerror if the leading coefficient of \code{rhs} is zero.}
|
||||
\lafunc{Polynomial operator/(const Polynomial &rhs) const}{quotient of \code{polydiv}; \texttt{operator\%} with the same signature returns the remainder.}
|
||||
\lafunc{Polynomial pow(const int i) const}{$p^i$, $i\ge0$ (repeated squaring).}
|
||||
\lafunc{Polynomial shifted(const int shift) const}{multiplication by $x^{\mathrm{shift}}$; for negative \code{shift} division by $x^{-\mathrm{shift}}$, dropping the low-order terms.}
|
||||
\lafunc{void simplify(const typename LA_traits<T>::normtype thr=0)}{remove leading coefficients with $|c_i|\le$ \code{thr} (the degree stays $\ge0$).}
|
||||
\lafunc{void normalize()}{divide by the leading coefficient (monic polynomial); laerror if it is zero.}
|
||||
\end{lareference}
|
||||
|
||||
\paragraph{Free operators.}
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T> Polynomial<T> operator+(const T &a, const Polynomial<T> &rhs)}{$a+p$.}
|
||||
\lafunc{template <typename T> Polynomial<T> operator-(const T &a, const Polynomial<T> &rhs) = delete}{Deliberately not available (compile error); write \code{-p+a}.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Calculus and transformations}
|
||||
\begin{lareference}
|
||||
\lafunc{Polynomial derivative(const int d=1) const}{$d$-th derivative ($d<0$: integral); the zero polynomial of degree 0 if $d>\deg p$.}
|
||||
\lafunc{Polynomial integral(const int d=1) const}{$d$-fold integral with zero integration constants ($d<0$: derivative).}
|
||||
\lafunc{Polynomial composition(const Polynomial &rhs) const}{$p(\mathrm{rhs}(x))$, the same as \code{value(p,rhs)}.}
|
||||
\lafunc{Polynomial powx(const int i) const}{$p(x^i)$; for $i=0$ the constant $p(1)$.}
|
||||
\lafunc{Polynomial even_powers() const; Polynomial odd_powers() const}{$E$ and $O$ such that $p(x)=E(x^2)+x\,O(x^2)$.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Evaluation}
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T, typename C> C value(const Polynomial<T> &p, const C &x)}{Horner scheme; \code{C} may be a scalar, a square matrix
|
||||
(\code{NRMat}, \code{NRSMat}, \ldots; constants are added to the diagonal), or another polynomial (composition). \code{C} needs a copy constructor,
|
||||
\code{=0}, \code{+=} with \code{T} and \code{operator*}.}
|
||||
\lafunc{template <typename T, typename C> C even_value(const Polynomial<T> &p, const C &x); C odd_value(const Polynomial<T> &p, const C &x)}{sums of the even resp.\ odd
|
||||
terms only, $E(x^2)$ and $x\,O(x^2)$; \code{C} may also be a square matrix.}
|
||||
\lafunc{template <typename T, typename C> NRVec<C> values(const Polynomial<T> &p, const NRVec<C> &x)}{\code{value} at each element of \code{x}.}
|
||||
\end{lareference}
|
||||
For polynomials of matrices with fewer matrix multiplications see \code{polynom()} in \header{matexp.h}, which accepts the coefficient
|
||||
vector, hence also a \code{Polynomial} (section~\ref{sec:nonclass:matexp}).
|
||||
|
||||
\subsection{Roots, interpolation, gcd}
|
||||
\begin{lareference}
|
||||
\lafunc{NRMat<T> companion() const}{companion matrix ($d\times d$, ones on the superdiagonal, last row $-c_i/c_d$) whose characteristic polynomial is $p/c_d$.}
|
||||
\lafunc{NRVec<typename LA_traits<T>::complextype> roots() const}{all roots as eigenvalues of the companion matrix (\code{gdiagonalize}, chapter~\ref{ch:nonclass}); for \code{double}
|
||||
and \code{std::complex<double>} only (laerror for integer types).}
|
||||
\lafunc{NRVec<T> realroots(const typename LA_traits<T>::normtype thr) const}{roots with $|\mathrm{Im}|<$ \code{thr}, real parts sorted ascending (\code{T = double}).}
|
||||
\lafunc{T newton(const T x0, const typename LA_traits<T>::normtype thr=1e-14, const int maxit=1000) const}{Newton iteration from \code{x0} until $|p(x)|<$ \code{thr}; no convergence report.}
|
||||
\lafunc{template <typename T> Polynomial<T> polyfromroots(const NRVec<T> &roots, const int skip= -1)}{monic $\prod_{i\ne\mathrm{skip}}(x-r_i)$.}
|
||||
\lafunc{template <typename T> Polynomial<T> lagrange_interpolation(const NRVec<T> &x, const NRVec<T> &y)}{interpolating polynomial of degree $n-1$ through the $n$ points; laerror for repeated $x$.}
|
||||
\lafunc{template <typename T> NRMat<T> Sylvester(const Polynomial<T> &p, const Polynomial<T> &q)}{Sylvester matrix of dimension $\deg p+\deg q$ (its determinant is the resultant).}
|
||||
\lafunc{template <typename T> Polynomial<T> poly_gcd(const Polynomial<T> &p, const Polynomial<T> &q, const typename LA_traits<T>::normtype thr=0, const int d= -1)}{Euclid's
|
||||
algorithm with \code{simplify(thr)} of the remainders, result not normalized. It stops at a zero remainder (degree 0 with $|c_0|\le$\code{thr}); with \code{d>=0} also when the remainder degree drops below \code{d}.
|
||||
For coprime polynomials the result is a nonzero constant; $\gcd(p,0)=p$.}
|
||||
\lafunc{template <typename T> Polynomial<T> svd_gcd(const Polynomial<T> &p, const Polynomial<T> &q, const typename LA_traits<T>::normtype thr=0)}{numerically more robust gcd:
|
||||
the degree $\deg p+\deg q-\mathrm{rank}\,S$ is determined from the singular values of the Sylvester matrix $S$ (threshold \code{thr} scaled by $\sqrt{\deg p\deg q}$),
|
||||
then \code{poly_gcd} with this degree. Floating point types only.}
|
||||
\lafunc{template <typename T> Polynomial<T> poly_lcm(const Polynomial<T> &p, const Polynomial<T> &q, const typename LA_traits<T>::normtype thr=0)}{$pq/\gcd(p,q)$.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Orthogonal polynomials}
|
||||
\begin{lareference}
|
||||
\lafunc{template <typename T> Polynomial<T> hermite_polynomial(int n)}{Hermite polynomial $H_n$ in the physicists' normalization (leading coefficient $2^n$).}
|
||||
\end{lareference}
|
||||
No other families are implemented.
|
||||
|
||||
\subsection{Example}
|
||||
\begin{lstlisting}
|
||||
Polynomial<double> p=polyfromroots(NRVec<double>({1.,2.,3.})); // (x-1)(x-2)(x-3)
|
||||
Polynomial<double> q({1.,1.}); // 1+x
|
||||
Polynomial<double> a,b;
|
||||
p.polydiv(q,a,b); // p = a*q + b
|
||||
std::cout << (a*q+b-p).norm() << std::endl;
|
||||
NRVec<double> r = p.realroots(1e-10); // 1 2 3
|
||||
Polynomial<double> pp = p.integral(2).derivative(2); // == p
|
||||
NRMat<double> m(3,3); m.randomize(1.);
|
||||
NRMat<double> pm = value(p,m); // p(m)
|
||||
Polynomial<double> h = hermite_polynomial<double>(4);
|
||||
std::cout << value(h,0.5) << ' ' << even_value(h,0.5) << std::endl;
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Rational numbers and continued fractions}\label{sec:polynomial:contfrac}
|
||||
\laheader{contfrac.h, contfrac.cc}
|
||||
|
||||
The classes are instantiated for \code{T = int}, \code{long}, \code{long long}. Integer overflow is not detected.
|
||||
|
||||
\subsection{Rational}
|
||||
\code{template <typename T> class Rational} is a small value class with the public members \code{T num, den}; it is not reference counted.
|
||||
Results of \code{+} and \code{-} of two rationals are reduced and have a positive denominator; other operations cancel common factors only partially.
|
||||
\begin{lareference}
|
||||
\lafunc{Rational(); Rational(const T p, const T q); explicit Rational(const T (&a)[2])}{uninitialized; $p/q$ (not reduced); from \code{{p,q}}.}
|
||||
\lafunc{explicit Rational(const ContFrac<T> &cf)}{value of the (full) continued fraction.}
|
||||
\lafunc{void simplify()}{make the denominator positive and divide by the gcd. All operations keep the denominator positive.}
|
||||
\lafunc{Rational operator-() const}{negation.}
|
||||
\lafunc{Rational & operator+=(const T &rhs); -=, *=, /= (const T&); Rational operator+(const T &rhs) const; -, *, /}{arithmetic with an integer.}
|
||||
\lafunc{Rational & operator*=(const Rational &rhs); /=; Rational operator+(const Rational &rhs) const; -, *, /; +=, -=}{rational arithmetic (\code{+}/\code{-} via the lcm of the denominators).}
|
||||
\lafunc{bool operator==(const Rational &rhs) const; != >= <= > <}{via the sign of the numerator of the difference.}
|
||||
\lafunc{ContFrac<T> operator+(const ContFrac<T> &rhs) const; -, *, /}{mixed arithmetic, the result is a continued fraction.}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const Rational<T> &x); std::istream & operator>>(...)}{format \code{p/q} (input: any single separator character).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{ContFrac}
|
||||
\code{template <typename T> class ContFrac : public NRVec<T>} stores a finite simple continued fraction
|
||||
\[
|
||||
[a_0;a_1,\ldots,a_n]=a_0+\cfrac{1}{a_1+\cfrac{1}{\ddots+\cfrac{1}{a_n}}}
|
||||
\]
|
||||
in vector elements $0\ldots n$; \code{length()} returns $n$ (\code{size()} is private). A zero term $a_i=0$ at a position $i>0$ means $\infty$, i.e.\ the fraction
|
||||
terminates before it. The canonical form has $a_i\ge1$ for $i>0$ and $a_n>1$ for $n>0$; comparisons are guaranteed only for canonical forms. Arithmetic with
|
||||
continued fractions uses Gosper's algorithms \cite{polynomial:hakmem} (cf.\ also \url{https://perl.plover.com/classes/cftalk/TALK}), which produce the terms of the
|
||||
result while consuming the terms of the operands, without forming the rational value. \code{ContFrac} is reference counted (section~\ref{sec:concepts:refcount});
|
||||
its modifying members call \code{copyonwrite()} or \code{resize()}.
|
||||
\begin{lareference}
|
||||
\lafunc{ContFrac(); ContFrac(const int n)}{empty; length $n$ uninitialized (not the value $n$!).}
|
||||
\lafunc{template<int SIZE> ContFrac(const T (&a)[SIZE]); ContFrac(const NRVec<T> &v); explicit ContFrac(const std::list<T> &x)}{from the terms $a_0,a_1,\ldots$}
|
||||
\lafunc{ContFrac(T p, T q); explicit ContFrac(const Rational<T> &r)}{exact expansion of $p/q$ in canonical form (also for negative values: $a_0=\lfloor p/q\rfloor$).}
|
||||
\lafunc{explicit ContFrac(double x, const int n, const T thres=0)}{expansion of a real number to at most $n$ terms; stops earlier when the remainder is zero or
|
||||
(if \code{thres!=0}) when the next term would exceed \code{thres} in absolute value. The result may be non-canonical.}
|
||||
\lafunc{int length() const; void resize(const int n, const bool preserve=true)}{$n$; set the length, new terms are 0 (= termination) when preserving.}
|
||||
\lafunc{void canonicalize()}{bring to the canonical form (absorbs a trailing 1, truncates at the first zero term, handles negative terms).
|
||||
It works directly on the terms without forming $p/q$ (no overflow for fixed-size integer \code{T}), and therefore requires that the terms
|
||||
$a_1,\ldots,a_n$ all have the same sign; with mixed signs the result is wrong (use \code{ContFrac(p,q)} from \code{convergent()} if the values fit into \code{T}).}
|
||||
\lafunc{void convergent(T *p, T *q, const int trunc= -1) const}{numerator and denominator of the convergent $[a_0;a_1,\ldots,a_{\mathrm{trunc}}]$ (all terms for $-1$;
|
||||
\code{trunc} must not exceed \code{length()}).}
|
||||
\lafunc{Rational<T> rational(const int trunc= -1) const; double value(const int trunc= -1) const}{the convergent as a \code{Rational}; as \code{double}.}
|
||||
\lafunc{ContFrac reciprocal() const}{$1/x$ (removes or prepends $a_0=0$).}
|
||||
\lafunc{ContFrac operator-() const}{$-x$ in canonical form, $[-a_0-1;1,a_1-1,a_2,\ldots]$.}
|
||||
\lafunc{ContFrac & operator+=(const T &rhs); -=; ContFrac operator+(const T &rhs) const; -}{shift of $a_0$.}
|
||||
\lafunc{ContFrac operator*(const T &rhs) const; /; ContFrac operator+(const Rational<T> &rhs) const; -, *, /}{via a homographic function (\code{Homographic::value}).}
|
||||
\lafunc{ContFrac operator+(const ContFrac &rhs) const; -, *, /}{via a bihomographic function (\code{BiHomographic::value}).}
|
||||
\lafunc{T compare(const ContFrac &rhs) const}{negative, zero or positive as $x<,=,>$ \code{rhs} (canonical forms).}
|
||||
\lafunc{bool operator==(const ContFrac &rhs) const; != > < >= <=}{via \code{compare}.}
|
||||
\lafunc{iterator begin() const; iterator end() const; iterator beyondend() const}{forward iterator over the terms; \code{end()} points past $a_n$ and
|
||||
\code{beyondend()} one further (used by Gosper's algorithm to feed the final $\infty$).}
|
||||
\end{lareference}
|
||||
Output by the inherited \code{NRVec} operator \code{<<} prints the number of terms and the terms.
|
||||
|
||||
\subsection{Gosper's arithmetics}
|
||||
\code{template <typename T> class Homographic} holds \code{T v[2][2]} $=\{\{a,b\},\{c,d\}\}$ representing $z=(a+bx)/(c+dx)$
|
||||
(first index: numerator/denominator, second: power of $x$). \code{template <typename T> class BiHomographic} holds \code{T v[2][2][2]}
|
||||
$=\{\{\{a,b\},\{c,d\}\},\{\{e,f\},\{g,h\}\}\}$ representing
|
||||
\[
|
||||
z=\frac{a+bx+cy+dxy}{e+fx+gy+hxy}
|
||||
\]
|
||||
(indices: numerator/denominator, power of $y$, power of $x$); e.g.\ $x+y$ is \code{{{{0,1},{1,0}},{{1,0},{0,0}}}}.
|
||||
\begin{lareference}
|
||||
\lafunc{Homographic(); explicit Homographic(const T (&a)[2][2])}{uninitialized; from the coefficients.}
|
||||
\lafunc{ContFrac<T> value(const ContFrac<T> &z) const}{the continued fraction of $h(z)$.}
|
||||
\lafunc{BiHomographic(); explicit BiHomographic(const T (&a)[2][2][2])}{uninitialized; from the coefficients.}
|
||||
\lafunc{ContFrac<T> value(const ContFrac<T> &x, const ContFrac<T> &y) const}{the continued fraction of $z(x,y)$.}
|
||||
\lafunc{input(const T &x, const bool inf) const; inputx, inputy; output(const T &z) const; bool outputready(T &x, bool first) const; int inputselect() const; bool terminate() const}{the
|
||||
elementary steps of Gosper's algorithm (consume a term or $\infty$, emit a term, test whether the next term is determined, choose the operand to read next,
|
||||
test for the end); used by \code{value}.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Example}
|
||||
\begin{lstlisting}
|
||||
ContFrac<int> x(11,101), v(3,7);
|
||||
std::cout << Rational<int>(x+v) << ' ' << Rational<int>(x*v) << std::endl; // 380/707 33/707
|
||||
BiHomographic<int> h({{{12,4},{3,1}},{{0,1},{-1,0}}}); // (x+3)(y+4)/(x-y)
|
||||
std::cout << Rational<int>(h.value(x,v)) << std::endl; // -4867/113
|
||||
ContFrac<long long> pi(3.14159265358979,10,1000000);
|
||||
Rational<long long> r = pi.rational(3); // 355/113
|
||||
\end{lstlisting}
|
||||
|
||||
\subsection{Pitfalls}
|
||||
\begin{itemize}
|
||||
\item \code{Polynomial<T>(n)} and \code{ContFrac<T>(n)} with an integer argument allocate a polynomial of degree $n$ / a fraction of length $n$; they do not
|
||||
represent the number $n$.
|
||||
\item The leading coefficient of a \code{Polynomial} can be zero after arithmetic; call \code{simplify()} before \code{polydiv}, \code{companion}, \code{normalize}.
|
||||
\item \code{Rational} and \code{ContFrac} arithmetics silently overflow for large terms; use \code{long long}.
|
||||
\item Relational operators of \code{ContFrac} are reliable only for canonical forms (e.g.\ after \code{canonicalize()} or from \code{ContFrac(p,q)}).
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,102 @@
|
||||
\chapter{Quaternions}\label{ch:quaternion}
|
||||
\laheader{quaternion.h, quaternion.cc}
|
||||
|
||||
Quaternions; plain data, no reference counting.
|
||||
|
||||
\section{Purpose}
|
||||
\code{Quaternion<T>} implements the quaternion algebra $\mathbb H$, $q=q_0+q_1\mathrm i+q_2\mathrm j+q_3\mathrm k$,
|
||||
and in particular unit quaternions as a parametrization of SO(3) (and SU(2)): conversions to and
|
||||
from rotation matrices, Euler angles and axis--angle, rotation of vectors, derivatives of the
|
||||
rotation matrix with respect to the quaternion components, and the functions $\exp$, $\log$, power.
|
||||
Like \header{vecmat3.h} it can be compiled without the rest of \LA{}: define \code{AVOID_LA}
|
||||
(do not include \header{la.h}, no \code{LA_traits}), and optionally \code{AVOID_STDSTREAM},
|
||||
\code{QUAT_NO_RANDOM}, \code{QUAT_NO_DOUBLE}. The class and functions are in namespace
|
||||
\code{LA_Quaternion} (\emph{not} imported globally, use \code{using namespace LA_Quaternion;}).
|
||||
The member functions and \code{exp}, \code{log}, \code{pow} are instantiated for \code{float}
|
||||
and \code{double} only; the conversion functions taking a generic matrix type \code{M} are
|
||||
templates defined in the header.
|
||||
|
||||
\section{Storage}
|
||||
Plain data \code{T q[4]}, $q_0$ being the real (scalar) part; no reference counting.
|
||||
\code{LA_traits<Quaternion<T>>} declares it plain data. The stream operators are templates in the
|
||||
header, so that e.g.\ \code{Quaternion<NRMat<double>>} (a quadruple of matrices, used for
|
||||
rotation matrix derivatives) can be printed.
|
||||
|
||||
\section{Reference}
|
||||
\begin{lareference}
|
||||
\lafunc{Quaternion(); Quaternion(const T (&a)[4])}{Uninitialized; from a C array (brace initialization \code{Quaternion<double> q({1,2,3,4})}).}
|
||||
\lafunc{Quaternion(const T x, const T u=0, const T v=0, const T w=0)}{$x+u\mathrm i+v\mathrm j+w\mathrm k$; with one argument a real quaternion.}
|
||||
\lafunc{Quaternion(const std::complex<T> &rhs)}{$\mathrm{Re} + \mathrm{Im}\,\mathrm i$.}
|
||||
\lafunc{explicit Quaternion(const T *x, const int shift=1)}{Sets $q_0=0$ and copies $4-\mathrm{shift}$ values to \code{q+shift}; with the default, a pure quaternion from a 3D vector (also works with a \code{Vec3} through its pointer conversion).}
|
||||
\lafunc{void identity(); void clear()}{Set to $1$ / to $0$.}
|
||||
\lafunc{T operator[](const int i) const; T &operator[](const int i); operator T*()}{Element access and pointer to data.}
|
||||
\lafunc{Quaternion &operator=(const T x)}{Real quaternion $x$.}
|
||||
\lafunc{Quaternion &operator+=(const T); Quaternion &operator-=(const T)}{Add/subtract to the real part only (non-mutating \code{+}, \code{-} versions exist).}
|
||||
\lafunc{Quaternion &operator*=(const T); Quaternion &operator/=(const T)}{Scaling (non-mutating \code{*}, \code{/} versions exist).}
|
||||
\lafunc{Quaternion operator-() const; operator+=, operator-=, operator+, operator-}{Algebra with quaternions.}
|
||||
\lafunc{Quaternion operator*(const Quaternion &rhs) const}{Hamilton product.}
|
||||
\lafunc{Quaternion times_vec3(const T *rhs) const; Quaternion vec3_times(const T *rhs) const}{Product $q\,(0,\mathbf r)$ resp.\ $(0,\mathbf r)\,q$ with a pure quaternion given as a 3-vector (saves flops).}
|
||||
\lafunc{Quaternion &conjugateme(); Quaternion conjugate() const}{$\bar q = q_0 - q_1\mathrm i - q_2\mathrm j - q_3\mathrm k$.}
|
||||
\lafunc{T dot(const Quaternion &rhs) const; T normsqr() const; T norm() const}{Euclidean scalar product in $\mathbb R^4$, $|q|^2$, $|q|$.}
|
||||
\lafunc{Quaternion &normalize(T *getnorm=NULL, bool unique_sign=false)}{Normalize to unit norm; optionally return the original norm; with \code{unique_sign} the sign is chosen so that $q_0\ge0$ (fixes the double cover).}
|
||||
\lafunc{Quaternion &fast_normalize(bool unique_sign=false)}{As \code{normalize}, using \code{fast_sqrtinv} for \code{float}.}
|
||||
\lafunc{Quaternion inverse() const; Quaternion operator/(const Quaternion &rhs) const}{$q^{-1}=\bar q/|q|^2$; $q\,\mathrm{rhs}^{-1}$.}
|
||||
\lafunc{void rotate(T *to, const T *from, Quaternion<T> *grad=NULL) const}{Rotates the 3-vector \code{from} by the \emph{normalized} \code{*this}: $(0,\mathbf{to}) = \bar q\,(0,\mathbf{from})\,q$, i.e.\ $\mathbf{to}=A\,\mathbf{from}$ with $A$ from \code{normquat2rotmat(q,A)}. If \code{grad} is given (array of 3 quaternions), \code{grad[i][k]} receives $\partial\,\mathrm{to}_i/\partial q_k$.}
|
||||
\lafunc{Quaternion rotateby(const Quaternion &rhs)}{Returns $\bar r\,q\,r$ for normalized $r$=\code{rhs}; \emph{the real part of} \code{*this} \emph{is ignored} (taken as 0), so this is meant for pure quaternions representing vectors.}
|
||||
\lafunc{Quaternion rotate_match(T *to, const T *from, const T *match) const}{Computes \code{to} as in \code{rotate} and returns the gradient of $\frac12|\mathbf{to}-\mathbf{match}|^2$ with respect to $q$ (for gradient-descent fitting of a rotation).}
|
||||
\lafunc{Quaternion commutator(const Quaternion &rhs) const; Quaternion anticommutator(const Quaternion &rhs) const}{$qr-rq$, $qr+rq$.}
|
||||
\lafunc{T geodesic_distance(const Quaternion &rhs) const}{$\arccos(2(q\cdot r)^2-1)$, the rotation angle between the two (normalized) rotations.}
|
||||
\lafunc{void normquat2euler(T *eul, const char *type) const}{Euler angles of the rotation, implemented as \code{rotmat2euler(eul, normquat2rotmat(q), type)}.}
|
||||
\lafunc{void euler2normquat(const T *eul, const char *type)}{Unit quaternion from Euler angles by explicit formulas, consistent with \code{euler2rotmat} (see the conventions below).}
|
||||
\lafunc{void normquat2eulerzyx(T *eul) const; void eulerzyx2normquat(const T *eul)}{Special pair for yaw--pitch--roll; equivalent to \code{euler2rotmat(...,"xyz",true,true,true)}; \code{eulerzyx2normquat} calls \code{euler2normquat(eul,"zyx")}.}
|
||||
\lafunc{void axis2normquat(const T *axis, const T &angle); void normquat2axis(T *axis, T &angle) const}{$q=(\cos\frac\phi2, \mathbf n\sin\frac\phi2)$ for a unit axis $\mathbf n$, and back (angle in $[0,2\pi]$; undefined for the identity).}
|
||||
\lafunc{void random_rotation()}{Uniformly distributed random unit quaternion (Marsaglia's method \cite{quaternion:marsaglia1972}).}
|
||||
\lafunc{int fprintf(FILE *f, const char *format) const; int sprintf(char *f, const char *format) const; int fscanf(FILE *f, const char *format); int sscanf(const char *f, const char *format)}{C-style I/O with four conversions.}
|
||||
\end{lareference}
|
||||
|
||||
Free functions (namespace \code{LA_Quaternion}; \code{M} is any type with \code{a[i][j]}
|
||||
indexing: C array, \code{Mat3}, \code{NRMat}):
|
||||
\begin{lareference}
|
||||
\lafunc{std::ostream &operator<<(std::ostream &, const Quaternion<T> &); std::istream &operator>>(std::istream &, Quaternion<T> &)}{Four components separated by spaces.}
|
||||
\lafunc{void normquat2su2mat(const Quaternion<T> &q, M &a)}{SU(2) matrix $\begin{pmatrix} q_0+\mathrm i q_1 & q_2+\mathrm i q_3\\ -q_2+\mathrm i q_3 & q_0-\mathrm i q_1\end{pmatrix}$ (elements \code{std::complex<T>}); $\pm q$ give different elements.}
|
||||
\lafunc{void normquat2rotmat(const Quaternion<T> &q, M &a, bool transpose=false)}{Rotation matrix of a unit quaternion, $a_{00}=q_0^2+q_1^2-q_2^2-q_3^2$, $a_{01}=2(q_1q_2+q_0q_3)$, $a_{10}=2(q_1q_2-q_0q_3)$, \dots{} ($\pm q$ give the same matrix). \code{transpose} should give $a\Tr$ (``to match the NASA paper'').}
|
||||
\lafunc{void quat2rotmat(Quaternion<T> q, M &a, bool transpose=false, const bool already_normalized=false)}{Normalizes a copy of $q$ first unless \code{already_normalized}.}
|
||||
\lafunc{void normquat2rotmatder(const Quaternion<T> &q, Quaternion<M> &a, bool transpose=false)}{\code{a[k]} $=\partial A/\partial q_k$, $k=0..3$, for $A$ from \code{normquat2rotmat} (the 3$\times$3 matrices must be preallocated if \code{M} is \code{NRMat}; \code{M} must have \code{transposeme()} if \code{transpose} is used).}
|
||||
\lafunc{void rotmat2normquat(const M &a, Quaternion<T> &q, bool transpose=false)}{Unit quaternion from a rotation matrix (method of Sarabandi and Thomas, branch on the sign of the trace); inverse of \code{normquat2rotmat}, convention compatible with Madgwick's MEMS paper.}
|
||||
\lafunc{Quaternion<T> exp(const Quaternion<T> &x)}{$e^{x_0}(\cos|\mathbf v|, \hat{\mathbf v}\sin|\mathbf v|)$, $\mathbf v=(x_1,x_2,x_3)$.}
|
||||
\lafunc{Quaternion<T> log(const Quaternion<T> &x)}{$(\ln|x|, \hat{\mathbf v}\arccos(x_0/|x|))$ -- principal branch; $\log(\exp(x))\neq x$ in general and $\log(xy)\neq\log x+\log y$.}
|
||||
\lafunc{Quaternion<T> pow(const Quaternion<T> &x, const T &y)}{$x^y = |x|^y(\cos y\varphi, \hat{\mathbf v}\sin y\varphi)$, $\varphi=\arccos(x_0/|x|)$.
|
||||
For real quaternions ($\mathbf v=0$) the limits are used; for a negative real $x_0$ (where $\hat{\mathbf v}$ is undefined) \code{log} and \code{pow}
|
||||
choose the $i$ axis as for complex numbers: $\log x=(\ln|x_0|,\pi,0,0)$, $x^y=|x_0|^y(\cos\pi y,\sin\pi y,0,0)$.}
|
||||
\end{lareference}
|
||||
|
||||
\paragraph{Conventions.} Note the passive convention: $A$=\code{normquat2rotmat(q)} satisfies
|
||||
$(0,A\mathbf v)=\bar q\,(0,\mathbf v)\,q$; it is the transpose of the ``textbook'' active matrix
|
||||
$q\,\mathbf v\,\bar q$. For all 12 Euler sequences \code{euler2normquat(e,type)} is consistent with
|
||||
\code{euler2rotmat}: \code{normquat2rotmat(q)} $=$ \code{euler2rotmat(e,type)}, and \code{normquat2euler} (defined via
|
||||
\code{rotmat2euler(normquat2rotmat(q))}) is its inverse, i.e.\ \code{euler2normquat(normquat2euler(q,type),type)} $=\pm q$
|
||||
(the same rotation). \code{normquat2eulerzyx}/\code{eulerzyx2normquat} are the yaw--pitch--roll pair for \code{"zyx"}.
|
||||
|
||||
\section{Example}
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
using namespace LA;
|
||||
using namespace LA_Quaternion;
|
||||
|
||||
Quaternion<double> q(1.,2.,3.,4.);
|
||||
q.normalize(NULL,true); //unit quaternion with q[0]>=0
|
||||
Mat3<double> a;
|
||||
normquat2rotmat(q,a);
|
||||
Vec3<double> v(1.,-2.,.5), w;
|
||||
q.rotate(w,v); //w == a*v
|
||||
Quaternion<double> back;
|
||||
rotmat2normquat(a,back); //back == q
|
||||
Vec3<double> axis={0,1/sqrt(2),1/sqrt(2)};
|
||||
Quaternion<double> r;
|
||||
r.axis2normquat(axis,0.5);
|
||||
Quaternion<double> rv = Quaternion<double>(v).rotateby(r);
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}
|
||||
\lawarning{Most functions assume a \emph{normalized} quaternion (names \code{normquat...}) and do not check it. Use \code{quat2rotmat} or call \code{normalize()} first.}
|
||||
\lawarning{\code{Vec3::operator*(const Vec3&)} is the cross product, not the dot product; \code{Vec3*Mat3} is a row vector times a matrix.}
|
||||
@@ -0,0 +1,174 @@
|
||||
\chapter{Regression}\label{ch:regression}
|
||||
|
||||
This chapter describes the kernel regression module: the class \code{REG} for kernel
|
||||
regression of a scalar function of points in $\mathbb R^d$, and the class \code{REGSURF} for
|
||||
regression of (potential energy and other) surfaces of molecular geometries, including the
|
||||
computation of absorption spectra by the machine-learning nuclear ensemble approach (ML-NEA).
|
||||
|
||||
\paragraph{Authorship and status.} The files \header{reg.h}, \header{reg.cc},
|
||||
\header{regsurf.h}, \header{regsurf.cc} and the test programs \header{test_reg.cc},
|
||||
\header{test_regsurf.cc} state ``Kernel ridge regression module Copyright (C) 2024 Pavel Florian
|
||||
\texttt{<florian43@seznam.cz>} and Jiri Pittner'', under the same GNU GPL version~3 (or later)
|
||||
licence as the rest of \LA{}. The module is written in a different style from the rest of the
|
||||
library: it works mostly with raw pointers into internally allocated \code{NRVec}/\code{NRMat}
|
||||
objects, returns \code{unsigned int} status codes (0 = success) in addition to calling
|
||||
\code{laerror}, and reads its input from text files. The headers are \emph{not} included by
|
||||
\header{la.h}; include \header{reg.h} or \header{regsurf.h} explicitly. Note that both headers
|
||||
include \code{<bits/stdc++.h>} (GCC specific) and contain \code{using namespace std;} at global
|
||||
scope. The classes are instantiated for \code{T=double} only.
|
||||
|
||||
The descriptions below say what the code does.
|
||||
\lawarning{The regression module is experimental and has not yet been tested much in practice: a number of
|
||||
errors were found and fixed only in October 2026. The comments in the code mention an elastic-net loss, which
|
||||
the fitting algorithms do not implement; the descriptions below document what the code actually does.}
|
||||
|
||||
%=====================================================================
|
||||
\section{Kernel functions}\label{sec:regression:kernels}
|
||||
\laheader{reg.h, reg.cc}
|
||||
All kernels take a difference vector \code{x} of length \code{dimensions} and a width $\sigma$.
|
||||
\begin{lareference}
|
||||
\lafunc{T Gaussian_kernel(const T *x, const unsigned int dimensions, const T sigma)}{$\exp(-|\mathbf x|^2/2\sigma^2)$.}
|
||||
\lafunc{T Laplacian_kernel(const T *x, const unsigned int dimensions, const T sigma)}{$\exp(-\sum_i|x_i|/\sigma)$ (1-norm).}
|
||||
\lafunc{T Exponential_kernel(const T *x, const unsigned int dimensions, const T sigma)}{$\exp(-|\mathbf x|/\sigma)$ (2-norm).}
|
||||
\lafunc{T Matern_kernel(const T *x, const unsigned int dimensions, const T sigma, const unsigned int n, const T *Matern_comb_number)}{Mat\'ern kernel of half-integer order $\nu=n+\frac12$: $e^{-r/\sigma}\sum_{k=0}^{n} c_k\,(2r/\sigma)^{n-k}$, $r=|\mathbf x|$, i.e.\ the usual form with length scale $l=\sigma\sqrt{2\nu}$ ($n=0$ exponential, $n=1$: $(1+r/\sigma)e^{-r/\sigma}$); evaluated by the Horner scheme, $k(0)=1$. \code{Matern_comb_number} must have $n+1$ elements.}
|
||||
\lafunc{unsigned int Matern_coefficient(const unsigned int n, T *comb_number)}{Fills $c_k=\frac{(n+k)!}{(2n)!}\binom nk$, $k=0..n$ ($n+1$ elements), by the recurrence $c_n=1$, $c_{k-1}=c_k\,k/((n+k)(n-k+1))$ (no factorials, no overflow for large $n$).}
|
||||
\end{lareference}
|
||||
The kernel is selected by name: \code{"Gaussian"}, \code{"Laplacian"}, \code{"Exponential"},
|
||||
\code{"Matern"}; for Mat\'ern the order $n$ is an additional parameter (rounded to an integer).
|
||||
|
||||
%=====================================================================
|
||||
\section{REG: kernel regression in $\mathbb R^d$}\label{sec:regression:reg}
|
||||
\laheader{reg.h, reg.cc}
|
||||
|
||||
\subsection{Model and parameters}
|
||||
The training set consists of $N$ points $\mathbf x_i\in\mathbb R^d$ (array of $N\cdot d$ values,
|
||||
point after point) with values $y_i$. The model is a weighted sum of kernels centred at the
|
||||
training points. The parameter array is $(\lambda, \alpha, \sigma[, n])$: regularization strength
|
||||
$\lambda$, mixing $\alpha\in[0,1]$ (clamped) between the ``kernel ridge'' part $\kappa=\lambda(1-\alpha)/2$
|
||||
and the ``lasso'' part $\lambda\alpha$, kernel width $\sigma$ and, for the Mat\'ern kernel, the order $n$.
|
||||
As implemented, the prediction for a point $\mathbf x$ is
|
||||
\[
|
||||
\hat y(\mathbf x) = \Bigl(\sum_j w_j k(\mathbf x-\mathbf x_j)\Bigr)\,\frac{S+\kappa}{S} + \lambda\alpha,
|
||||
\qquad S=\sum_j k(\mathbf x-\mathbf x_j),
|
||||
\]
|
||||
(the factor is omitted if $\kappa=0$ or $S=0$).
|
||||
|
||||
\subsection{Construction}
|
||||
\begin{lareference}
|
||||
\lafunc{REG(const T *x, const T *y, const T *params, const unsigned int data_size, const unsigned int x_dimensions, const string kernel)}{Model from memory (data are copied).}
|
||||
\lafunc{REG(const string x_file, const string y_file, const string param_file)}{Model from text files, see below.}
|
||||
\lafunc{void Init(const T *x, const T *y, const T *params, const unsigned int data_size, const unsigned int x_dimensions, const string kernel)}{Called by the constructors; allocates the working storage. With an unknown kernel name it returns silently and the object stays uninitialized (later calls fail with laerror).}
|
||||
\lafunc{~REG()}{Frees the internal buffers.}
|
||||
\end{lareference}
|
||||
File formats (one number per line): \code{param_file} contains $N$, $d$, the kernel name,
|
||||
$\lambda$, $\alpha$, $\sigma$ and for \code{"Matern"} $n$; \code{x_file} contains $N\cdot d$
|
||||
coordinates, \code{y_file} $N$ values. If a file cannot be read the constructor returns silently
|
||||
without initializing the object.
|
||||
|
||||
\subsection{Fitting and prediction}
|
||||
\begin{lareference}
|
||||
\lafunc{unsigned int REG_Fit(const T max_Loss, const unsigned int max_learning_cycles)}{Iterative fit. Builds the kernel matrix $K_{ij}=k(\mathbf x_i-\mathbf x_j)$ (unit diagonal) and its row sums $s_i$ on first use, starts from $w=y$ and repeats at most \code{max_learning_cycles} times the multiplicative update $p=Kw$, $p_i\leftarrow p_i(s_i+\kappa)/s_i+\lambda\alpha$, $w_i\leftarrow w_i y_i/p_i$. If \code{max_Loss}$>0$, stops when \code{Loss_Function()}$<$\code{max_Loss}.}
|
||||
\lafunc{unsigned int REG_Fit_grad(const T max_Loss, const unsigned int max_learning_cycles, const T learning_rate)}{Gradient-type variant: with $p$ as above (same $\kappa$), $g_j=\sum_k(p_k-y_k)K_{jk}/\sqrt N$ and $w_j\leftarrow w_j/(1-\eta g_j)$, $\eta$=\code{learning_rate}.}
|
||||
\lafunc{T REG_RMSE()}{Root mean square deviation of the predictions stored during the last fitting cycle from the training values.}
|
||||
\lafunc{T Loss_Function()}{Currently identical to \code{REG_RMSE()} (the regularization terms mentioned in the comment are not added).}
|
||||
\lafunc{unsigned int REG_Get_predictions(const T *x, T *y_preds, const unsigned int data_size)}{Predictions for \code{data_size} new points (array of \code{data_size}$\cdot d$ values).}
|
||||
\lafunc{unsigned int REG_Get_Score(const T *x, T *y_preds, const unsigned int data_size, const T *y, T *score)}{Predictions and \code{score[0]}$=\sum_i(y_i-\hat y_i)^2$ (sum of squares, not RMSE).}
|
||||
\lafunc{unsigned int REG_Save_fitted(const string model_file); unsigned int REG_Load_fitted(const string model_file)}{Write/read the $N$ weights, one per line (the model itself must be constructed with the same data and parameters).}
|
||||
\lafunc{unsigned int REG_Set_params(const T *params, const string kernel); unsigned int REG_Get_params(T *params, string *kernel)}{Set/get $(\lambda,\alpha,\sigma[,n])$ and the kernel name. If the kernel, $\sigma$ or $n$ changes, the kernel matrix is rebuilt in the next fit (a change of $\lambda$, $\alpha$ only needs no rebuild).}
|
||||
\lafunc{unsigned int REG_Get_weight_coeff(T *weight)}{Copy the $N$ weights.}
|
||||
\lafunc{unsigned int Build_REG_distances_mat(); unsigned int Build_REG_kernels_mat(); unsigned int Build_REG_kernels_mat_sums(); T Kernel(const T *x, const unsigned int dimensions, const unsigned int kernel_id)}{Internal steps, public in the class: difference vectors, kernel matrix, its row sums, kernel by numeric id (0 Gaussian, 1 Laplacian, 2 Exponential, 3 Mat\'ern).}
|
||||
\end{lareference}
|
||||
|
||||
|
||||
\subsection{Example}
|
||||
Abridged from \header{test_reg.cc}:
|
||||
\begin{lstlisting}
|
||||
#include "reg.h"
|
||||
using namespace LA;
|
||||
|
||||
REG<double> reg("reg_test_x.csv", "reg_test_y.csv", "reg_test_p.csv");
|
||||
reg.REG_Fit(0.0, 20); //20 cycles, no loss threshold
|
||||
double x=0.5, y;
|
||||
reg.REG_Get_predictions(&x, &y, 1);
|
||||
reg.REG_Save_fitted("reg_test_m.csv");
|
||||
\end{lstlisting}
|
||||
|
||||
%=====================================================================
|
||||
\section{REGSURF: regression of molecular surfaces and ML-NEA spectra}\label{sec:regression:regsurf}
|
||||
\laheader{regsurf.h, regsurf.cc}
|
||||
|
||||
\subsection{Model}
|
||||
A \code{REGSURF} model describes \code{count_levels} surfaces (e.g.\ the ground and excited state
|
||||
energies) as functions of the geometry of a system of \code{count_kernels} particles (atoms) in
|
||||
\code{count_dimensions} dimensions, trained on \code{count_geometries} geometries. For each
|
||||
geometry the matrix of interparticle distances $r_{jk}$ is converted to a \emph{descriptor}
|
||||
matrix $D_{jk}$:
|
||||
\begin{itemize}
|
||||
\item \code{"RE"}: $D_{jk}=r^{(0)}_{jk}/r_{jk}$, with the first training geometry as reference, zero diagonal;
|
||||
\item \code{"Coulomb_matrix"}: $D_{jk}=Z_jZ_k/r_{jk}$, $D_{jj}=Z_j^{2.4}$ (needs the nuclear charges $Z$);
|
||||
\item \code{"Inverse_pairs"}: $D_{jk}=1/r_{jk}$, zero diagonal;
|
||||
\item a user function set by \code{s_Set_extern_descriptor}.
|
||||
\end{itemize}
|
||||
For geometry $g$ the kernel matrix is $K^{(g)}_{jk}=k(D^{(g)}_{jk}-D^{(0)}_{jk})$ (one-dimensional
|
||||
kernel of the descriptor difference with respect to the first geometry) and its row sums
|
||||
$s^{(g)}_j$ enter the prediction for level $\ell$, which is essentially $\sum_j w^{(\ell)}_j s^{(g)}_j$
|
||||
with one weight per particle and level, corrected by a kernel ridge factor and the additive
|
||||
$\lambda_\ell\alpha_\ell$ as in \code{REG}. The parameter array is
|
||||
$(\lambda_0..\lambda_{L-1}, \alpha_0..\alpha_{L-1}, \sigma[, n])$, $L$=\code{count_levels}.
|
||||
|
||||
\subsection{Construction and data files}
|
||||
\begin{lareference}
|
||||
\lafunc{REGSURF(const unsigned int count_dimensions, const unsigned int count_kernels, const unsigned int count_geometries, const unsigned int count_levels, const string descriptor, const string kernel, const T *params, const T *geometries, const T *values, const unsigned int *Z = nullptr)}{Model from memory. \code{geometries}: $G\cdot K\cdot d$ coordinates (geometry, particle, coordinate); \code{values}: $L\cdot G$ values, level-major (all geometries of level 0 first); \code{Z}: $K$ charges (needed for \code{"Coulomb_matrix"}, otherwise \code{laerror}; may be omitted for the other descriptors).}
|
||||
\lafunc{REGSURF(const string geometries_file, const string energies_file, const string parameters_file, const string Z_file = "")}{Model from text files, see below.}
|
||||
\lafunc{void Init(...)}{Same arguments as the memory constructor; called by both constructors. Unknown kernel: laerror.}
|
||||
\lafunc{~REGSURF()}{Frees the internal buffers.}
|
||||
\end{lareference}
|
||||
File formats: the parameters file contains, one per line, $d$, $K$, $G$, $L$, the descriptor
|
||||
name, the kernel name, $\lambda_0..\lambda_{L-1}$, $\alpha_0..\alpha_{L-1}$, $\sigma$ and for
|
||||
\code{"Matern"} $n$. The geometries file is free format: all numbers containing a decimal point
|
||||
(or comma) are read in order, other tokens (atom counts, element symbols, comments) are skipped,
|
||||
so concatenated XYZ files can be used. The energies file has one value per line in level-major
|
||||
order, the $Z$ file one integer per line.
|
||||
|
||||
\subsection{Fitting, prediction and parameters}
|
||||
\begin{lareference}
|
||||
\lafunc{unsigned int s_Fit(const T max_Loss, const unsigned int max_learning_cycles, const T learning_rate, const bool element_invariant=true, unsigned int *kernel_classes=nullptr)}{Fits the weights of all levels: builds distances, descriptors, kernel matrices and row sums on first use, initializes the weights of level $i$ to the constant $(y_{i,0}-\lambda_i\alpha_i)/(K^2+\kappa_i)$ ($y_{i,0}$ value at the base geometry 0, $\kappa_i=\lambda_i(1-\alpha_i)K/2$) and performs at most \code{max_learning_cycles} sweeps of gradient-like updates over geometries $1..G-1$ with step \code{learning_rate}; stops a level early if \code{max_Loss}$>0$ and its loss is below \code{max_Loss}. With \code{element_invariant}, the update of each weight is replaced by the mean over the particles of the same class (\code{kernel_classes}, default the charges $Z$; without both no averaging is done).}
|
||||
\lafunc{T s_RMSE(unsigned int level); T Loss_Function(unsigned int level)}{RMSE of the predictions on the training geometries for one level; \code{Loss_Function} currently returns the same.}
|
||||
\lafunc{unsigned int s_Get_predictions(const unsigned int count_geometries, const unsigned int count_levels, const T *geom, const unsigned int *surfaces, T *predictions)}{Predictions for new geometries \code{geom} and the levels listed in \code{surfaces[0..count_levels-1]}; \code{predictions[i*count_levels+j]} is geometry $i$, level \code{surfaces[j]}.}
|
||||
\lafunc{unsigned int s_Score(const unsigned int count_geometries, const unsigned int count_levels, const T *geom, const unsigned int *surfaces, T *predictions, const T *y_values, T *score)}{Predictions and their RMSE against \code{y_values} in \code{score[0]}.}
|
||||
\lafunc{unsigned int s_Save_fitted(const string weights); unsigned int s_Load_fitted(const string weights)}{Write/read the $L\cdot K$ weights, one per line.}
|
||||
\lafunc{unsigned int s_Set_params(const string descriptor, const string kernel, const T *params); unsigned int s_Get_params(string *descriptor, string *kernel, T *params)}{Change/query descriptor, kernel and parameter array; a change of descriptor, kernel, $\sigma$ or $n$ invalidates the precomputed matrices (rebuilt in the next fit; $\lambda$, $\alpha$ need no rebuild). Unknown descriptor or kernel names raise \code{laerror} (also in the constructors). The descriptor is always set from its name, so a descriptor installed by \code{s_Set_extern_descriptor} must be installed again after \code{s_Set_params}.}
|
||||
\lafunc{unsigned int s_Set_extern_descriptor(unsigned int (*extern_func)(const unsigned int, const unsigned int, const T*, T*))}{Use a user descriptor function \code{f(count_particles, count_geometries, distances, desc)} which maps the $K\times K$ distance matrices of all geometries (consecutive) to descriptor matrices of the same layout.}
|
||||
\lafunc{unsigned int s_Get_weight_coeff(const unsigned int s_number, T *weights)}{Copy the $K$ weights of level \code{s_number}.}
|
||||
\lafunc{T Kernel(const T *x, const unsigned int dimensions, const unsigned int kernel_id); unsigned int Build_s_distances_mat(...); unsigned int Build_s_descriptor_mat(...); unsigned int Build_s_kernels_mat(...); unsigned int Build_s_kernels_mat_sums()}{Internal steps, public in the class.}
|
||||
\end{lareference}
|
||||
Free functions \code{Build_s_RE_descriptor_mat}, \code{Build_s_coulomb_}\allowbreak\code{matrix_descriptor_mat},
|
||||
\code{Build_s_inverse_}\allowbreak\code{pairs_descriptor_mat} (\code{count_particles, count_geometries, distances, desc}
|
||||
and the reference distances resp.\ $Z$) implement the built-in descriptors.
|
||||
|
||||
\subsection{ML-NEA absorption spectra}
|
||||
\begin{lareference}
|
||||
\lafunc{unsigned int s_Load_ML_NEA_geometries(const unsigned int count_geometries, const T *geometries); unsigned int s_Load_ML_NEA_geometries(const unsigned int count_geometries, const string geometries_file)}{Load an ensemble of geometries (e.g.\ sampled from a ground-state distribution) from memory or from a free-format geometry file as above.}
|
||||
\lafunc{unsigned int s_Predict_ML_NEA_geometries()}{Predict all levels for all ensemble geometries.}
|
||||
\lafunc{unsigned int s_Compute_ML_NEA_spectra(const T E_min, const T step, const unsigned int count_steps, const T delta)}{Spectrum on the grid $E_k=E_{\min}+k\cdot\mathrm{step}$, $k<\mathrm{count\_steps}$, as a sum over geometries and levels of Gaussians of width $\delta$ centred at the predicted excitation energies $\Delta E$ (energies assumed in atomic units), weighted by $\Delta E\,f/E$. The Gaussians are normalized, $\sqrt{2/\pi}\,\delta^{-1}\exp(-2(E-\Delta E)^2/\delta^2)$ (standard deviation $\delta/2$), and only grid points within $20\delta$ of $\Delta E$ are evaluated (beyond that the exponential underflows to zero). The oscillator strength $f=\Delta E_{\min}/\Delta E$ ($\Delta E_{\min}$ the minimum of the level over the ensemble) is only a placeholder for the true oscillator strength, which the model does not predict.}
|
||||
\lafunc{unsigned int s_Save_ML_NEA_spectra(const string ML_NEA_spectra_file)}{Write the spectrum normalized to unit sum, one value per line.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Example}
|
||||
Abridged from \header{test_regsurf.cc} (10 levels):
|
||||
\begin{lstlisting}
|
||||
#include "regsurf.h"
|
||||
using namespace LA;
|
||||
|
||||
REGSURF<double> regsurf("xyz.dat", "E_sum.dat", "conf_surf.dat", "Z.dat");
|
||||
regsurf.s_Fit(0.0, 100, 0.5);
|
||||
for(unsigned int i=0; i<10; ++i) cout << regsurf.s_RMSE(i) << endl;
|
||||
regsurf.s_Load_ML_NEA_geometries(50000, "nea_geoms.xyz");
|
||||
regsurf.s_Predict_ML_NEA_geometries();
|
||||
regsurf.s_Compute_ML_NEA_spectra(0.00, 0.0025, 4000, 0.2);
|
||||
regsurf.s_Save_ML_NEA_spectra("spectra.dat");
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}\label{sec:regression:pitfalls}
|
||||
\lawarning{\code{REG} and \code{REGSURF} hold raw pointers to their own buffers and have no user-defined copy constructor or assignment: copying an object leads to double deletion. Pass them by reference.}
|
||||
\lawarning{Errors in the input files are partly reported by \code{laerror} and partly lead to a silently uninitialized object (\code{REG}); check the return codes and the results.}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
\chapter{Packed symmetric and hermitian matrices: NRSMat}\label{ch:smat}
|
||||
\laheader{smat.h, smat.cc}
|
||||
|
||||
\section{Purpose}\label{sec:smat:purpose}
|
||||
\code{NRSMat<T>} stores a square symmetric (real \code{T}) or hermitian (complex \code{T})
|
||||
matrix of order $n$ in packed form, i.e.\ only $n(n+1)/2$ elements of one triangle are kept.
|
||||
It halves the memory compared to \code{NRMat<T>} and gives access to the packed BLAS/LAPACK
|
||||
routines (\code{dspmv}, \code{zhpmv}, \code{dspr2}, \code{zhpr2}, \code{dspev}, \ldots).
|
||||
Typical uses are Hamiltonians, overlap and density matrices, Hessians (see \ref{ch:optimize})
|
||||
and as input for \code{diagonalize()} (see \ref{ch:nonclass}).
|
||||
Results of operations which are not symmetric in general (e.g.\ the product of two symmetric
|
||||
matrices) are returned as \code{NRMat<T>}.
|
||||
|
||||
\code{NRSMat_from1<T>} (section~\ref{sec:smat:from1}) is the same class with the
|
||||
two-index \code{operator()} counting from 1.
|
||||
|
||||
The file \code{smat.cc} contains explicit instantiations for the element types \code{double},
|
||||
\code{std::complex<double>} and the integer types \code{char}, \code{short}, \code{int}, \code{long}, \code{long long} and
|
||||
their unsigned variants (\code{float} is not instantiated).
|
||||
Most numerical members have BLAS-based specializations only for the two types \code{double} and
|
||||
\code{std::complex<double>}. Some members are defined only for these two types:
|
||||
\code{amax}, \code{amin}, \code{norm}, \code{dot}, \code{axpy}, \code{randomize}, \code{inverse}
|
||||
and the products with \code{NRMat} and \code{NRSMat}.
|
||||
|
||||
\section{Storage and concepts}\label{sec:smat:storage}
|
||||
|
||||
\subsection{Packed lower triangle}
|
||||
The elements $A_{ij}$ with $i\ge j$ (lower triangle) are stored row by row, i.e.\ the
|
||||
row-major packed lower triangle, which is the same memory layout as the column-major packed
|
||||
upper triangle (Fortran \code{UPLO='U'}):
|
||||
\begin{equation}\label{eq:smat:index}
|
||||
A_{ij} \;\to\; \mathtt{v}[k],\qquad k = \frac{i(i+1)}{2}+j \quad (0\le j\le i<n),
|
||||
\end{equation}
|
||||
so the order is $A_{00}, A_{10}, A_{11}, A_{20}, A_{21}, A_{22}, \ldots$
|
||||
\code{size()} returns the length of the packed data $n(n+1)/2$, \emph{not} $n$; \code{nrows()} and
|
||||
\code{ncols()} both return $n$.
|
||||
The packed data can be accessed linearly by \code{operator[]} and by the conversion to a raw pointer.
|
||||
The data of an \code{NRSMat<T>} can be viewed as an \code{NRVec<T>} of length $n(n+1)/2$ and
|
||||
vice versa without copying (see section~\ref{sec:smat:construct}).
|
||||
|
||||
\code{smat.h} defines the macro \code{NN2} (\code{(size_t)nn*(nn+1)/2}) which it uses internally and
|
||||
does not undefine; avoid using this name in your own code.
|
||||
|
||||
\subsection{Index functions}\label{sec:smat:indexfunc}
|
||||
The following inline function templates (namespace \code{LA}, template parameter = integer type of the
|
||||
indices) compute the packed index and return \code{size_t}. They are used by the class itself and
|
||||
are useful for code working directly with packed data (e.g.\ two-electron integrals in \ref{ch:fourindex}).
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{size_t SMat_index(T i, T j)}{Index (\ref{eq:smat:index}) for $0\le i,j<n$, either order: $(i,j)$ and $(j,i)$ give the same result.}
|
||||
\lafunc{size_t SMat_index_igej(T i, T j); size_t SMat_index_ilej(T i, T j)}{As \code{SMat_index}, faster, assuming $i\ge j$ resp.\ $i\le j$ (not checked).}
|
||||
\lafunc{size_t SMat_index_1(T i, T j)}{Index for indices counting from 1 ($1\le i,j\le n$): $i(i-1)/2+j-1$ for $i\ge j$; the resulting packed index counts from 0.}
|
||||
\lafunc{size_t SMat_index_1igej(T i, T j); size_t SMat_index_1ilej(T i, T j)}{As \code{SMat_index_1}, assuming $i\ge j$ resp.\ $i\le j$.}
|
||||
\lafunc{size_t ASMat_index(T i, T j)}{Packed index of the strictly lower triangle (antisymmetric matrices, used by \ref{ch:fourindex}): $i(i-1)/2+j$ for $i>j$, symmetric in $i,j$; returns \code{(size_t)-1} for $i=j$.}
|
||||
\lafunc{size_t ASMat_index_1(T i, T j)}{The same for indices counting from 1: $(i-2)(i-1)/2+j-1$ for $i>j$; \code{(size_t)-1} for $i=j$. Computed in the arithmetic of \code{T} (no cast to \code{size_t}), so it may overflow for large \code{int} indices.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Hermitian interpretation of complex matrices}\label{sec:smat:hermitian}
|
||||
For complex \code{T} an \code{NRSMat} is a \emph{hermitian} matrix: the lower triangle is stored and the
|
||||
upper triangle is its complex conjugate, $A_{ij}=A_{ji}^*$ for $i<j$. For real \code{T} this is just the
|
||||
symmetric matrix. The arithmetic of the library follows this convention:
|
||||
\begin{itemize}
|
||||
\item products: \code{NRSMat*NRVec}, \code{NRSMat*NRMat}, \code{NRMat*NRSMat}, \code{NRVec*NRSMat} ($x\Tr H$),
|
||||
\code{NRSMat*NRSMat}, \code{NRVec::gemv} (\code{zhpmv}, \code{trans}='t' gives $H\Tr x = H^* x$),
|
||||
\code{rank2update} (\code{zhpr2}), \code{diagonalize()}, \code{linear_solve()}, \code{trace2()} and the other
|
||||
functions of chapter~\ref{ch:nonclass};
|
||||
\item conversions: \code{NRMat<T>(const NRSMat<T>&)} conjugates the upper triangle and
|
||||
\code{NRSMat(const NRMat<T>&)} takes the hermitian part $(A+A\Hc)/2$;
|
||||
\item \code{fprintf()}, \code{sum()} and \code{NRMat<T>::operator+=/-=} (also \code{+}, \code{-})
|
||||
with an \code{NRSMat} operand use the full hermitian matrix; \code{operator>>} and \code{fscanf} read the full square matrix, the elements
|
||||
of the lower triangle (read later) are the ones stored;
|
||||
\item \code{operator<<}, \code{permuted()}, \code{submatrix(const NRVec<int>&)}, \code{rsum()} and \code{csum()} are built on
|
||||
\code{operator()} and therefore use the stored element also for $i<j$ (no conjugation);
|
||||
\item \code{randomize()} produces a hermitian matrix (real diagonal); \code{conjugateme()}/\code{conjugate()}
|
||||
conjugate all stored elements, which is the transposition of a hermitian matrix.
|
||||
\end{itemize}
|
||||
\lawarning{Element access does \emph{not} follow the hermitian convention: \code{operator()(i,j)} (both the const
|
||||
and the non-const version, also of \code{NRSMat_from1}) and \code{operator[]} just access the storage. $(i,j)$ and $(j,i)$
|
||||
refer to the same stored element $A_{\max(i,j),\min(i,j)}$, and \emph{no} complex conjugation is performed for $i<j$:
|
||||
\code{s(0,1)} returns $A_{10}$, not $A_{01}=A_{10}^*$. Code reading the upper triangle of a complex hermitian matrix must
|
||||
conjugate itself, e.g.\ \code{std::conj(s(j,i))} for $i<j$, and must write only the elements with $i\ge j$.}
|
||||
|
||||
\textbf{Planned extension.} \code{NRSMat} might in the future be extended by a \code{bool} member \code{hermitian}
|
||||
which would toggle the behaviour for complex types between the hermitian and the complex symmetric interpretation
|
||||
(element access, conversions, products, \code{operator<<}, \code{permuted()}, \code{submatrix()}, \code{rsum()}, \code{csum()}).
|
||||
The conversion \code{Tensor(const NRSMat&)} (chapter~\ref{ch:tensor}), which now marks complex matrices as hermitian (symmetry 2),
|
||||
would then have to respect the flag.
|
||||
|
||||
\subsection{Reference counting}
|
||||
\code{NRSMat} follows the copy-on-demand scheme of section~\ref{sec:concepts:refcount}: the copy
|
||||
constructor and \code{operator=} share the data, \code{operator|=} copies deeply, all modifying members
|
||||
call \code{copyonwrite()} (I found no member modifying data without it, except the broken constructor
|
||||
from a raw pointer, see below), \code{resize()} detaches, and in DEBUG builds the non-const
|
||||
\code{operator[]} and \code{operator()} raise an error if the data are shared.
|
||||
Note that the constructor \code{NRSMat(const NRVec<T>&, int)} and \code{NRVec<T>(const NRSMat<T>&)}
|
||||
also share the data (the reference count is common to the vector and the matrix).
|
||||
|
||||
GPU support (\code{CUDALA}) is experimental, see section~\ref{sec:concepts:gpu}; many members
|
||||
are CPU only (\code{NOT_GPU}).
|
||||
|
||||
\section{Construction, assignment, conversion}\label{sec:smat:construct}
|
||||
\begin{lareference}
|
||||
\lafunc{NRSMat()}{Empty matrix (no data, no reference count).}
|
||||
\lafunc{explicit NRSMat(const int n, const GPUID loc = undefined)}{Order $n$, elements uninitialized.}
|
||||
\lafunc{NRSMat(const T &a, const int n)}{Order $n$, all $n(n+1)/2$ stored elements (i.e.\ also the off-diagonal ones) set to \code{a}.}
|
||||
\lafunc{NRSMat(const T *a, const int n)}{Copy of $n(n+1)/2$ packed elements (lower triangle by rows) from \code{a}.}
|
||||
\lafunc{NRSMat(const NRSMat &rhs)}{Shallow copy.}
|
||||
\lafunc{NRSMat(const typename LA_traits_complex<T>::NRSMat_Noncomplex_type &rhs, bool imagpart = false)}{Complex matrix from a real one: \code{rhs} becomes the real part (\code{imagpart=false}) or the imaginary part (\code{imagpart=true}), the other part is zero. Defined only for \code{T=std::complex<double>}.}
|
||||
\lafunc{explicit NRSMat(const NRMat<T> &rhs)}{Symmetric part $(A+A\Tr)/2$ of a square dense matrix (deep copy; nonsquare input is checked only in DEBUG).}
|
||||
\lafunc{explicit NRSMat(const NRVec<T> &rhs, const int n)}{Interprets a vector of length $n(n+1)/2$ as packed matrix of order $n$. \emph{Shares} the data with \code{rhs} (no copy); length checked only in DEBUG.}
|
||||
\lafunc{explicit NRSMat(const SparseMat<T> &rhs); explicit NRSMat(const SparseSMat<T> &rhs)}{Dense from sparse, see \ref{ch:sparse}.}
|
||||
\lafunc{~NRSMat()}{Destructor, frees the data when the last reference disappears.}
|
||||
\lafunc{NRSMat & operator=(const NRSMat &rhs)}{Shallow copy, see section~\ref{sec:concepts:assign}.}
|
||||
\lafunc{NRSMat & operator|=(const NRSMat &rhs)}{Deep copy.}
|
||||
\lafunc{NRSMat & operator=(const T &a)}{Scalar matrix: zero all elements (by \code{memset}), set the diagonal to \code{a}. Dimension must be set before.}
|
||||
\lafunc{void identity()}{\code{*this = (T)1}.}
|
||||
\lafunc{void clear()}{Set all elements to zero (keeps the dimension).}
|
||||
\lafunc{void resize(const int n)}{Change the order; data are not preserved and new elements are uninitialized. A shared matrix is detached; an unshared one of the same order is left untouched; \code{n=0} deallocates.}
|
||||
\lafunc{void dealloc()}{\code{resize(0)}.}
|
||||
\lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{Make the data private, see section~\ref{sec:concepts:refcount}.}
|
||||
\lafunc{void swap(NRSMat &rhs)}{Exchange contents without copying.}
|
||||
\lafunc{void moveto(const GPUID dest); GPUID getlocation() const}{GPU location (experimental), see section~\ref{sec:concepts:gpu}.}
|
||||
\lafunc{int getcount() const}{Current reference count (0 for an empty matrix).}
|
||||
\end{lareference}
|
||||
Conversions in other classes: \code{explicit NRMat<T>(const NRSMat<T>&)} expands to a full square matrix
|
||||
(hermitian for complex types, see section~\ref{sec:smat:hermitian}); \code{explicit NRVec<T>(const NRSMat<T>&)} views the
|
||||
packed data as a vector (shared data). \code{NRMat<T>::transposedtimes()} and \code{timestransposed()}
|
||||
return $A\Hc A$ and $A A\Hc$ as \code{NRSMat} (see \ref{ch:mat}).
|
||||
|
||||
\section{Element access}\label{sec:smat:access}
|
||||
\begin{lareference}
|
||||
\lafunc{T& operator()(const int i, const int j); const T& operator()(const int i, const int j) const}{Element $(i,j)$, $0\le i,j<n$, either order; $(i,j)$ and $(j,i)$ refer to the same storage; plain storage access, no complex conjugation for $i<j$ (see section~\ref{sec:smat:hermitian}). Range checked in DEBUG.}
|
||||
\lafunc{T& operator[](const size_t ij); const T& operator[](const size_t ij) const}{Packed element number \code{ij}, $0\le$ \code{ij} $<n(n+1)/2$, see (\ref{eq:smat:index}).}
|
||||
\lafunc{operator T*(); operator const T*() const}{Pointer to the packed data.}
|
||||
\lafunc{int nrows() const; int ncols() const}{Order $n$.}
|
||||
\lafunc{size_t size() const}{Number of stored elements $n(n+1)/2$.}
|
||||
\lafunc{bool transp(const int i, const int j) const}{Returns \code{i>j}; helper for code that uses packed storage for a matrix one triangle of which is redundant but not equal to the other (e.g.\ antisymmetric), to know whether the stored element must be transformed.}
|
||||
\lafunc{bool issymmetric() const}{Always true (interface compatibility with sparse matrices).}
|
||||
\lafunc{void simplify()}{Does nothing (interface compatibility with sparse matrices).}
|
||||
\end{lareference}
|
||||
\lawarning{The non-const \code{operator()} and \code{operator[]} do not call \code{copyonwrite()}. Call
|
||||
\code{x.copyonwrite()} before writing through them; in DEBUG builds they raise an error for a shared
|
||||
matrix even when only reading, so read elements of a possibly shared matrix through a const reference.}
|
||||
|
||||
\section{Arithmetic and BLAS/LAPACK-backed operations}\label{sec:smat:arith}
|
||||
All in-place operators call \code{copyonwrite()}. The binary operators returning \code{NRSMat} are
|
||||
generated from the in-place ones.
|
||||
\begin{lareference}
|
||||
\lafunc{NRSMat & operator*=(const T &a)}{Multiply all elements by \code{a} (\code{dscal}/\code{zscal}).}
|
||||
\lafunc{NRSMat & operator+=(const T &a); NRSMat & operator-=(const T &a)}{Add/subtract $a\cdot 1$, i.e.\ \code{a} on the diagonal only.}
|
||||
\lafunc{NRSMat & operator+=(const NRSMat &rhs); NRSMat & operator-=(const NRSMat &rhs)}{Elementwise sum/difference (\code{daxpy}/\code{zaxpy}).}
|
||||
\lafunc{const NRSMat operator-() const}{Unary minus.}
|
||||
\lafunc{const NRSMat operator*(const T &a) const; const NRSMat operator+(const T &a) const; const NRSMat operator-(const T &a) const}{As the in-place versions (\code{+}/\code{-} act on the diagonal).}
|
||||
\lafunc{const NRSMat operator+(const NRSMat &rhs) const; const NRSMat operator-(const NRSMat &rhs) const}{Sum, difference.}
|
||||
\lafunc{const NRMat<T> operator+(const NRMat<T> &rhs) const; const NRMat<T> operator-(const NRMat<T> &rhs) const}{Sum/difference with a dense matrix, dense result.}
|
||||
\lafunc{const NRMat<T> operator*(const NRSMat &rhs) const}{Matrix product (not symmetric in general). Real: dedicated BLAS-1/2 code; complex: \code{*this * NRMat(rhs)}, see section~\ref{sec:smat:hermitian}.}
|
||||
\lafunc{const NRMat<T> operator*(const NRMat<T> &rhs) const}{$S A$ by \code{dspmv}/\code{zhpmv} column by column.}
|
||||
\lafunc{const NRVec<T> operator*(const NRVec<T> &rhs) const}{$S x$ (\code{dspmv}/\code{zhpmv}).}
|
||||
\lafunc{const NRVec<std::complex<T> > operator*(const NRVec<std::complex<T> > &rhs) const}{Real symmetric matrix times complex vector (real and imaginary parts separately).}
|
||||
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x) const}{$r \leftarrow \beta r + \alpha S x$, forwards to \code{r.gemv(beta,*this,trans,alpha,x)}; \code{trans} is ignored.}
|
||||
\lafunc{void gemv(const T beta, NRVec<std::complex<T> > &r, const char trans, const T alpha, const NRVec<std::complex<T> > &x) const}{The same, real matrix and complex vectors.}
|
||||
\lafunc{void axpy(const T alpha, const NRSMat &x)}{$S \leftarrow S + \alpha x$. For hermitian complex matrices the result is hermitian only for real $\alpha$
|
||||
(not checked).}
|
||||
\lafunc{void rank2update(const T alpha, const NRVec<T> &x, const NRVec<T> &y)}{Rank-2 update $S \leftarrow S + \alpha x y\Hc + \bar\alpha y x\Hc$ (real: $\alpha(xy\Tr+yx\Tr)$); \code{dspr2}/\code{zhpr2} for \code{double}/\code{complex<double>}, generic loop otherwise. A symmetric rank-1 update $S \leftarrow S + \beta x x\Hc$ (real $\beta$) is obtained as \code{rank2update(beta/2,x,x)}; there is no separate rank-1 member.}
|
||||
\lafunc{const T dot(const NRSMat &rhs) const}{$\sum_{i\ge j} \bar A_{ij} B_{ij}$, i.e.\ the dot product of the \emph{packed} vectors (off-diagonal elements counted once; \code{ddot}/\code{zdotc}).}
|
||||
\lafunc{const T dot(const NRVec<T> &rhs) const}{Dot product of the packed data with a vector of length $n(n+1)/2$.}
|
||||
\lafunc{const typename LA_traits<T>::normtype norm(const T scalar = (T)0) const}{Euclidean norm of the packed data of $S - \mathtt{scalar}\cdot 1$; off-diagonal elements are counted \emph{once}, so this is not the Frobenius norm of the full matrix.}
|
||||
\lafunc{const T trace() const}{Trace.}
|
||||
\lafunc{const T sum(bool onlytriangle=false) const}{Sum of all elements of the full matrix, or of the stored triangle only.}
|
||||
\lafunc{const NRVec<T> rsum() const; const NRVec<T> csum() const}{Row sums and column sums of the full matrix (equal for real, complex conjugate of each other for hermitian matrices).}
|
||||
\lafunc{inline const T amax() const; inline const T amin() const}{Stored element with the largest/smallest absolute value (complex: $|\Re|+|\Im|$); \code{double} and \code{complex<double>} only.}
|
||||
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide = 0, bool cache = false) const}{\code{divide=false}: store the diagonal into \code{r} and return \code{&r[0]}; \code{divide=true}: divide \code{r} elementwise by the nonzero diagonal elements and return \code{NULL}. \code{cache} is unused. Interface used by iterative solvers (\ref{ch:iterative}).}
|
||||
\lafunc{NRSMat inverse()}{Inverse via \code{NRMat<T>(*this).inverse()}, symmetrized back. \code{double} and \code{complex<double>} only.}
|
||||
\lafunc{NRSMat& conjugateme(); const NRSMat conjugate() const}{Complex conjugation of all stored elements (no-op for real types).}
|
||||
\lafunc{NRSMat<typename LA_traits<T>::normtype> abs() const}{Elementwise absolute values.}
|
||||
\lafunc{NRSMat<typename LA_traits<T>::normtype> diffabs(const NRSMat &rhs) const}{Elementwise $|A_{ij}|-|B_{ij}|$.}
|
||||
\lafunc{void randomize(const typename LA_traits<T>::normtype &x)}{Uniformly distributed elements in $(-x,x)$; complex: real and imaginary parts independent, imaginary part of the diagonal set to zero. \code{double} and \code{complex<double>} only.}
|
||||
\lafunc{const bool operator==(const NRSMat &rhs) const; const bool operator!=(const NRSMat &rhs) const}{Exact elementwise comparison.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Other member functions}\label{sec:smat:other}
|
||||
\subsection{Submatrices and permutations}
|
||||
\begin{lareference}
|
||||
\lafunc{const NRSMat submatrix(const int from, const int to) const}{Diagonal block with rows/columns \code{from}\ldots\code{to} (inclusive).}
|
||||
\lafunc{const NRSMat submatrix(const NRVec<int> &selection) const}{Matrix $B_{ij} = A_{s_i s_j}$ for the index list $s$ = \code{selection}.}
|
||||
\lafunc{void storesubmatrix(const int from, const NRSMat &rhs)}{Store \code{rhs} as diagonal block starting at \code{from}.}
|
||||
\lafunc{void storesubmatrix(const NRVec<int> &selection, const NRSMat &rhs)}{$A_{s_i s_j} = B_{ij}$.}
|
||||
\lafunc{const NRSMat permuted(const NRPerm<int> &p, const bool inverse=false) const}{Symmetric permutation of rows and columns by \code{p} (indices from 1, see \ref{ch:permutation}): \code{inverse=false} gives $B_{p_i p_j}=A_{ij}$, \code{inverse=true} gives $B_{ij}=A_{p_i p_j}$.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Input/output}
|
||||
See section~\ref{sec:concepts:io} for the general conventions.
|
||||
\begin{lareference}
|
||||
\lafunc{void put(int fd, bool dimensions = 1, bool transp = 0, bool orcaformat=false) const}{Binary output: if \code{dimensions}, $n$ is written twice (as for a square \code{NRMat}), followed by \code{sizeof(T)} if \code{orcaformat}; then the $n(n+1)/2$ packed elements. \code{transp} is ignored.}
|
||||
\lafunc{void get(int fd, bool dimensions = 1, bool transp = 0, bool orcaformat=false)}{Binary input matching \code{put}; with \code{dimensions=false} the current order is used.}
|
||||
\lafunc{void fprintf(FILE *f, const char *format, const int modulo) const}{Formatted output of the full square matrix by \code{lawritemat} (as \code{NRMat::fprintf}, see \ref{ch:mat}).}
|
||||
\lafunc{void fscanf(FILE *f, const char *format)}{Reads the dimensions (must be equal) and then the \emph{whole square} matrix row by row; this is intentional, the data are assumed symmetric. Since $(i,j)$ and $(j,i)$ share the storage, the element read later, i.e.\ the lower triangle one, wins.}
|
||||
\lafunc{void printsorted(std::ostream &s, int direction=1, bool absvalue=false, bool stable=false, typename LA_traits<T>::normtype thr=0, int topmax=0) const}{Print the stored elements sorted with their indices, see \code{NRVec::printsorted} in \ref{ch:vec}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Related free functions}\label{sec:smat:free}
|
||||
\begin{lareference}
|
||||
\lafunc{std::ostream& operator<<(std::ostream &s, const NRSMat<T> &x)}{Text output: \code{n n}, then the full square matrix row by row.}
|
||||
\lafunc{std::istream& operator>>(std::istream &s, NRSMat<T> &x)}{Text input of the same format; the full square is read, the lower triangle element wins (as in \code{fscanf}). Error if the dimensions differ.}
|
||||
\lafunc{const NRSMat<T> operator+(const T &a, const NRSMat<T> &rhs); const NRSMat<T> operator*(const T &a, const NRSMat<T> &rhs)}{Scalar from the left: $a\cdot 1 + S$, $aS$.}
|
||||
\lafunc{const NRSMat<T> operator-(const T &a, const NRSMat<T> &rhs) = delete}{Deliberately not available (compile error); write \code{-S+a} for $a\cdot1-S$.}
|
||||
\lafunc{NRSMat<std::complex<T> > complexify(const NRSMat<T> &rhs)}{Complex copy of a real matrix.}
|
||||
\lafunc{void NRSMat_convert(NRSMat<T> &a, const NRSMat<S> &b)}{Element type conversion (\code{a} is resized, elements converted by a cast).}
|
||||
\end{lareference}
|
||||
Functions in other headers taking \code{NRSMat} (described in \ref{ch:nonclass}): \code{diagonalize()}
|
||||
(\code{dspev}/\code{zhpev}, also generalized problem), \code{linear_solve()}, \code{trace2()},
|
||||
\code{twoside_transform()} ($C\Hc S C$), \code{matrixfunction()}, \code{sqrt()}, \code{sqrtinv()}, \code{log()},
|
||||
\code{realpart()}, \code{imagpart()}. \code{NRMat<T>} has \code{operator+=}, \code{operator-=},
|
||||
\code{operator+}, \code{operator-} and \code{operator*} with an \code{NRSMat<T>} right operand, and
|
||||
\code{NRVec<T>} has \code{gemv} with an \code{NRSMat} and \code{operator*(const NRSMat<T>&)} (vector from the left);
|
||||
see \ref{ch:mat} and \ref{ch:vec}.
|
||||
|
||||
\section{NRSMat\_from1}\label{sec:smat:from1}
|
||||
\code{NRSMat_from1<T>} is derived from \code{NRSMat<T>} and differs only in \code{operator()}, whose
|
||||
indices run from 1 to $n$ (\code{SMat_index_1}). \code{operator[]} and everything else are inherited and
|
||||
still count from 0; arithmetic operators return \code{NRSMat<T>}, which converts back implicitly.
|
||||
\begin{lareference}
|
||||
\lafunc{NRSMat_from1(); explicit NRSMat_from1(const int n); NRSMat_from1(const T &a, const int n); NRSMat_from1(const T *a, const int n)}{As for \code{NRSMat} (the last one inherits the bug of the base constructor).}
|
||||
\lafunc{NRSMat_from1(const NRSMat<T> &rhs)}{Implicit conversion from the base class (shallow).}
|
||||
\lafunc{explicit NRSMat_from1(const NRMat<T> &rhs); explicit NRSMat_from1(const NRVec<T> &rhs, const int n)}{As for \code{NRSMat}.}
|
||||
\lafunc{const T& operator()(const int i, const int j) const; T& operator()(const int i, const int j)}{Element $(i,j)$, $1\le i,j\le n$. Range checked in DEBUG; unlike the base class the non-const version does not check the reference count. Plain storage access as in the base class (no conjugation, section~\ref{sec:smat:hermitian}).}
|
||||
\end{lareference}
|
||||
|
||||
\section{Examples}\label{sec:smat:examples}
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
using namespace LA;
|
||||
|
||||
int main()
|
||||
{
|
||||
const int n=4;
|
||||
NRSMat<double> s(n);
|
||||
for(int i=0; i<n; ++i) for(int j=0; j<=i; ++j) s(i,j) = 1./(1.+i+j); //fill lower triangle
|
||||
NRVec<double> x(1.,n);
|
||||
NRVec<double> y = s*x; //dspmv
|
||||
NRMat<double> full(s); //full square copy
|
||||
NRSMat<double> t = s; //shares data with s
|
||||
t.copyonwrite(); t(0,3) = 5.; //same storage as t(3,0); s unchanged
|
||||
t.rank2update(0.5,x,y); //t += 0.5*(x y^T + y x^T)
|
||||
const NRSMat<double> &sc = s;
|
||||
double sum=0; for(size_t k=0; k<sc.size(); ++k) sum += sc[k]; //packed loop
|
||||
NRVec<double> w(n); NRMat<double> v(n,n);
|
||||
NRSMat<double> work(s); //diagonalize() detaches work itself
|
||||
diagonalize(work,w,&v); //eigenvalues/vectors, work is overwritten
|
||||
std::cout << s << w;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}\label{sec:smat:pitfalls}
|
||||
\begin{itemize}
|
||||
\item \code{size()} is $n(n+1)/2$, use \code{nrows()} for the order.
|
||||
\item \code{dot()} and \code{norm()} work on the packed triangle; for the Frobenius quantities of the
|
||||
full matrix use e.g.\ \code{NRMat<T>(s).norm()} or \code{trace2()}.
|
||||
\item For complex types read section~\ref{sec:smat:hermitian} carefully.
|
||||
\item \code{NRSMat(const NRVec<T>&,int)} and \code{NRVec<T>(const NRSMat<T>&)} share data with their argument.
|
||||
\item \code{NRSMat(const NRMat<T>&)} silently symmetrizes (takes the hermitian part for complex types), it does not check symmetry.
|
||||
\item Complex matrices: \code{operator()(i,j)} with $i<j$ does not conjugate (plain storage access, section~\ref{sec:smat:hermitian}).
|
||||
\item Products of two symmetric matrices return \code{NRMat<T>}; converting back with \code{NRSMat(...)} symmetrizes.
|
||||
\item \code{operator=(const T&)} produces a scalar (diagonal) matrix, whereas the constructor \code{NRSMat(a,n)} fills all elements.
|
||||
\end{itemize}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
\chapter{Sparse matrices}\label{ch:sparse}
|
||||
\laheader{sparsemat.h, sparsemat.cc, sparsesmat.h, sparsesmat.cc}
|
||||
|
||||
\section{Overview}\label{sec:sparse:overview}
|
||||
\LA{} offers two independent sparse matrix classes with different storage schemes, plus a small helper class:
|
||||
\begin{itemize}
|
||||
\item \code{SparseMat<T>} (section~\ref{sec:sparse:sparsemat}): an \emph{unordered linked list} of
|
||||
(row, column, value) triplets. Appending elements is cheap, duplicates are allowed and summed,
|
||||
sorting is done on demand. A flag marks symmetric matrices of which only one triangle is stored.
|
||||
Products with dense vectors and matrices are its main purpose (e.g.\ CI-type Hamiltonians in
|
||||
Davidson/Lanczos solvers, \ref{ch:iterative}, and the matrix exponential).
|
||||
\item \code{SparseSMat<T>} (section~\ref{sec:sparse:sparsesmat}): an array of rows, each row a
|
||||
\code{std::map} from column index to value. It allows efficient random access and accumulation and
|
||||
sparse$\times$sparse products, and offers a pivoted sparse Cholesky decomposition. It was written for
|
||||
exponentiating symmetric matrices (e.g.\ the unitary complex symmetric $\exp(\mathrm{i}H)$ with real symmetric $H$),
|
||||
but despite its name it is a general (also nonsymmetric and rectangular) sparse matrix; symmetry
|
||||
(hermiticity for complex types) is only a convention of some of its operations.
|
||||
\item \code{SemiSparseMat<T>} (section~\ref{sec:sparse:semisparse}): a dense diagonal plus a
|
||||
\code{SparseMat} off-diagonal part, just enough to be used in iterative eigensolvers.
|
||||
\end{itemize}
|
||||
\code{CSRMat<T>} (section~\ref{sec:sparse:csr}) is unfinished.
|
||||
|
||||
Common definitions in \code{sparsemat.h}:
|
||||
\begin{lareference}
|
||||
\lafunc{typedef unsigned int SPMatindex; typedef int SPMatindexdiff}{Index type and index difference type. All row and column indices count from 0.}
|
||||
\lafunc{const double SPARSEEPSILON=1e-15}{Threshold: elements with absolute value below (or equal to) it are dropped by \code{simplify()} and by most constructors and accumulating operations.}
|
||||
\lafunc{template<typename T> struct matel {T elem; SPMatindex row; SPMatindex col; matel *next;}}{List element of \code{SparseMat}; also the value type returned by the iterators of both classes.}
|
||||
\end{lareference}
|
||||
Both classes are explicitly instantiated for \code{double} and \code{std::complex<double>}.
|
||||
Several operations are implemented for \code{double} only. \code{SparseMat} nowhere conjugates when
|
||||
it mirrors elements of a symmetric matrix (source comments: ``not OK for complex hermitean''), so
|
||||
its complex symmetric matrices are complex symmetric, not hermitian. \code{SparseSMat} stores both triangles
|
||||
and its mirroring operations follow the hermitian convention of \code{NRSMat} (section~\ref{sec:smat:hermitian}).
|
||||
Neither class has GPU support.
|
||||
|
||||
The header \code{sparsemat_traits.h} is an obsolete leftover: it specializes a template \code{NRMat_traits}
|
||||
which no longer exists, is not included by any file and is not installed; it is kept in the source tree.
|
||||
|
||||
\section{SparseMat}\label{sec:sparse:sparsemat}
|
||||
\laheader{sparsemat.h, sparsemat.cc}
|
||||
|
||||
\subsection{Storage}\label{sec:sparse:storage}
|
||||
The nonzero elements form a singly linked list of \code{matel<T>} (pointer \code{list}); new elements are
|
||||
prepended. The list is in no particular order and may contain several entries with the same
|
||||
$(i,j)$, which represent their sum; \code{simplify()} merges such duplicates, removes elements with
|
||||
$|a_{ij}|\le$ \code{SPARSEEPSILON} and leaves the list sorted by rows. Operations needing an ordering
|
||||
(\code{simplify}, sparse products, \code{dot}) build private arrays of pointers sorted by rows or by columns
|
||||
(member \code{sort()}); these arrays and the cached element count \code{nonzero} are kept until the next
|
||||
operation which calls the private \code{unsort()}: \code{add()} (and everything built on it), \code{simplify},
|
||||
\code{resize}, \code{transposeme}, the permutation members, \code{get}, assignment, and \code{copyonwrite()} of a
|
||||
shared matrix. Writing \emph{indices} through the iterator or \code{getlist()} after a sort is not detected.
|
||||
|
||||
\textbf{Symmetric flag.} If \code{issymmetric()} is true, the matrix is square and each stored off-diagonal
|
||||
element $a_{ij}$ represents both $a_{ij}$ and $a_{ji}$ (no conjugation); diagonal elements are stored once.
|
||||
Which triangle is used is arbitrary: the constructor from \code{NRSMat} stores $i\ge j$, \code{simplify()}
|
||||
moves all elements to $i\le j$, and user code may mix them, but it must not store both $a_{ij}$ and $a_{ji}$
|
||||
unless their sum is meant. \code{setsymmetric()} only sets the flag, \code{defineunsymmetric()} only clears it
|
||||
(the data are then interpreted as a triangular matrix), and \code{setunsymmetric()} unfolds the matrix by
|
||||
adding the mirror images of the off-diagonal elements. Operations not supporting symmetric storage
|
||||
(sparse$\times$sparse products, \code{sort()}) unfold it themselves or raise an error.
|
||||
|
||||
\subsection{Reference counting}\label{sec:sparse:refcount}
|
||||
The list is reference counted: the copy constructor, \code{operator=} and \code{transpose()} of a symmetric
|
||||
matrix share it (sorted arrays are never shared). There is \emph{no} \code{operator|=}; a deep copy is
|
||||
obtained by \code{SparseMat<T> b(a); b.copyonwrite();}. \code{copyonwrite()} copies the list (in reversed
|
||||
order) when it is shared.
|
||||
|
||||
Most modifying members call \code{copyonwrite()}: \code{simplify}, \code{operator+=}/\code{-=} with a matrix,
|
||||
\code{addtriangle}, \code{join}, \code{axpy}, \code{operator*=}, \code{transposeme}, \code{setunsymmetric},
|
||||
\code{incsize}, \code{addsafe}, the permutation members, \code{gemm}, \code{get}; \code{operator=(const T&)},
|
||||
\code{resize} and \code{gemm} with \code{beta=0} detach by reallocation. Exceptions:
|
||||
\begin{itemize}
|
||||
\item \code{add()} is a minimal inline function: no \code{copyonwrite()}, no range check (it only invalidates
|
||||
the sorted arrays and the cached \code{nonzero} if they exist). By design, user code must call
|
||||
\code{copyonwrite()} before \code{add()} on a possibly shared matrix (as done in the examples in \code{t.cc}).
|
||||
Adding to a shared list does not corrupt the other copies (elements are prepended), but the added
|
||||
elements are leaked or, after a later \code{copyonwrite()}, silently included only in this copy.
|
||||
\item \code{operator+=(const T&)} and \code{operator-=(const T&)} call \code{add()} before the \code{copyonwrite()}
|
||||
inside \code{simplify()} (result correct, memory leaked when shared).
|
||||
\item The iterator and \code{getlist()} give writable access to the list elements without \code{copyonwrite()}.
|
||||
\item \code{norm()} is const but calls \code{simplify()} (which detaches a shared list).
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Construction, assignment, conversion}
|
||||
\begin{lareference}
|
||||
\lafunc{SparseMat()}{Undefined empty matrix ($0\times0$, no reference count).}
|
||||
\lafunc{SparseMat(const SPMatindex n, const SPMatindex m)}{Defined empty (all zero) $n\times m$ matrix, not symmetric.}
|
||||
\lafunc{SparseMat(const SparseMat &rhs)}{Shallow copy; an empty matrix gets its own reference count.}
|
||||
\lafunc{explicit SparseMat(const NRMat<T> &rhs)}{From a dense matrix, elements with $|a_{ij}|>$ \code{SPARSEEPSILON}.}
|
||||
\lafunc{explicit SparseMat(const NRSMat<T> &rhs)}{From a packed symmetric matrix; symmetric flag set, lower triangle stored.}
|
||||
\lafunc{~SparseMat()}{Destructor.}
|
||||
\lafunc{SparseMat & operator=(const SparseMat &rhs)}{Shallow copy (also copies the symmetric flag).}
|
||||
\lafunc{SparseMat & operator=(const T &a)}{Scalar matrix $a\cdot1$; the matrix must be square and dimensioned, old contents are discarded.}
|
||||
\lafunc{void identity()}{\code{*this = (T)1}.}
|
||||
\lafunc{void resize(const SPMatindex n, const SPMatindex m)}{New empty $n\times m$ matrix; data are discarded (detaches if shared), the symmetric flag is cleared.}
|
||||
\lafunc{void dealloc()}{\code{resize(0,0)}.}
|
||||
\lafunc{void incsize(const SPMatindex n, const SPMatindex m)}{Enlarge the dimensions by $n$ and $m$ keeping the elements (used for direct sums).}
|
||||
\lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{Make the list private, see section~\ref{sec:concepts:refcount}.}
|
||||
\lafunc{void clear()}{Zero matrix of the same dimensions: as \code{resize(nrows(),ncols())}, but the symmetric flag is kept (detaches if shared).}
|
||||
\lafunc{void swap(SparseMat &rhs)}{Exchange contents without copying.}
|
||||
\lafunc{int getcount() const}{Reference count (0 if undefined).}
|
||||
\end{lareference}
|
||||
Dense from sparse: \code{explicit NRMat<T>(const SparseMat<T>&)} (symmetric matrices are unfolded,
|
||||
duplicates summed), \code{explicit NRSMat<T>(const SparseMat<T>&)} (requires the symmetric flag, duplicates summed),
|
||||
\code{explicit NRVec<T>(const SparseMat<T>&)} (for a $1\times m$ or $n\times 1$ matrix).
|
||||
|
||||
\subsection{Element insertion and access}
|
||||
\begin{lareference}
|
||||
\lafunc{inline void add(const SPMatindex n, const SPMatindex m, const T elem)}{Prepend element $(n,m)$; it is summed with existing entries at the same position. No checks, no \code{copyonwrite()}, see section~\ref{sec:sparse:refcount}. The matrix must have been dimensioned (constructor with dimensions or \code{resize}).}
|
||||
\lafunc{void addsafe(const SPMatindex n, const SPMatindex m, const T elem)}{As \code{add}, but ignores $|$\code{elem}$|\le$ \code{SPARSEEPSILON}, calls \code{copyonwrite()} and creates the reference count of an undefined matrix (range check only with the lowercase \code{debug} macro).}
|
||||
\lafunc{void simplify(const double sparseepsilon=SPARSEEPSILON)}{Sum duplicates, remove elements with $|a_{ij}|\le$ \code{sparseepsilon}, sort by rows; symmetric matrices are first moved to the upper triangle ($i\le j$).}
|
||||
\lafunc{unsigned int length() const}{Number of list elements (cached until the next modification, see section~\ref{sec:sparse:storage}).}
|
||||
\lafunc{unsigned int sort(int type) const}{Build the internal array sorted by rows (\code{type=0}) or columns (\code{type=1}) and return the number of elements. Logically const, but unfolds a symmetric matrix (\code{setunsymmetric}). Normally called internally only.}
|
||||
\lafunc{inline matel<T> *getlist() const; void setlist(matel<T> *l)}{Raw access to the list head (no reference count handling; \code{setlist} does not free the old list).}
|
||||
\lafunc{iterator begin() const; iterator end() const}{Forward iterator over the raw list (unsorted, duplicates possible, one triangle for symmetric matrices). \code{*it} is \code{matel<T>&}, \code{it->row}, \code{it->col}, \code{it->elem} are writable; \code{++} (pre- and postfix), \code{==}, \code{!=}.}
|
||||
\lafunc{inline SPMatindex nrows() const; inline SPMatindex ncols() const}{Dimensions.}
|
||||
\lafunc{inline bool issymmetric() const}{Symmetric flag.}
|
||||
\lafunc{inline void setsymmetric(); inline void defineunsymmetric(); void setunsymmetric()}{Set the flag (square only) / clear the flag / unfold the stored triangle, see section~\ref{sec:sparse:storage}.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Arithmetic}
|
||||
Binary operators with a matrix operand must not combine a symmetric left operand with a general right one.
|
||||
\begin{lareference}
|
||||
\lafunc{SparseMat & operator*=(const T &a)}{Scale all elements (\code{a=0} calls \code{clear()}).}
|
||||
\lafunc{SparseMat & operator+=(const T &a); SparseMat & operator-=(const T &a)}{Add/subtract $a\cdot 1$ (square matrices), then \code{simplify()}.}
|
||||
\lafunc{SparseMat & operator+=(const SparseMat &rhs); SparseMat & operator-=(const SparseMat &rhs)}{Append \code{rhs} (unfolded if \code{rhs} is symmetric and \code{*this} is not; error for symmetric \code{*this} and general \code{rhs}), then \code{simplify()}.}
|
||||
\lafunc{const SparseMat operator+(const T &rhs) const; const SparseMat operator-(const T &rhs) const; const SparseMat operator*(const T &rhs) const}{Out-of-place versions.}
|
||||
\lafunc{const SparseMat operator+(const SparseMat &rhs) const; const SparseMat operator-(const SparseMat &rhs) const}{Out-of-place versions.}
|
||||
\lafunc{SparseMat & addtriangle(const SparseMat &rhs, const bool lower, const char sign)}{Append the elements of \code{rhs} with $i\le j$ (\code{lower=true}) or $i\ge j$ (\code{lower=false}), all of them if \code{rhs} is symmetric; \code{sign} is \code{'+'} or \code{'-'}. No \code{simplify()}.}
|
||||
\lafunc{SparseMat & join(SparseMat &rhs)}{Fast \code{+=}: moves the list of \code{rhs} (must be unshared, same dimensions and symmetry) to the end of this list; \code{rhs} is left empty. No \code{simplify()}.}
|
||||
\lafunc{void axpy(const T alpha, const SparseMat &x, const bool transp=0)}{$A \leftarrow A + \alpha x$ or $A + \alpha x\Tr$; no \code{simplify()} (except for $\alpha=\pm1$, which uses \code{+=}/\code{-=}).}
|
||||
\lafunc{const T trace() const}{Trace.}
|
||||
\lafunc{const typename LA_traits<T>::normtype norm(const T scalar=(T)0) const}{Frobenius norm of $A-\mathtt{scalar}\cdot1$ of the full matrix (symmetric off-diagonal elements counted twice). Calls \code{simplify()}.}
|
||||
\lafunc{const T dot(const SparseMat &rhs) const}{$\sum_{ij} a_{ij} b_{ij}$ without conjugation (also for complex types), general (not symmetric) matrices only; merge of the two lists sorted by rows.}
|
||||
\lafunc{const T dot(const NRMat<T> &rhs) const}{$\sum_{ij} a_{ij} b_{ij}$ with a dense matrix; \code{double} only.}
|
||||
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide=0, bool cache=false) const}{Square matrix: diagonal into \code{r} (\code{divide=false}, returns \code{&r[0]}) or divide \code{r} by the nonzero diagonal elements (\code{divide=true}, returns \code{NULL}). Rectangular matrix: the diagonal of $A\Tr A$ (assuming a simplified matrix), for preconditioning. \code{cache} is unused.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Products}
|
||||
\begin{lareference}
|
||||
\lafunc{inline const NRVec<T> operator*(const NRVec<T> &rhs) const}{$A x$.}
|
||||
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x, bool treat_as_symmetric=false) const}{$r \leftarrow \beta r + \alpha\,\mathrm{op}(A)x$; forwards to \code{NRVec<T>::gemv}. Any \code{trans} other than \code{'n'} means transposition (no conjugation). With \code{treat_as_symmetric} a general matrix is used as if it had the symmetric flag. The sizes of \code{x} and (in DEBUG) of the result are checked.}
|
||||
\lafunc{inline const NRMat<T> operator*(const NRMat<T> &rhs) const}{$A B$ via \code{NRMat<T>::gemm} with a sparse first operand; \code{double} only (\code{complex<double>} raises ``not implemented yet'').}
|
||||
\lafunc{const SparseMat operator*(const SparseMat &rhs) const}{Sparse product of two general matrices (error if one is symmetric); the result is simplified.}
|
||||
\lafunc{void gemm(const T beta, const SparseMat &a, const char transa, const SparseMat &b, const char transb, const T alpha)}{$C \leftarrow \beta C + \alpha\,\mathrm{op}(A)\,\mathrm{op}(B)$ for general \code{a}, \code{b}; if \code{*this} is symmetric (possible only with $\beta\neq0$) only elements with $i\le j$ are added. Result simplified. \code{a} and \code{b} must not be \code{*this} (checked in DEBUG).}
|
||||
\end{lareference}
|
||||
Related functions in other classes:
|
||||
\code{NRVec<T>::gemv(const T beta, const SparseMat<T> &a, const char trans, const T alpha, const NRVec &x, const bool treat_as_symmetric=false)},
|
||||
\code{NRVec<T>::operator*(const SparseMat<T>&)} ($x\Tr A$),
|
||||
\code{NRMat<T>::gemm(const T &beta, const SparseMat<T> &a, const char transa, const NRMat &b, const char transb, const T &alpha)}
|
||||
(\code{double} only) and \code{NRMat<T>::operator+=}, \code{operator-=(const SparseMat<T>&)}.
|
||||
|
||||
\subsection{Transposition, permutations, direct sums}
|
||||
\begin{lareference}
|
||||
\lafunc{void transposeme(); const SparseMat transpose() const}{Transpose in place / out of place (no conjugation); trivial for symmetric matrices.}
|
||||
\lafunc{void permuteindices(const NRVec<SPMatindex> &p)}{Square matrix: $(i,j)\to(p_i,p_j)$, indices from 0.}
|
||||
\lafunc{void permuterows(const NRVec<SPMatindex> &p); void permutecolumns(const NRVec<SPMatindex> &p)}{$i\to p_i$ resp.\ $j\to p_j$ (for symmetric matrices both indices are permuted), indices from 0.}
|
||||
\lafunc{void permuteme_rows(const NRPerm<int> &p, const bool inverse=false); void permuteme_cols(const NRPerm<int> &p, const bool inverse=false)}{The same with a permutation object (\ref{ch:permutation}), optionally inverted. The matrix indices count from 0, the permutation (as always for \code{NRPerm}) from 1: element $(i,j)$ moves to row $p_{i+1}-1$ resp.\ column $p_{j+1}-1$ (for symmetric matrices both indices are permuted), the same direction as \code{NRMat::permuted_rows}/\code{permuted_cols}.}
|
||||
\lafunc{void permuteme_both(const NRPerm<int> &p, const NRPerm<int> &q, const bool inverse=false)}{Rows by \code{p}, columns by \code{q} (same index convention).}
|
||||
\lafunc{const SparseMat permuted_rows(const NRPerm<int> &p, const bool inverse=false) const; const SparseMat permuted_cols(const NRPerm<int> &p, const bool inverse=false) const; const SparseMat permuted_both(const NRPerm<int> &p, const NRPerm<int> &q, const bool inverse=false) const}{Out-of-place versions.}
|
||||
\lafunc{SparseMat & oplusequal(const SparseMat &rhs); const SparseMat oplus(const SparseMat &rhs) const}{Direct sum $A\oplus B$ (block diagonal); both must have the same symmetry.}
|
||||
\lafunc{SparseMat & oplusequal(const NRMat<T> &rhs); const SparseMat oplus(const NRMat<T> &rhs) const}{Direct sum with a dense matrix (general \code{*this} only).}
|
||||
\lafunc{SparseMat & oplusequal(const NRSMat<T> &rhs); const SparseMat oplus(const NRSMat<T> &rhs) const}{Direct sum with a packed symmetric matrix (symmetric \code{*this} only; the lower triangle of \code{rhs} is added).}
|
||||
\lafunc{const SparseMat otimes(const SparseMat &rhs) const; const SparseMat otimes(const NRMat<T> &rhs) const; const SparseMat otimes(const NRSMat<T> &rhs) const}{Direct (Kronecker) product: declared, but not implemented anywhere (link error).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Input/output}
|
||||
\begin{lareference}
|
||||
\lafunc{void put(int fd, bool dimensions=1, bool transposed=false, bool orcaformat=false) const}{Binary output: if \code{dimensions}, $n$, $m$ (\code{SPMatindex}), the symmetric flag and the cached element count (\code{int}); then for each list element row, column and the value (written by \code{LA_traits<T>::put}, so nested types work); terminated by the pair $(-1,-1)$. \code{transposed} swaps rows and columns.}
|
||||
\lafunc{void get(int fd, bool dimensions=1, bool transposed=false, bool orcaformat=false)}{Binary input matching \code{put}. With \code{dimensions=false} the read list replaces the current one (which is freed, or detached if shared); the dimensions are kept.}
|
||||
\end{lareference}
|
||||
The argument \code{orcaformat} exists for the uniform \code{LA_traits} interface (section~\ref{sec:concepts:io}), so that
|
||||
e.g.\ \code{NRVec<SparseMat<T>>} can be written and read; the ORCA format is defined for dense data only and
|
||||
\code{orcaformat=true} raises \code{laerror}. The same holds for \code{SparseSMat}.
|
||||
|
||||
\subsection{Related free functions}
|
||||
\begin{lareference}
|
||||
\lafunc{std::ostream& operator<<(std::ostream &s, const SparseMat<T> &x)}{Text output: \code{n m}, then lines \code{row col value} in list order, terminated by \code{-1 -1}. The symmetric flag is not written.}
|
||||
\lafunc{std::istream& operator>>(std::istream &s, SparseMat<T> &x)}{Text input of the same format; the result is a general (not symmetric) matrix.}
|
||||
\lafunc{inline const SparseMat<T> commutator(const SparseMat<T> &x, const SparseMat<T> &y, const bool trx=0, const bool tryy=0)}{$[\mathrm{op}(x),\mathrm{op}(y)] = xy-yx$, via two \code{gemm} calls (general matrices only).}
|
||||
\lafunc{inline const SparseMat<T> anticommutator(const SparseMat<T> &x, const SparseMat<T> &y, const bool trx=0, const bool tryy=0)}{$xy+yx$.}
|
||||
\end{lareference}
|
||||
In \ref{ch:nonclass}: \code{realpart()} and \code{imagpart()} of a \code{SparseMat<complex<double>>}. The generic
|
||||
matrix functions in \code{matexp.h} (e.g.\ \code{exp()}) work with \code{SparseMat}, and the iterative solvers
|
||||
of \ref{ch:iterative} use its \code{gemv}, \code{diagonalof}, \code{issymmetric}, \code{nrows} interface.
|
||||
|
||||
\section{SemiSparseMat}\label{sec:sparse:semisparse}
|
||||
\laheader{sparsemat.h}
|
||||
A rudimentary class (``implemented just enough to call davidson etc.\ on it'') holding a dense diagonal and
|
||||
a sparse off-diagonal part, both public members. If \code{diagonal} is nonempty, the diagonal elements
|
||||
are assumed to be stored \emph{only} there. It is not reference counted by itself; its members are.
|
||||
\begin{lareference}
|
||||
\lafunc{NRVec<T> diagonal; SparseMat<T> offdiagonal}{The two parts (public data members).}
|
||||
\lafunc{SemiSparseMat(); SemiSparseMat(const SPMatindex n, const SPMatindex m)}{Empty object / $n\times m$; for $n=m$ the diagonal vector is allocated and zeroed (zero matrix).}
|
||||
\lafunc{SPMatindex nrows() const; SPMatindex ncols() const; SPMatindex issymmetric() const; void setsymmetric()}{Forwarded to \code{offdiagonal}.}
|
||||
\lafunc{void add(const SPMatindex n, const SPMatindex m, const T elem)}{Diagonal elements go to \code{diagonal} (if allocated), others to \code{offdiagonal.add()}; no \code{copyonwrite()}.}
|
||||
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x, bool treat_as_symmetric=false) const; NRVec<T> operator*(const NRVec<T> &rhs) const}{Matrix-vector product.}
|
||||
\lafunc{const T* diagonalof(NRVec<T> &x, const bool divide=0, bool cache=false) const}{As for \code{SparseMat}, using \code{diagonal} if allocated.}
|
||||
\lafunc{void clear(); void copyonwrite(); void resize(const SPMatindex n, const SPMatindex m)}{Applied to both parts (\code{resize} gives a zero matrix and allocates the diagonal only for $n=m$).}
|
||||
\lafunc{std::ostream& operator<<(std::ostream &s, const SemiSparseMat<T> &x)}{Prints both parts.}
|
||||
\lafunc{explicit NRMat<T>(const SemiSparseMat<T> &rhs)}{Dense conversion.}
|
||||
\end{lareference}
|
||||
|
||||
\section{SparseSMat}\label{sec:sparse:sparsesmat}
|
||||
\laheader{sparsesmat.h, sparsesmat.cc}
|
||||
|
||||
\subsection{Storage and concepts}
|
||||
The data member \code{v} is an array of \code{nrows()} pointers to \code{std::map<SPMatindex,T>}; row $i$ maps
|
||||
column indices to values and is \code{NULL} if empty. Each position occurs at most once, rows are
|
||||
sorted by column automatically.
|
||||
|
||||
Despite its name, \code{SparseSMat} is a \emph{general} sparse matrix: it need not be square ($n\times m$),
|
||||
and the elements $(i,j)$ and $(j,i)$ are stored independently, so nonsymmetric matrices are represented
|
||||
exactly. \code{operator*} (the general product $AB$), \code{gemv}, \code{axpy}, the iterator, \code{transpose()},
|
||||
\code{submatrix}/\code{storesubmatrix}, \code{put}/\code{get}, the text I/O and the conversions to and from \code{NRMat}
|
||||
work for general matrices. The symmetry (hermiticity for complex types) survives only as a convention of some operations:
|
||||
\begin{itemize}
|
||||
\item \code{add(i,j,x)} with the default \code{both=true} inserts $x$ at $(i,j)$ and $x^*$ at $(j,i)$; use
|
||||
\code{add(i,j,x,false)} for a single element;
|
||||
\item the constructor from \code{NRSMat} and \code{otimes_sparse} produce hermitian (real: symmetric) matrices;
|
||||
\item \code{gemm} computes $A\Tr B$ (or $A\Hc B$), i.e.\ $AB$ only for $A=A\Tr$ ($A=A\Hc$), and is restricted to square matrices;
|
||||
\item \code{NRSMat(const SparseSMat&)} takes the lower triangle and assumes a hermitian matrix;
|
||||
\item \code{issymmetric()} always returns \code{true} (needed by the Davidson interface, chapter~\ref{ch:iterative}).
|
||||
\end{itemize}
|
||||
In particular complex symmetric matrices such as $\exp(\mathrm{i}H)$ with real symmetric $H$ (the original purpose of
|
||||
the class, built e.g.\ by \code{imagmatrix} and \code{exp}), hermitian and general matrices are all handled correctly
|
||||
by \code{operator*} and \code{exp}.
|
||||
|
||||
\textbf{Planned extension.} In the future \code{SparseSMat} may be handled as a general matrix class with a member
|
||||
\code{symmetry_type} (0 for no symmetry, 1 symmetric, 2 hermitian); the conversion from \code{NRSMat}, \code{otimes_sparse},
|
||||
\code{gemm} (and \code{issymmetric()}) would then be implemented according to it, in a backward compatible way.
|
||||
|
||||
\textbf{Reference counting.} The row array is shared by the copy constructor and \code{operator=}; there is no
|
||||
\code{operator|=}. \code{copyonwrite()} (no arguments) copies all maps (element type copied by its copy
|
||||
constructor, i.e.\ no nested deep copy). It is called by \code{operator*=}, \code{axpy} (hence \code{+=}, \code{-=}),
|
||||
\code{operator+=(const T&)}, \code{operator-=(const T&)}, \code{gemm} and \code{storesubmatrix};
|
||||
\code{resize}, \code{clear}, \code{get(...,false,...)} and \code{operator=(const T&)} detach by reallocation. Not calling it:
|
||||
\code{add()} (by design: element-level access, like \code{operator[]} of the dense classes and \code{SparseMat::add()};
|
||||
call \code{copyonwrite()} before adding to a possibly shared matrix), \code{operator[]} (raw row access, no reference count check
|
||||
even in DEBUG) and \code{simplify()} (removes only near-zero elements, which then disappear from all sharing
|
||||
copies).
|
||||
|
||||
\subsection{Construction, assignment, conversion}
|
||||
\begin{lareference}
|
||||
\lafunc{SparseSMat()}{Undefined empty matrix.}
|
||||
\lafunc{explicit SparseSMat(const SPMatindex n); explicit SparseSMat(const SPMatindex n, const SPMatindex m)}{Empty (zero) $n\times n$ or $n\times m$ matrix.}
|
||||
\lafunc{SparseSMat(const SparseSMat &rhs)}{Shallow copy.}
|
||||
\lafunc{explicit SparseSMat(const NRSMat<T> &rhs)}{From a packed symmetric (complex: hermitian) matrix (both triangles inserted, the upper one conjugated, $|a_{ij}|>$ \code{SPARSEEPSILON}).}
|
||||
\lafunc{explicit SparseSMat(const NRMat<T> &rhs)}{From a dense $n\times m$ matrix, nonzero elements copied as they are (no symmetrization).}
|
||||
\lafunc{explicit SparseSMat(const SparseMat<T> &rhs)}{From a \code{SparseMat} (same dimensions); duplicates are summed, a symmetric \code{SparseMat} is unfolded without conjugation (complex symmetric, as \code{SparseMat} defines it).}
|
||||
\lafunc{explicit SparseSMat(const CSRMat<T> &rhs)}{Declared but not implemented (\code{CSRMat} is not supported).}
|
||||
\lafunc{~SparseSMat()}{Destructor.}
|
||||
\lafunc{SparseSMat & operator=(const SparseSMat &rhs)}{Shallow copy.}
|
||||
\lafunc{SparseSMat & operator=(const T &a)}{Scalar matrix $a\cdot1$ (diagonal elements are created even for $a=0$).}
|
||||
\lafunc{void copyonwrite()}{Make the data private.}
|
||||
\lafunc{void resize(const SPMatindex nn, const SPMatindex mm)}{Empty matrix of the new size (detaches if shared); \code{resize(0,0)} frees everything and leaves an undefined (empty) matrix.}
|
||||
\lafunc{void dealloc(); void clear()}{\code{resize(0,0)}; \code{resize(nrows(),ncols())}.}
|
||||
\lafunc{inline void setcoldim(int i)}{Set the number of columns without touching the data.}
|
||||
\lafunc{inline int getcount() const}{Reference count.}
|
||||
\end{lareference}
|
||||
Dense from sparse: \code{explicit NRMat<T>(const SparseSMat<T>&)} copies all stored elements;
|
||||
\code{explicit NRSMat<T>(const SparseSMat<T>&)} (square only) takes the lower triangle $i\ge j$ (as stored in \code{NRSMat}; the matrix is assumed hermitian).
|
||||
|
||||
\subsection{Element insertion and access}
|
||||
\begin{lareference}
|
||||
\lafunc{inline void add(const SPMatindex n, const SPMatindex m, const T elem, const bool both=true)}{Add \code{elem} to element $(n,m)$ and, if \code{both} and $n\ne m$, its complex conjugate to $(m,n)$. Creates the row map if needed; no \code{copyonwrite()}; range check in DEBUG only. Resulting zeros remain until \code{simplify()}.}
|
||||
\lafunc{inline const std::map<SPMatindex,T> *line(const SPMatindex n) const}{Read-only access to row $n$ (\code{NULL} if empty).}
|
||||
\lafunc{inline ROWTYPE & operator[](const SPMatindex i)}{Row $i$ as a reference to the pointer \code{std::map<SPMatindex,T>*} (\code{typedef ROWTYPE}), possibly \code{NULL}. Non-const only.}
|
||||
\lafunc{unsigned long long simplify()}{Remove elements with $|a_{ij}|<$ \code{SPARSEEPSILON} and empty rows; returns the number of remaining elements.}
|
||||
\lafunc{inline unsigned long long length()}{\code{simplify()} (non-const).}
|
||||
\lafunc{int nrows() const; int ncols() const; inline bool issymmetric() const}{Dimensions; \code{issymmetric()} is always true.}
|
||||
\lafunc{iterator begin() const; iterator end() const}{Iterator over all stored elements, row by row in increasing column order. \code{*it} is a \code{matel<T>} \emph{copy} held in the iterator (writing to it does not change the matrix). It is meant for comparison with \code{end()} or via \code{it.notend()}; \code{iterator(const SparseSMat&)} starts at the first element.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Arithmetic and products}
|
||||
\begin{lareference}
|
||||
\lafunc{SparseSMat & operator*=(const T &a)}{Scale (\code{a=0} clears the matrix).}
|
||||
\lafunc{SparseSMat & operator+=(const T &a); SparseSMat & operator-=(const T &a)}{Add/subtract $a\cdot1$.}
|
||||
\lafunc{void axpy(const T alpha, const SparseSMat &x, const bool transp=0)}{$A\leftarrow A+\alpha x$, then \code{simplify()}; \code{transp} is ignored.}
|
||||
\lafunc{inline SparseSMat & operator+=(const SparseSMat &rhs); inline SparseSMat & operator-=(const SparseSMat &rhs)}{\code{axpy(}$\pm1$\code{,rhs)}.}
|
||||
\lafunc{inline const SparseSMat operator*(const T &rhs) const; inline const SparseSMat operator+(const T &rhs) const; inline const SparseSMat operator-(const T &rhs) const}{Out-of-place scalar operations.}
|
||||
\lafunc{inline const SparseSMat operator+(const SparseSMat &rhs) const; inline const SparseSMat operator-(const SparseSMat &rhs) const}{Sum, difference.}
|
||||
\lafunc{void gemm(const T beta, const SparseSMat &a, const char transa, const SparseSMat &b, const char transb, const T alpha)}{$C\leftarrow\beta C+\alpha\,\tilde A\Tr\tilde B$ where $\tilde A=\bar A$ if \code{transa=='c'} and $\tilde B=\bar B$ if \code{transb=='c'} (other values: no conjugation); all matrices square of equal order. For symmetric $A$ this is $\mathrm{op}(A)\,\mathrm{op}(B)$; it is \emph{not} a general product. Rows $k$ of $A$ and $B$ are multiplied as an outer product by BLAS and scattered into the maps; the result is simplified.}
|
||||
\lafunc{const SparseSMat operator*(const SparseSMat &rhs) const}{General product $AB$ of the stored matrices (hermitian, complex symmetric, nonsymmetric or rectangular): row $i$ of the result is accumulated as $\sum_k A_{ik}\,(\text{row }k\text{ of }B)$; simplified.}
|
||||
\lafunc{void gemv(const T beta, NRVec<T> &r, const char trans, const T alpha, const NRVec<T> &x) const}{$r\leftarrow\beta r+\alpha Ax$ (general $A$); only \code{trans='n'} is implemented.}
|
||||
\lafunc{inline const NRVec<T> operator*(const NRVec<T> &rhs) const}{$Ax$.}
|
||||
\lafunc{typename LA_traits<T>::normtype norm(const T scalar=(T)0) const}{Frobenius norm of $A-\mathtt{scalar}\cdot1$ over all stored elements.}
|
||||
\lafunc{const T* diagonalof(NRVec<T> &r, const bool divide=0, bool cache=false) const}{As for \code{SparseMat} (square matrices only).}
|
||||
\lafunc{SparseSMat transpose(bool conj=false) const}{Transposed (optionally conjugated) copy.}
|
||||
\lafunc{void transposeme() const}{Raises an error (not implemented).}
|
||||
\lafunc{SparseSMat<T> cholesky(void) const}{Pivoted Cholesky decomposition of a positive semidefinite symmetric/hermitian matrix (not checked): returns $R$ with $R\Hc R = A$ (\code{result.transpose(true)*result} reproduces the input). The pivoting is done a priori by sorting the diagonal; pivots with real part below \code{CHOLESKYEPSILON}$=10^{-16}$ are skipped (rank-deficient case), elements below \code{SPARSEEPSILON} are dropped. $R$ is upper triangular only in the permuted basis.}
|
||||
\lafunc{SparseSMat submatrix(const int fromrow, const int torow, const int fromcol, const int tocol) const}{Block (inclusive bounds).}
|
||||
\lafunc{void storesubmatrix(const int fromrow, const int fromcol, const SparseSMat &rhs)}{Store \code{rhs} into the block starting at $(\mathtt{fromrow},\mathtt{fromcol})$: the old elements of the block are removed first (as \code{NRMat::storesubmatrix}); calls \code{copyonwrite()}.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Input/output and free functions}
|
||||
\begin{lareference}
|
||||
\lafunc{void put(int fd, bool dimen=1, bool transp=0, bool orcaformat=false) const}{Binary output: optionally $n$, $m$; then row, column, value of each element; terminated by $(-1,-1)$. With \code{transp} the transposed matrix is written (dimensions and indices swapped), as for \code{SparseMat}.}
|
||||
\lafunc{void get(int fd, bool dimen=1, bool transp=0, bool orcaformat=false)}{Binary input matching \code{put}; with \code{transp} the transposed matrix is stored. Elements are accumulated by \code{add(...,false)}; with \code{dimen=false} the read matrix replaces the old contents (\code{clear()}), the dimensions are kept.}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const SparseSMat<T> &x)}{Text output: \code{n m}, lines \code{row col value} (all stored elements), terminator \code{-1 -1}.}
|
||||
\lafunc{std::istream& operator>>(std::istream &s, SparseSMat<T> &x)}{Text input of the same format (square only); every element is inserted only at its own position (\code{add(i,j,x,false)}), so both triangles must be present in the input.}
|
||||
\lafunc{SparseSMat<T> otimes_sparse(const NRVec<T> &lhs, const NRVec<T> &rhs, const bool conjugate=false, const T &scale=1)}{\emph{Symmetrized} outer product, symmetric (complex: hermitian) regardless of the input vectors (unlike \code{SparseMat}):
|
||||
with $M=\mathtt{scale}\cdot l\,r\Tr$ (or $l\,r\Hc$ if \code{conjugate}) the result is $S=M+M\Hc$ ($=l\,r\Tr+r\,l\Tr$ for real types),
|
||||
including the doubled diagonal $S_{ii}=M_{ii}+M_{ii}^*$. Note that \code{otimes_sparse(x,x,true)} is $2\,x x\Hc$; use \code{scale=0.5} for $x x\Hc$.
|
||||
Elements with $|S_{ij}|\le$ \code{SPARSEEPSILON} are omitted. Both vectors must have the same length (\code{laerror} otherwise).}
|
||||
\lafunc{SparseSMat<T> NRMat<T>::operator*(const SparseSMat<T> &rhs) const}{Declared in mat.h: dense times sparse, general product $AS$ with a sparse result (row $i$ accumulated as $\sum_k A_{ik}\,(\text{row }k\text{ of }S)$, zero $A_{ik}$ skipped; simplified). Instantiated for \code{double} and \code{std::complex<double>}.}
|
||||
\end{lareference}
|
||||
In \ref{ch:nonclass}: \code{complexmatrix()}, \code{realmatrix()} and \code{imagmatrix()} build a
|
||||
\code{SparseSMat<complex<double>>} from real ones. The generic \code{exp()} of \code{matexp.h} works with
|
||||
\code{SparseSMat}.
|
||||
|
||||
\section{CSRMat}\label{sec:sparse:csr}
|
||||
\laheader{csrmat.h}
|
||||
\code{CSRMat<T>} is meant to hold a matrix in compressed sparse row format (row pointers, column indices,
|
||||
values, optionally on the GPU via cuSPARSE) for fast products. The class is unfinished: only the declarations
|
||||
exist, \code{csrmat.cc} contains no implementation and no instantiation. It is \emph{not supported} and must not be used;
|
||||
the declarations (also \code{NRMat(const CSRMat&)} and \code{SparseSMat(const CSRMat&)}) are kept for possible future work.
|
||||
|
||||
\section{Examples}\label{sec:sparse:examples}
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
using namespace LA;
|
||||
|
||||
int main()
|
||||
{
|
||||
//general sparse matrix as a list of triplets
|
||||
SparseMat<double> a(4,4);
|
||||
a.add(0,0,2.); a.add(1,2,1.); a.add(3,0,-1.);
|
||||
a.add(1,2,0.5); //duplicate position: values are summed
|
||||
a.simplify(); //merge duplicates, drop zeros
|
||||
NRVec<double> x(1.,4);
|
||||
NRVec<double> y = a*x; //sparse * dense vector
|
||||
NRMat<double> d(a); //dense copy
|
||||
for(SparseMat<double>::iterator p=a.begin(); p!=a.end(); ++p)
|
||||
std::cout << p->row <<" "<< p->col <<" "<< p->elem <<"\n";
|
||||
|
||||
//symmetric storage: only one triangle is stored
|
||||
SparseMat<double> s(4,4);
|
||||
s.setsymmetric();
|
||||
s.add(2,0,1.5); s.add(3,3,1.);
|
||||
NRVec<double> z = s*x; //uses s(2,0) and s(0,2)
|
||||
NRSMat<double> sd(s); //dense packed copy
|
||||
|
||||
//shared matrices: detach before add()
|
||||
SparseMat<double> b(a); //shares the list with a
|
||||
b.copyonwrite();
|
||||
b.add(2,2,3.);
|
||||
|
||||
//map-based sparse matrix
|
||||
SparseSMat<double> h(4);
|
||||
h.add(0,1,0.5); //adds (0,1) and (1,0)
|
||||
h.add(2,2,1.);
|
||||
SparseSMat<double> h2 = h*h; //result stays symmetric
|
||||
NRVec<double> w = h*x;
|
||||
std::cout << y << z << w << NRMat<double>(h2) << sd;
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}\label{sec:sparse:pitfalls}
|
||||
\begin{itemize}
|
||||
\item \code{add()} of both classes does not call \code{copyonwrite()}; call it yourself on a matrix that may be shared.
|
||||
\item \code{SparseMat} may contain duplicates; call \code{simplify()} before relying on the list contents
|
||||
(iteration, \code{NRSMat} conversion, \code{diagonalof} for rectangular matrices).
|
||||
\item In a symmetric \code{SparseMat} never store both $a_{ij}$ and $a_{ji}$; \code{setsymmetric()} does not check this.
|
||||
\item \code{SparseSMat::gemm} computes $A\Tr B$ (or $A\Hc B$); use \code{operator*} for the general product.
|
||||
\item Complex \code{SparseMat} matrices are complex symmetric, never hermitian, in all mirroring operations, whereas \code{SparseSMat} (\code{add} with \code{both=true}, conversion from \code{NRSMat}, \code{otimes_sparse}) is hermitian.
|
||||
\end{itemize}
|
||||
+404
@@ -0,0 +1,404 @@
|
||||
\chapter{Tensors}\label{ch:tensor}
|
||||
|
||||
\laheader{tensor.h, tensor.cc}
|
||||
|
||||
\section{Purpose and main idea}\label{sec:tensor:purpose}
|
||||
|
||||
\code{Tensor<T>} is a dense tensor of arbitrary rank whose indices are organized in \emph{index groups}.
|
||||
Each group has its own index range, offset and permutational symmetry (none, symmetric, antisymmetric,
|
||||
hermitean, antihermitean), and only the permutationally non-equivalent elements of a group are stored.
|
||||
Indices can optionally carry names (\code{INDEXNAME}) and most operations can address indices by name.
|
||||
The class provides element access with automatic sign/conjugation handling, contractions implemented via
|
||||
unwinding to matrices and BLAS \code{gemm}, reordering of indices and groups, (anti)symmetrization by a
|
||||
general permutation algebra, the HOSVD/Tucker decomposition, conversions to and from \code{NRVec},
|
||||
\code{NRMat}, \code{NRSMat} and the dense four-index classes of chapter~\ref{ch:fourindex}, and binary,
|
||||
text and NumPy I/O.
|
||||
|
||||
Typical uses are many-body quantities (amplitudes, integrals, density matrices) where antisymmetry
|
||||
in groups of indices should be exploited for storage.
|
||||
Limitations stated in the source:
|
||||
\begin{itemize}
|
||||
\item the implementation is ``rudimentary''; only \code{Tensor<double>} and \code{Tensor<std::complex<double> >}
|
||||
are instantiated (tensor.cc);
|
||||
\item index types \code{LA_index} and \code{LA_largeindex} are \code{int}, so the total size is limited
|
||||
to $2^{31}$ elements (\code{NRVec} limitation);
|
||||
\item symmetry \emph{between} index groups (e.g.\ $(ij|kl)=(kl|ij)$) is not supported; one would have to
|
||||
nest \code{Tensor<Tensor<T> >} or use the four-index classes;
|
||||
\item covariant and contravariant indices are not distinguished: the header \code{#undef}s
|
||||
\code{LA_TENSOR_INDEXPOSITION}, the code guarded by this macro is not active.
|
||||
\end{itemize}
|
||||
|
||||
\section{Storage and concepts}\label{sec:tensor:storage}
|
||||
|
||||
\subsection{Index groups}
|
||||
|
||||
\begin{lstlisting}
|
||||
typedef class INDEXGROUP {
|
||||
public:
|
||||
int number; // number of indices in the group (>0)
|
||||
int symmetry; // -2,-1,0,1,2, see below
|
||||
LA_index offset; // indices run offset ... offset+range-1
|
||||
LA_index range;
|
||||
bool operator==(const INDEXGROUP &rhs) const; bool operator!=(const INDEXGROUP &rhs) const;
|
||||
} INDEXGROUP;
|
||||
\end{lstlisting}
|
||||
\code{INDEXGROUP} is a plain aggregate without constructor; all members must be set by the user.
|
||||
If the macro \code{LA_TENSOR_ZERO_OFFSET} is defined before including the header, \code{offset} becomes a
|
||||
\code{static const} zero (which saves some arithmetic); otherwise any offset, also negative, is allowed.
|
||||
Index groups are compared memberwise.
|
||||
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}rp{0.3\linewidth}p{0.17\linewidth}p{0.3\linewidth}@{}}
|
||||
\toprule
|
||||
\code{symmetry} & meaning & stored elements & odd permutation gives\\
|
||||
\midrule
|
||||
0 & no symmetry & $r^m$ & ---\\
|
||||
1 & symmetric & $\binom{r+m-1}{m}$ & $+x$\\
|
||||
$-1$ & antisymmetric & $\binom{r}{m}$ & $-x$; zero for a repeated index\\
|
||||
2 & hermitean (complex \code{T} only) & $\binom{r+m-1}{m}$ & $x^*$\\
|
||||
$-2$ & antihermitean (complex \code{T} only) & $\binom{r+m-1}{m}$ & $-x^*$\\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
Here $m$ is \code{number} and $r$ is \code{range}. For real \code{T} the symmetries $\pm 2$ are illegal
|
||||
(\code{canonicalize_shape()} raises an error); a group with a single index always gets symmetry 0.
|
||||
For (anti)hermitean groups the diagonal elements (repeated indices) are stored including their
|
||||
real/imaginary part which should vanish; this is \emph{not} checked automatically, see
|
||||
\code{enforce_hermiticity()} and \code{fulfills_hermiticity()}.
|
||||
The symmetry factors of different groups combine multiplicatively (for complex \code{T} via a small group
|
||||
multiplication table, so that e.g.\ two conjugations cancel).
|
||||
|
||||
\subsection{Storage order}
|
||||
|
||||
The element data are kept in the public member \code{NRVec<T> data}. The \emph{leftmost} index is the least
|
||||
significant, i.e.\ it changes fastest: group 0 is the innermost loop, and inside a non-symmetric group
|
||||
the first index is the fastest. For a group with $m$ indices $I_0,\dots,I_{m-1}$ and offset subtracted, the
|
||||
position inside the group is
|
||||
\begin{align*}
|
||||
&\textstyle\sum_{k} I_k\, r^k && \text{(no symmetry)},\\
|
||||
&\textstyle\sum_{k} \binom{I_k+k}{k+1}, \quad I_0\le I_1\le\dots\le I_{m-1} && \text{(symmetric, (anti)hermitean)},\\
|
||||
&\textstyle\sum_{k} \binom{I_k}{k+1}, \quad I_0< I_1<\dots< I_{m-1} && \text{(antisymmetric)},
|
||||
\end{align*}
|
||||
i.e.\ the canonical (stored) element has nondecreasing (increasing) indices; for $m=2$ this is the
|
||||
usual lower-triangle packing \code{i1*(i1+1)/2+i0} of \code{NRSMat}. The total position is
|
||||
$\sum_g p_g\,\mathtt{cumsizes}[g]$, where \code{groupsizes[g]} is the number of stored elements of group $g$
|
||||
and \code{cumsizes[g]} the product of the sizes of groups $0\dots g-1$.
|
||||
The binomial coefficients are taken from the precomputed table of \code{simplicial()} (miscfunc.h),
|
||||
which limits symmetric groups to fewer than 16 indices and ranges below 1024.
|
||||
|
||||
\subsection{Public data members}
|
||||
|
||||
All data members are public:
|
||||
\begin{lareference}
|
||||
\lafunc{NRVec<INDEXGROUP> shape}{the index groups; \code{shape.size()==0} for a scalar (rank 0).}
|
||||
\lafunc{NRVec<T> data}{the stored elements.}
|
||||
\lafunc{int myrank}{total number of indices (sum of \code{number} over groups), $-1$ for a default-constructed tensor.}
|
||||
\lafunc{NRVec<LA_largeindex> groupsizes; NRVec<LA_largeindex> cumsizes}{redundant, precomputed from \code{shape} by \code{calcsize()}.}
|
||||
\lafunc{NRVec<INDEXNAME> names}{index names in flat order (all indices of group 0, then group 1, \dots);
|
||||
either empty (unnamed tensor) or of length \code{rank()}.}
|
||||
\end{lareference}
|
||||
If \code{shape} is modified directly, \code{calcsize()}, \code{calcrank()} and possibly \code{data.resize()}
|
||||
must be called by the user to restore consistency.
|
||||
|
||||
\subsection{Index types}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{typedef int LA_index; typedef int LA_largeindex}{an index value; a position in \code{data}.}
|
||||
\lafunc{typedef NRVec<LA_index> FLATINDEX}{all indices of an element in flat order.}
|
||||
\lafunc{typedef NRVec<FLATINDEX> SUPERINDEX}{indices structured by groups: \code{I[g][i]}.}
|
||||
\lafunc{typedef NRVec<LA_largeindex> GROUPINDEX}{one position (inside the compressed group storage) per group.}
|
||||
\lafunc{typedef NRMat<LA_index> INDEXMATRIX}{rows are the \code{FLATINDEX}es of all stored elements, in the order of \code{data}.}
|
||||
\lafunc{struct INDEX {int group; int index;}}{position of one index: group number and index number inside the group (both from 0); constructors \code{INDEX()} and \code{INDEX(int g, int i)}, \code{operator==}.}
|
||||
\lafunc{typedef NRVec<INDEX> INDEXLIST}{a list of indices.}
|
||||
\lafunc{struct INDEXNAME {char name[N_INDEXNAME];}}{index name, \code{N_INDEXNAME}=8; constructible from \code{const char *} (copied by \code{strncpy}), so a string literal can be passed wherever an \code{INDEXNAME} is expected; \code{operator=}, \code{==}, \code{!=} compare by \code{strncmp}. Use at most 7 characters so that the name stays NUL-terminated.}
|
||||
\end{lareference}
|
||||
\code{LA_traits} are specialized for \code{INDEXNAME}, \code{INDEXGROUP} and \code{INDEX} as plain data,
|
||||
so that vectors of them can be compared and written/read in binary form (section~\ref{sec:concepts:traits},
|
||||
\ref{sec:concepts:io}).
|
||||
|
||||
\subsection{Reference counting and copy on demand}
|
||||
|
||||
\code{Tensor} itself has no reference counter, but all its large members are \code{NRVec}s, so the
|
||||
scheme of section~\ref{sec:concepts:refcount} applies member by member: the copy constructor and
|
||||
\code{operator=} share \code{data}, \code{shape}, \code{names} etc.\ (shallow copy), and
|
||||
\code{copyonwrite()} detaches all of them. There is \emph{no} \code{operator|=}; a deep copy is made by
|
||||
\code{Tensor<T> b(a); b.copyonwrite();}.
|
||||
Arithmetic members delegate to \code{NRVec} methods which call \code{copyonwrite()}; contractions call
|
||||
\code{data.copyonwrite()}; results of restructuring operations are freshly allocated.
|
||||
An explicit \code{copyonwrite()} is needed before writing through \code{lhs()}, which, like a non-const
|
||||
\code{operator[]}, raises an error in DEBUG builds when the data are shared. The element reading \code{operator()} is
|
||||
\code{const} only, so reading a shared tensor is safe. \code{loopover()} and \code{grouploopover()} pass writable pointers
|
||||
to the callback and therefore detach the data first (one copy if shared, otherwise only a check); use
|
||||
\code{constloopover()}/\code{constgrouploopover()} for reading only, they never copy. \code{renameindex()},
|
||||
\code{permute_inside_group()}, \code{enforce_hermiticity()} and \code{flatten()} also detach what they modify.
|
||||
|
||||
\section{Construction, assignment, conversion}\label{sec:tensor:construction}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{Tensor()}{empty tensor, \code{rank()==-1}.}
|
||||
\lafunc{explicit Tensor(const T &x)}{scalar (rank 0) with value \code{x}.}
|
||||
\lafunc{Tensor(const NRVec<INDEXGROUP> &s); Tensor(const INDEXGROUP &g)}{allocate a tensor of the given shape (data not initialized); a single group can be given directly. The shape is canonicalized (\code{canonicalize_shape()}).}
|
||||
\lafunc{Tensor(const NRVec<INDEXGROUP> &s, const NRVec<INDEXNAME> &newnames); Tensor(const INDEXGROUP &g, const NRVec<INDEXNAME> &newnames)}{the same with index names; \code{newnames} must be empty or of length \code{rank()}.}
|
||||
\lafunc{Tensor(const NRVec<INDEXGROUP> &s, const NRVec<T> &mydata); Tensor(const NRVec<INDEXGROUP> &s, const NRVec<T> &mydata, const NRVec<INDEXNAME> &newnames)}{use \code{mydata} as the data (shared, not copied); its size must match the shape.}
|
||||
\lafunc{Tensor(int xrank, const NRVec<INDEXGROUP> &xshape, const NRVec<LA_largeindex> &xgroupsizes, const NRVec<LA_largeindex> xcumsizes, const NRVec<T> &xdata); Tensor(..., const NRVec<INDEXNAME> &xnames)}{low-level constructor setting all members directly without any check.}
|
||||
\lafunc{Tensor(const Tensor &rhs); Tensor& operator=(const Tensor &rhs)}{shallow copy, see above.}
|
||||
\lafunc{void resize(const NRVec<INDEXGROUP> &s)}{set a new shape, reallocate data (not initialized) and delete the names.}
|
||||
\lafunc{void deallocate()}{release data, shape, names and auxiliary arrays.}
|
||||
\lafunc{void copyonwrite()}{detach all members (deep copy if shared).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Conversions}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{explicit Tensor(const NRVec<T> &x)}{rank-1 tensor, offset 0, data shared with \code{x}.}
|
||||
\lafunc{explicit Tensor(const NRMat<T> &x, bool flat=false)}{rank-2 tensor with a copy of the data and offset 0. A square matrix gives one non-symmetric group of 2 indices unless \code{flat} is true; otherwise two groups of one index. Because of the storage order, \code{t(i,j)==x(j,i)}: tensor index 0 is the column index.}
|
||||
\lafunc{explicit Tensor(const NRSMat<T> &x)}{one group of two indices, data shared with \code{x}; symmetric (symmetry 1) for real, hermitian (symmetry 2) for complex types, as \code{NRSMat} itself (chapter~\ref{ch:smat}); equal to \code{Tensor(NRMat<T>(x))}. If \code{NRSMat} gets the planned \code{hermitian} flag (section~\ref{sec:smat:hermitian}), this constructor has to respect it.}
|
||||
\lafunc{NRMat<T> matrix(int n=1) const}{reinterpret the data as a matrix whose column index runs over the $n$ leftmost index groups (e.g.\ an index made leftmost by \code{unwind_index()}) and whose row index runs over the remaining groups. The matrix \emph{shares} the data with the tensor. \code{Tensor<T>(x).matrix()} reproduces \code{x}.}
|
||||
\lafunc{template<fourindexsymtype S, typename T, typename I> Tensor<T> fourindex2tensor(const fourindex_dense<S,T,I> &f)}{conversion from a dense four-index object (chapter~\ref{ch:fourindex}), see the table below; indices get offset 1.}
|
||||
\lafunc{template<fourindexsymtype S, typename T, typename I> void tensor2fourindex(const Tensor<T> &t, fourindex_dense<S,T,I> &f)}{inverse conversion; shape, ranges, offsets and symmetries are checked.}
|
||||
\end{lareference}
|
||||
|
||||
\code{fourindex2tensor}/\code{tensor2fourindex} are implemented (overloaded) for the following symmetry types.
|
||||
Since the leftmost tensor index is the least significant, \code{f(i,j,k,l)} corresponds to \code{t(k,l,i,j)}
|
||||
(for the types marked * this is equal to \code{t(i,j,k,l)} or \code{t(l,k,j,i)} by symmetry):
|
||||
\begin{center}
|
||||
\begin{tabular}{@{}p{0.5\linewidth}p{0.46\linewidth}@{}}
|
||||
\toprule
|
||||
\code{fourindexsymtype} & tensor shape\\
|
||||
\midrule
|
||||
\code{nosymmetry} & 4 groups of 1 index, range $n$\\
|
||||
\code{twoelectronrealmullikanAB}* & 2 symmetric groups of 2 indices\\
|
||||
\code{twoelectronrealmullikan}* & 2 symmetric groups of 2 indices (the pair symmetry is lost, data expanded)\\
|
||||
\code{T2IjAb_aces} & 4 groups of 1 index, ranges \code{nvra, nvrb, noca, nocb}\\
|
||||
\code{T2ijab_aces} & 2 antisymmetric groups of 2 indices, ranges \code{nvrt}, \code{nocc}\\
|
||||
\code{antisymtwoelectronrealdiracAB}* & 4 groups of 1 index (data expanded)\\
|
||||
\code{antisymtwoelectronrealdirac}* & 2 antisymmetric groups of 2 indices (data expanded)\\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
In the reverse direction the symmetric matrix of pairs is obtained by symmetrizing the tensor data.
|
||||
|
||||
\subsection{NumPy files}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{void numpy_write(const char *name, const char *descr="<f8") const}{write a flat tensor (\code{is_flat()}) as a NumPy \code{.npy} file (format version 1.0, C order, the shape is written in reverse so that the NumPy array index order equals the tensor index order). \code{descr} must match \code{T} (\code{"<f8"} for \code{double}, \code{"<c16"} for complex); this is not checked.}
|
||||
\lafunc{void numpy_read(const char *name)}{read a \code{.npy} file (version 1.0, C or Fortran order) into a flat tensor with offsets 0; the type is not checked.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Element access}\label{sec:tensor:access}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{T operator()(LA_index i1...) const}{element by a variable list of \code{rank()} indices (of type \code{int}); returns the value including the symmetry sign/conjugation, 0 for a repeated index in an antisymmetric group.}
|
||||
\lafunc{T operator()(const FLATINDEX &I) const; T operator()(const SUPERINDEX &I) const}{the same with the indices in a vector.}
|
||||
\lafunc{Signedpointer<T> lhs(LA_index i1...); Signedpointer<T> lhs(const FLATINDEX &I); Signedpointer<T> lhs(const SUPERINDEX &I)}{element as an l-value: the returned proxy supports \code{=}, \code{+=}, \code{-=} (the value is transformed by the sign/conjugation of the permutation before being stored), \code{*=} and \code{/=} (for an element stored as a conjugate the stored value is multiplied by the conjugated factor, so that the element itself is multiplied by the factor). Writes to elements with a repeated index of an antisymmetric group are ignored (a DEBUG build prints a warning once). Call \code{copyonwrite()} first if the tensor may be shared.}
|
||||
\lafunc{LA_largeindex index(int *sign, const SUPERINDEX &I) const; LA_largeindex index(int *sign, const FLATINDEX &I) const}{position in \code{data} of the canonical element and the sign: 1, $-1$, 2 (conjugate), $-2$ (negative conjugate), or 0 and position $-1$ for an element which vanishes by antisymmetry. Index ranges are checked in DEBUG builds.}
|
||||
\lafunc{LA_largeindex vindex(int *sign, LA_index i1, va_list args) const}{the same for a \code{va_list}; ends \code{args}.}
|
||||
\lafunc{SUPERINDEX inverse_index(LA_largeindex s) const}{canonical indices of the element stored at position \code{s} (not efficient).}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Looping over all elements}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{void loopover(void (*callback)(const SUPERINDEX &, T *), bool skipzeros=false)}{call \code{callback} for every stored element with its canonical indices and a pointer into \code{data}. With \code{skipzeros} the index value 0 is skipped in all indices (useful e.g.\ for indices running $\dots,-2,-1,1,2,\dots$ with a negative offset); the pointer passed to the callback always points to the element of the passed indices (with \code{skipzeros} it is computed from the indices).}
|
||||
\lafunc{void constloopover(void (*callback)(const SUPERINDEX &, const T *), bool skipzeros=false) const}{the same for reading.}
|
||||
\lafunc{void grouploopover(void (*callback)(const GROUPINDEX &, T *)); void constgrouploopover(void (*callback)(const GROUPINDEX &, const T *)) const}{loop over all stored elements with the position inside each group's compressed storage instead of the individual indices.}
|
||||
\lafunc{INDEXMATRIX indexmatrix() const}{matrix whose row $s$ is the flat index of \code{data[s]}; allows a simple \code{for} loop over all elements instead of a callback.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Index bookkeeping}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{int myflatposition(int group, int index) const; int myflatposition(const INDEX &i) const}{position of an index in the flat index list.}
|
||||
\lafunc{INDEX myindexposition(int flatindex) const}{inverse of \code{myflatposition}.}
|
||||
\lafunc{int flatposition(int group, int index, const NRVec<INDEXGROUP> &shape); int flatposition(const INDEX &i, const NRVec<INDEXGROUP> &shape); INDEX indexposition(int flatindex, const NRVec<INDEXGROUP> &shape)}{free versions for a given shape.}
|
||||
\lafunc{LA_largeindex subindex(int *sign, const INDEXGROUP &g, const NRVec<LA_index> &I)}{position of the index tuple \code{I} inside the storage of one group, with sign as for \code{index()}.}
|
||||
\lafunc{NRVec<LA_index> inverse_subindex(const INDEXGROUP &g, LA_largeindex s)}{canonical index tuple of position \code{s} in a group.}
|
||||
\lafunc{FLATINDEX superindex2flat(const SUPERINDEX &I)}{concatenate the groups of a superindex.}
|
||||
\lafunc{bool zero_in_index(const FLATINDEX &); bool zero_in_index(const SUPERINDEX &); bool zero_in_index(const INDEXMATRIX &, const LA_largeindex row)}{does any index equal 0 (for index ranges with a negative offset where 0 is excluded)?}
|
||||
\lafunc{bool shares_index(const FLATINDEX &I, const FLATINDEX &J)}{do \code{I} and \code{J} have a common index value?}
|
||||
\end{lareference}
|
||||
|
||||
\section{Arithmetic}\label{sec:tensor:arithmetic}
|
||||
|
||||
Binary operations require identical shapes (\code{laerror} otherwise) and, if both tensors are named,
|
||||
identical names.
|
||||
\begin{lareference}
|
||||
\lafunc{Tensor& operator*=(const T &a); Tensor operator*(const T &a) const; Tensor& operator/=(const T &a); Tensor operator/(const T &a) const}{scaling.}
|
||||
\lafunc{Tensor& operator+=(const Tensor &rhs); Tensor& operator-=(const Tensor &rhs); Tensor operator+(const Tensor &rhs) const; Tensor operator-(const Tensor &rhs) const}{elementwise sum and difference of the stored data.}
|
||||
\lafunc{Tensor& axpy(const T alpha, const Tensor &rhs)}{\code{*this += alpha*rhs}.}
|
||||
\lafunc{Tensor operator-() const}{negation; the result has no index names.}
|
||||
\lafunc{Tensor operator*(const Tensor &rhs) const}{outer product; the indices of \code{rhs} become the less significant (leftmost) ones: \code{(a*b)(J,I)=a(I)*b(J)}. Names are concatenated, so either both or none of the factors must be named.}
|
||||
\lafunc{T dot(const Tensor &rhs) const}{full contraction $\sum_I \overline{a_I} b_I$ (complex conjugation of \code{*this}); antisymmetric groups are accounted for by the factor $m!$, symmetric and (anti)hermitean groups are not supported (\code{laerror}).}
|
||||
\lafunc{typename LA_traits<T>::normtype norm() const}{Frobenius norm of the \emph{stored} data (redundant elements are not counted).}
|
||||
\lafunc{Tensor& conjugateme(); Tensor conjugate() const}{complex conjugation.}
|
||||
\lafunc{void clear()}{set all elements to zero.}
|
||||
\lafunc{void randomize(const typename LA_traits<T>::normtype &x)}{random elements, followed by \code{enforce_hermiticity()}.}
|
||||
\lafunc{void enforce_hermiticity()}{zero the imaginary (hermitean) or real (antihermitean) part of elements with repeated indices in (anti)hermitean groups.}
|
||||
\lafunc{bool fulfills_hermiticity() const}{check the above condition.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Contractions}\label{sec:tensor:contractions}
|
||||
|
||||
Contractions are performed by unwinding the contracted indices of both tensors to the leftmost positions
|
||||
(expanding them to their full range, \code{unwind_index()}), followed by one matrix multiplication
|
||||
(\code{cblas_dgemm}/\code{cblas_zgemm}).
|
||||
The index order of the result is: the remaining indices of the second tensor (\code{rhs}, \code{rhs2}) as
|
||||
the less significant ones, followed by the remaining indices of the first one (\code{*this} or \code{rhs1});
|
||||
each remaining group keeps its symmetry (a group from which an index was contracted keeps its symmetry
|
||||
with one index less). If both tensors are named, the names of the contracted indices must agree and the
|
||||
result is named accordingly.
|
||||
|
||||
In the \code{add...} versions the result is accumulated into \code{*this}:
|
||||
$\mathtt{*this} \leftarrow \beta\,\mathtt{*this} + \alpha\,\mathrm{contraction}$; with \code{doresize=true}
|
||||
\code{*this} is first resized to the result shape (this requires \code{beta==0}), otherwise its shape (and
|
||||
names, if named) must match. \code{conjugate1} conjugates the first tensor, \code{conjugate}/\code{conjugate2}
|
||||
the second one.
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{void addcontraction(const Tensor &rhs1, int group, int index, const Tensor &rhs2, int rhsgroup, int rhsindex, T alpha=1, T beta=1, bool doresize=false, bool conjugate1=false, bool conjugate=false)}{contract one index of \code{rhs1} with one index of \code{rhs2}.}
|
||||
\lafunc{void addcontraction(const Tensor &rhs1, const INDEX &I1, const Tensor &rhs2, const INDEX &I2, ...); void addcontraction(const Tensor &rhs1, const Tensor &rhs2, const INDEXNAME &iname, ...)}{the same with \code{INDEX} positions or with the common index name (same trailing arguments).}
|
||||
\lafunc{Tensor contraction(int group, int index, const Tensor &rhs, int rhsgroup, int rhsindex, T alpha=1, bool conjugate1=false, bool conjugate=false) const; Tensor contraction(const INDEX &I, const Tensor &rhs, const INDEX &RHSI, ...) const; Tensor contraction(const Tensor &rhs, const INDEXNAME &iname, ...) const}{return $\alpha\,$\code{*this}$\cdot$\code{rhs} contracted over one index.}
|
||||
\lafunc{void addcontractions(const Tensor &rhs1, const INDEXLIST &il1, const Tensor &rhs2, const INDEXLIST &il2, T alpha=1, T beta=1, bool doresize=false, bool conjugate1=false, bool conjugate2=false); void addcontractions(const Tensor &rhs1, const Tensor &rhs2, const NRVec<INDEXNAME> &names, ...)}{contract the indices \code{il1[i]} with \code{il2[i]} simultaneously in a single matrix multiplication. Finding an optimal contraction order is the responsibility of the user (contracting one index at a time may be cheaper). For empty lists the outer product is added.}
|
||||
\lafunc{Tensor contractions(const INDEXLIST &il1, const Tensor &rhs2, const INDEXLIST &il2, T alpha=1, bool conjugate1=false, bool conjugate2=false) const; Tensor contractions(const Tensor &rhs2, const NRVec<INDEXNAME> &names, ...) const}{returning versions.}
|
||||
\lafunc{void addgroupcontraction(const Tensor &rhs1, int group, const Tensor &rhs2, int rhsgroup, T alpha=1, T beta=1, bool doresize=false, bool conjugate1=false, bool conjugate=false); Tensor groupcontraction(int group, const Tensor &rhs, int rhsgroup, T alpha=1, bool conjugate1=false, bool conjugate=false) const}{contract over all indices of a whole group without unwinding them; the groups must have equal shape and be non-symmetric or antisymmetric (for antisymmetric groups the factor $m!$ is included, so the result equals the contraction of the flattened tensors).}
|
||||
\lafunc{Tensor innercontraction(const INDEXLIST &il1, const INDEXLIST &il2) const; Tensor innercontraction(const INDEX &i1, const INDEX &i2) const; Tensor innercontraction(const NRVec<INDEXNAME> &nl1, const NRVec<INDEXNAME> &nl2) const; Tensor innercontraction(const INDEXNAME &n1, const INDEXNAME &n2) const}{trace(s) inside one tensor: indices \code{il1[i]} and \code{il2[i]} are set equal and summed over; the result is a scalar tensor if no index remains.}
|
||||
\lafunc{void add_permuted_contractions(const char *antisymmetrizer, const Tensor &rhs1, const Tensor &rhs2, T alpha=1, T beta=1, bool conjugate1=false, bool conjugate2=false)}{for uniquely named tensors: contract all indices whose names occur in both \code{rhs1} and \code{rhs2}, apply the antisymmetrizer given as a string, permute the result to the index order of \code{*this} and accumulate. The string uses the notation of Kucharski and Bartlett: space-separated factors, each a list of index names where \code{/} separates classes and \code{,} separates names within a class; e.g.\ \code{"k/l,m"} is $P(k/lm)=1-P_{kl}-P_{km}$. Indices not mentioned are not permuted; \code{""} means just contraction and reordering. See \code{general_antisymmetrizer} in chapter~\ref{ch:permutation}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Restructuring}\label{sec:tensor:restructuring}
|
||||
|
||||
Permutations are \code{NRPerm<int>} (chapter~\ref{ch:permutation}) and act on group numbers $1\dots n$.
|
||||
Names are permuted together with the indices unless stated otherwise.
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{Tensor permute_index_groups(const NRPerm<int> &p) const}{reorder the data permuting whole index groups: group $i$ of the result is group $p_i$ of the source (both counted from 1).}
|
||||
\lafunc{Tensor permute_index_groups(const NRVec<INDEXNAME> &names) const}{the same with the permutation given by the requested order of the groups' first index names (for a flat tensor: of all names).}
|
||||
\lafunc{Tensor unwind_index_group(int group) const}{move a group to the leftmost (least significant) position, the others keep their order.}
|
||||
\lafunc{Tensor unwind_index(int group, int index) const; Tensor unwind_index(const INDEX &I) const; Tensor unwind_index(const INDEXNAME &N) const}{take one index out of its group and make it a separate leftmost non-symmetric group, expanded to its full range; the rest of the group keeps its symmetry. Without data reorganization if the index is already first in a non-symmetric group 0.}
|
||||
\lafunc{Tensor unwind_indices(const INDEXLIST &il) const; Tensor unwind_indices(const NRVec<INDEXNAME> &names) const}{the same for several indices, which become groups $0,1,\dots$ in the listed order.}
|
||||
\lafunc{Tensor flatten(int group= -1) const}{expand the given group (all groups for $-1$) into single non-symmetric indices, keeping the flat index order and names.}
|
||||
\lafunc{Tensor merge_indices(const INDEXLIST &il, int symmetry=0) const; Tensor merge_indices(const NRVec<INDEXNAME> &nl, int symmetry=0) const}{opposite of \code{flatten}: the listed indices (equal range and offset) form a new leftmost group of the given symmetry; for a nonzero symmetry the data are (anti)symmetrized, i.e.\ averaged over the permutations with the appropriate signs.}
|
||||
\lafunc{Tensor merge_index_groups(const NRVec<int> &groups) const}{permute the listed non-symmetric groups to the front (in the given order, the remaining groups follow in their original order) and merge them into one group; group numbers from 0, repeated numbers raise an error.}
|
||||
\lafunc{void split_index_group(int group)}{in place, formal (no data movement): split a non-symmetric group into single-index groups.}
|
||||
\lafunc{void split_index_group1(int group)}{in place, formal: split off the first index of a non-symmetric group.}
|
||||
\lafunc{void merge_adjacent_index_groups(int groupfrom, int groupto)}{in place, formal: merge consecutive non-symmetric groups of equal range and offset into one.}
|
||||
\lafunc{void permute_inside_group(int group, const NRPerm<int> &p, bool inverse=false)}{for a group with nonzero symmetry: apply the sign/conjugation of the permutation parity to the data and permute the corresponding names.}
|
||||
\lafunc{Tensor subtensor1(int i) const}{the slice for the value \code{i} of the last (most significant) index, which must form a group of its own.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Permutation algebra, decompositions and transformations}\label{sec:tensor:other}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{void apply_permutation_algebra(const Tensor &rhs, const PermutationAlgebra<int,T> &pa, bool inverse=false, T alpha=1, T beta=0, bool conjugate_by_parity=false)}{$\mathtt{*this} \leftarrow \beta\,\mathtt{*this}$, then for all stored elements $I$ of \code{*this}: $\mathtt{this}(I) \mathrel{+}= \alpha \sum_P c_P\, \mathtt{rhs}(P(I))$, with $P$ acting on the flat index list (inverse permutations if \code{inverse}); with \code{conjugate_by_parity} odd permutations take the complex conjugate. Can represent general (anti)symmetrizers, e.g.\ \code{general_antisymmetrizer} (chapter~\ref{ch:permutation}). \code{*this} may have a symmetry which \code{rhs} lacks. The names of \code{*this} are taken from (or checked against) the names of \code{rhs} permuted by the first permutation. Element-by-element, not optimally efficient.}
|
||||
\lafunc{void apply_permutation_algebra(const NRVec<Tensor> &rhsvec, const PermutationAlgebra<int,T> &pa, bool inverse=false, T alpha=1, T beta=0)}{the same with \code{rhs} being the outer product of the tensors in \code{rhsvec}, which is not formed explicitly (e.g.\ Grassmann products building a density matrix from cumulants); rather inefficient.}
|
||||
\lafunc{NRVec<NRMat<T> > Tucker(typename LA_traits<T>::normtype thr=1e-12, bool inverseorder=false)}{HOSVD/Tucker decomposition: \code{*this} is replaced by the flat core tensor and the factor matrices (one per index, columns orthonormal) are returned; singular values below \code{thr} are dropped, which reduces the corresponding range of the core.}
|
||||
\lafunc{Tensor inverseTucker(const NRVec<NRMat<T> > &x, bool inverseorder=false) const}{rebuild the tensor from the core (\code{*this}) and the matrices returned by \code{Tucker} with the same \code{inverseorder}.}
|
||||
\lafunc{Tensor linear_transform(const NRVec<NRMat<T> > &x) const}{transform every index of group $g$ by the matrix \code{x[g]} (\code{x[g].ncols()} must equal the range; the new range is \code{x[g].nrows()}), preserving the group symmetries: $t'_{\dots a \dots} = \sum_i x_{ai}\, t_{\dots i\dots}$.}
|
||||
\lafunc{Tensor linear_transform(const int g, const Tensor<T> &x) const; Tensor linear_transform(const int g, const NRMat<T> &x) const}{transform only group \code{g}, keeping its position and symmetry; \code{x} must be a flat rank-2 tensor (useful for raising/lowering indices) or a matrix.}
|
||||
\end{lareference}
|
||||
For the \code{Tensor} argument the contraction runs over its index 0, i.e.\ $t'_{\dots a\dots}=\sum_i x(i,a)\,t_{\dots i\dots}$ in tensor
|
||||
indexing, which is the same as the matrix convention above for \code{x} built by \code{Tensor<T>(matrix,true)}.
|
||||
|
||||
\section{Names and shape queries}\label{sec:tensor:names}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{bool is_named() const}{are names set? (\code{laerror} if their number differs from the rank).}
|
||||
\lafunc{bool is_uniquely_named() const}{named without repetitions.}
|
||||
\lafunc{void defaultnames(const char *basename="i")}{set names \code{i000}, \code{i001}, \dots}
|
||||
\lafunc{int findflatindex(const INDEXNAME nam) const}{flat position of the named index, $-1$ if not found (\code{laerror} if unnamed).}
|
||||
\lafunc{INDEX findindex(const INDEXNAME nam) const; NRVec<INDEX> findindexlist(const NRVec<INDEXNAME> &names) const}{position(s) of named indices; \code{laerror} if not found.}
|
||||
\lafunc{void renameindex(const INDEXNAME namfrom, const INDEXNAME nameto)}{rename an index (see the copy-on-write remark above).}
|
||||
\lafunc{int rank() const; LA_largeindex size() const}{number of indices; number of stored elements.}
|
||||
\lafunc{int calcrank(); LA_largeindex calcsize()}{recompute \code{myrank}, resp.\ \code{groupsizes}, \code{cumsizes} from \code{shape} and return the rank, resp.\ size.}
|
||||
\lafunc{void canonicalize_shape()}{set symmetry 0 for single-index groups; check the symmetry codes.}
|
||||
\lafunc{bool is_flat() const; bool is_compressed() const; bool has_symmetry() const; bool has_hermiticity() const}{all groups have one index; some multi-index group has nonzero symmetry; some group has nonzero symmetry; complex \code{T} and some group is (anti)hermitean.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Input and output}\label{sec:tensor:io}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{void put(int fd, bool with_names=false) const; void get(int fd, bool with_names=false)}{binary I/O of shape, auxiliary arrays, data and optionally names (section~\ref{sec:concepts:io}); with \code{with_names} the names are always written (an empty list if the tensor has none), so \code{put} and \code{get} must use the same \code{with_names}.}
|
||||
\lafunc{template<typename T> std::ostream & operator<<(std::ostream &s, const Tensor<T> &x)}{text output: the shape, the names, then one line per stored element with its flat indices and value.}
|
||||
\lafunc{template<typename T> std::istream & operator>>(std::istream &s, Tensor<T> &x)}{reads the format written by \code{<<}.}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const INDEXGROUP &x); std::istream & operator>>(std::istream &s, INDEXGROUP &x)}{\code{number symmetry offset range}.}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const INDEXNAME &x); std::istream & operator>>(std::istream &s, INDEXNAME &x)}{the name as a word.}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const INDEX &x); std::istream & operator>>(std::istream &s, INDEX &x)}{\code{group index}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Helpers in the header}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template<typename T> T signeddata(const int sgn, const T &data)}{apply a sign code ($\pm1$, $\pm2$ = with conjugation, 0 gives 0) to a value.}
|
||||
\lafunc{template<typename T> class Signedpointer}{proxy returned by \code{lhs()}, see section~\ref{sec:tensor:access}.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Examples}\label{sec:tensor:examples}
|
||||
|
||||
An antisymmetric rank-3 tensor:
|
||||
\begin{lstlisting}
|
||||
int n=4;
|
||||
INDEXGROUP g;
|
||||
g.number=3; g.symmetry= -1; g.offset=0; g.range=n;
|
||||
Tensor<double> t(g); // stores n(n-1)(n-2)/6 elements
|
||||
t.clear();
|
||||
t.lhs(0,1,2) = 1.; // implies t(1,0,2)=-1 etc.
|
||||
t.lhs(2,1,3) = 5.; // stored as t(1,2,3) = -5
|
||||
const Tensor<double> &tc=t;
|
||||
cout << tc(1,2,3) << " " << tc(3,2,1) << " " << tc(1,1,2) << endl; // -5 5 0
|
||||
Tensor<double> tf = t.flatten(); // the same tensor stored in full, n^3 elements
|
||||
\end{lstlisting}
|
||||
|
||||
Contractions of named tensors:
|
||||
\begin{lstlisting}
|
||||
INDEXGROUP s; s.number=1; s.symmetry=0; s.offset=0; s.range=n;
|
||||
NRVec<INDEXGROUP> shape(4); for(int i=0; i<4; ++i) shape[i]=s;
|
||||
Tensor<double> x(shape), y(shape);
|
||||
x.randomize(1.); y.randomize(1.);
|
||||
INDEXNAME xn[4]={"i","j","k","l"}; x.names=xn;
|
||||
INDEXNAME yn[4]={"m","n","j","i"}; y.names=yn;
|
||||
INDEXNAME cn[2]={"i","j"};
|
||||
// z(m,n,k,l) = sum_ij x(i,j,k,l) y(m,n,j,i); remaining indices of y come first
|
||||
Tensor<double> z = x.contractions(y,NRVec<INDEXNAME>(cn));
|
||||
// result in a prescribed index order, antisymmetrized in k,l:
|
||||
// w(k,l,m,n) = z(m,n,k,l) - z(m,n,l,k)
|
||||
Tensor<double> w(shape);
|
||||
INDEXNAME wn[4]={"k","l","m","n"}; w.names=wn;
|
||||
w.add_permuted_contractions("k/l",x,y,1.,0.);
|
||||
\end{lstlisting}
|
||||
|
||||
Tucker decomposition:
|
||||
\begin{lstlisting}
|
||||
NRVec<INDEXGROUP> sh(3);
|
||||
for(int i=0; i<3; ++i) {sh[i].number=1; sh[i].symmetry=0; sh[i].offset=0; sh[i].range=n+i;}
|
||||
Tensor<double> a(sh); a.randomize(1.);
|
||||
Tensor<double> a0(a); a0.copyonwrite(); // keep the original
|
||||
NRVec<NRMat<double> > u = a.Tucker(1e-12); // a now holds the core tensor
|
||||
Tensor<double> b = a.inverseTucker(u); // b == a0
|
||||
Tensor<double> c = a.linear_transform(u); // the same
|
||||
\end{lstlisting}
|
||||
|
||||
Conversion from a dense four-index object (chapter~\ref{ch:fourindex}):
|
||||
\begin{lstlisting}
|
||||
fourindex_dense<nosymmetry,double,int> f(n);
|
||||
f.randomize(1.);
|
||||
Tensor<double> t4 = fourindex2tensor(f); // f(i,j,k,l) == t4(k,l,i,j), indices from 1
|
||||
fourindex_dense<nosymmetry,double,int> ff;
|
||||
tensor2fourindex(t4,ff);
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}\label{sec:tensor:pitfalls}
|
||||
|
||||
\begin{itemize}
|
||||
\item The leftmost index is the fastest; a tensor built from a matrix is its transpose in index notation:
|
||||
\code{Tensor<T>(x)(i,j)==x(j,i)}.
|
||||
\item \code{norm()}, \code{operator+=} etc.\ act on the stored data only; the norm of a compressed tensor
|
||||
differs from the norm of its flattened form.
|
||||
\item Results of contractions have the remaining indices of the \emph{second} tensor first.
|
||||
\item \code{lhs()} writes require an unshared tensor: call \code{copyonwrite()} after copying.
|
||||
There is no \code{operator|=}.
|
||||
\item Default-constructed \code{INDEXGROUP}s are uninitialized; set all members.
|
||||
\item Operations based on \code{loopover()} use static global variables to pass data to the callbacks;
|
||||
they are neither reentrant nor thread safe.
|
||||
\item Index names longer than 7 characters are not NUL-terminated.
|
||||
\item An outer product of a named and an unnamed tensor raises an error (inconsistent number of names).
|
||||
\end{itemize}
|
||||
@@ -0,0 +1,346 @@
|
||||
\chapter{Utilities}\label{ch:utilities}
|
||||
|
||||
This chapter documents the auxiliary parts of \LA{} which are not linear algebra classes: a scratch-file storage of
|
||||
fixed-size records used by the out-of-core variants of the iterative solvers, the random number generators used by all
|
||||
\code{randomize()} functions, a few special functions and combinatorial numbers, small self-contained algorithms for
|
||||
embedded use, the reference of the error handling functions, the single include file \header{la.h} and the version
|
||||
string, elementary number theory and graph algorithms. The small classes Vec3/Mat3, Quaternion and bitvector are described in
|
||||
chapters~\ref{ch:vecmat3}, \ref{ch:quaternion} and \ref{ch:bitvector}.
|
||||
|
||||
%=====================================================================
|
||||
\section{Scratch-file storage: AuxStorage}\label{sec:utilities:auxstorage}
|
||||
\laheader{auxstorage.h}
|
||||
|
||||
\subsection{Purpose and storage}
|
||||
\code{AuxStorage<T>} stores numbered records of equal length in a binary file, using unformatted \code{read}/\code{write}
|
||||
of the raw element array. It is used by \code{davidson}, \code{lanczos}, \code{gmres} and \code{DIIS}
|
||||
(chapter~\ref{ch:iterative}) to keep the subspace vectors on disk when the in-core option is not selected, and to write
|
||||
eigenvectors to a named file. The record length (in bytes) is fixed by the first \code{put()}; all later records must have
|
||||
the same size. Record \code{pos} starts at byte offset \code{pos*recl}, so records may be written in any order; reading
|
||||
a never written record lying before the end of the file gives zeros (a hole of the file), reading past the end of the file
|
||||
calls \code{laerror}.
|
||||
|
||||
The class itself holds only a file descriptor and the file name; it is \emph{not} reference counted. The objects stored are
|
||||
ordinary \LA{} objects: \code{get()} calls \code{x.copyonwrite()} before reading into \code{x}, \code{put()} only reads
|
||||
its argument.
|
||||
|
||||
The comment in the header gives the limits of the design:
|
||||
\begin{itemize}
|
||||
\item \code{T} must be plain data (\code{double}, \code{std::complex<double>}, \dots); it will not work if \code{T} is a
|
||||
class with dynamically allocated components (e.g.\ \code{NRVec<NRVec<double> >} or \code{NRVec<NRMat<double> >}),
|
||||
since only the bytes of the top-level array are written;
|
||||
\item it cannot be implemented for \code{SparseMat}, which lacks a fixed record length;
|
||||
\item for more complex I/O the \code{put()} and \code{get()} methods of the individual classes are to be used
|
||||
(section~\ref{sec:concepts:io}).
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Reference}
|
||||
\begin{lareference}
|
||||
\lafunc{template<typename T> class AuxStorage}{Scratch file of records whose elements are of type \code{T}.}
|
||||
\lafunc{AuxStorage(void)}{Creates and opens a new unique temporary file \header{AUX_XXXXXX} in \code{\$TMPDIR} if it is set,
|
||||
otherwise in the current working directory, by \code{mkostemp()} (atomic creation, with \code{O_LARGEFILE}), and unlinks it at
|
||||
once: the open descriptor keeps it usable, and its disk space is released when it is closed, also after a crash.
|
||||
\code{laerror} if the file cannot be created.}
|
||||
\lafunc{AuxStorage(const char *name)}{Opens the file \code{name} for reading and writing; an existing file of this name is
|
||||
first deleted (\code{unlink}), so the file always starts empty. The file is \emph{not} deleted by the destructor (used to keep
|
||||
eigenvectors). \code{laerror} if it cannot be opened or if the name has 32 or more characters.}
|
||||
\lafunc{~AuxStorage(void)}{Closes the file; deletes it if it was created by the default constructor.}
|
||||
\lafunc{AuxStorage(const AuxStorage &) = delete; AuxStorage &operator=(const AuxStorage &) = delete}{Not copyable (a copy would close
|
||||
the file twice); use pointers, as the iterative solvers do.}
|
||||
\lafunc{void put(const NRVec<T> &x, const int pos)}{Writes the elements of \code{x} as record \code{pos} (counted from 0).
|
||||
The first \code{put} defines the record length; \code{laerror} if a later object has a different size in bytes.}
|
||||
\lafunc{void get(NRVec<T> &x, const int pos) const}{Reads record \code{pos} into \code{x}, which must be already allocated
|
||||
with the right size. Calls \code{x.copyonwrite()}. \code{laerror} if nothing has been written yet, if the size of \code{x} in
|
||||
bytes differs from the record length, or if the record cannot be read in full.}
|
||||
\lafunc{void put(const NRMat<T> &x, const int pos); void get(NRMat<T> &x, const int pos) const}{The same for a dense
|
||||
matrix (record of \code{nrows()*ncols()} elements in the storage order of \code{NRMat}).}
|
||||
\lafunc{void put(const NRSMat<T> &x, const int pos); void get(NRSMat<T> &x, const int pos) const}{The same for a packed
|
||||
symmetric matrix (record of $n(n+1)/2$ elements).}
|
||||
\end{lareference}
|
||||
Only the byte size is compared, so a record written as a vector can be read into a matrix of the same number of elements.
|
||||
|
||||
\subsection{Example}
|
||||
\begin{lstlisting}
|
||||
AuxStorage<double> st; //temporary file AUX_xxxxxx in $TMPDIR or the working directory
|
||||
NRVec<double> v(1.,100), w(100);
|
||||
st.put(v,3); //record 3, record length 100 doubles
|
||||
st.get(w,3); //w must be allocated with 100 elements
|
||||
\end{lstlisting}
|
||||
|
||||
\subsection{Pitfalls}
|
||||
\begin{itemize}
|
||||
\item The target of \code{get()} must be preallocated with exactly the record size (otherwise \code{laerror}).
|
||||
\item The temporary file is placed in \code{\$TMPDIR}, or in the current working directory if \code{TMPDIR} is not set; it is
|
||||
not visible in the directory listing (unlinked), but occupies disk space there while the object exists.
|
||||
\item The file position is shared: \code{get()} and \code{put()} use \code{lseek64} followed by \code{read}/\code{write}, so
|
||||
one object must not be used concurrently from several threads.
|
||||
\item \code{lseek64}, \code{off64_t}, \code{O_LARGEFILE} and \code{mkostemp} are Linux (glibc) specific.
|
||||
\item A short write is not detected (only a negative return value of \code{write} is checked).
|
||||
\end{itemize}
|
||||
|
||||
%=====================================================================
|
||||
\section{Random numbers}\label{sec:utilities:random}
|
||||
\laheader{la_random.h, la_random.cc}
|
||||
|
||||
All random-number consumers of the library (\code{randomize()} of \code{NRVec}, \code{NRMat}, \code{NRSMat}, \code{Vec3},
|
||||
\code{Mat3}, \code{Quaternion}, \code{bitvector}, random permutations, \dots) obtain random numbers through four macros,
|
||||
so that the generator can be replaced without changing the library. \header{la_random.h} is included by
|
||||
\header{la_traits.h}, i.e.\ by every \LA{} header.
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{double randdouble()}{Uniform in $[0,1)$: \code{random()/(1.+RAND_MAX)}.}
|
||||
\lafunc{double randdoublesigned()}{Uniform in $[-1,1)$: \code{2.*random()/(1.+RAND_MAX)-1.}.}
|
||||
\lafunc{unsigned int randint32()}{32 random bits from two calls of \code{random()} (which gives 31 bits with glibc).}
|
||||
\lafunc{uint64_t randint64()}{64 random bits from three calls of \code{random()} ($31+31+2$ bits).}
|
||||
\lafunc{RANDDOUBLE, RANDDOUBLESIGNED, RANDINT32, RANDINT64}{Macros used at all call sites; unless defined before the
|
||||
inclusion of \header{la_random.h} they expand to \code{LA::randdouble}, \code{LA::randdoublesigned}, \code{LA::randint32},
|
||||
\code{LA::randint64}.}
|
||||
\lafunc{WEAK_SYMBOL}{Expands to \code{__attribute__((weak))} with GNU compilers, empty otherwise.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection{Replacing the generator}
|
||||
The four functions are defined in \header{la_random.cc} as \emph{weak symbols}. A program can replace any of them by its own
|
||||
(strong) definition with the same signature in namespace \code{LA}; the linker then uses the user's function also in the
|
||||
calls made from inside the precompiled library, and the remaining default functions stay available:
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
namespace LA {
|
||||
double randdoublesigned() {return my_generator.uniform(-1.,1.);} //replaces the library's weak definition
|
||||
}
|
||||
\end{lstlisting}
|
||||
Defining one of the macros (e.g.\ \code{-DRANDDOUBLE=myrand}) affects only code compiled by the user (inline and
|
||||
template code in the headers), not the calls in the compiled library; the weak-symbol replacement is therefore the
|
||||
mechanism to use for a consistent change.
|
||||
|
||||
\subsection{Seeding}
|
||||
The default functions use the C library generator \code{random()}, so they are seeded by \code{srandom(seed)}; with glibc
|
||||
\code{srand()} seeds the same generator (as used in \header{testoptimize.cc} and \header{t.cc}). Without seeding the sequence
|
||||
is the same in every run. A seed from the system can be taken e.g.\ from \header{/dev/random}, as in \header{t.cc}:
|
||||
\begin{lstlisting}
|
||||
int seed;
|
||||
int f=open("/dev/random",O_RDONLY);
|
||||
if(sizeof(int)!=read(f,&seed,sizeof(int))) laerror("cannot read /dev/random");
|
||||
close(f);
|
||||
srand(seed);
|
||||
\end{lstlisting}
|
||||
The default generators are not thread safe in the sense of reproducibility (they share the global state of
|
||||
\code{random()}); a thread-safe or higher-quality generator is to be supplied by replacement.
|
||||
|
||||
%=====================================================================
|
||||
\section{Special functions and combinatorial numbers}\label{sec:utilities:miscfunc}
|
||||
\laheader{miscfunc.h, miscfunc.cc}
|
||||
|
||||
Plain functions in namespace \code{LA} for \code{double} and 64-bit unsigned integers. They keep internal static tables
|
||||
(filled on demand), so they are not thread safe on the first calls.
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{const double c_euler}{Euler--Mascheroni constant $\gamma=0.5772156649\dots$}
|
||||
\lafunc{unsigned long long factorial(const int n)}{$n!$ from a table, $0\le n\le 20$; \code{laerror} for $n<0$ and for
|
||||
$n>20$ (overflow of 64 bits). Exact; used where integer arithmetic is needed (permutation ranks and counts, \code{binom}).}
|
||||
\lafunc{double double_factorial(const int n)}{$n!$ (not the double factorial $n!!$) in \code{double} precision,
|
||||
$0\le n\le 170$: \code{factorial(n)} for $n\le20$, beyond from a table extended on demand. \code{laerror} for $n<0$ and
|
||||
$n>170$. Used by \code{bernoulli_number}, \code{zeta} and \code{gamma}.}
|
||||
\lafunc{double factorialln(const int n)}{$\ln n!$ for any $n\ge0$: tabulated up to $n=30$, accumulated (and cached) up to
|
||||
$n=180$, Stirling series with Bernoulli-number corrections beyond. \code{laerror} for $n<0$.}
|
||||
\lafunc{unsigned long long binom(int n, int k)}{Binomial coefficient $\binom nk$; 0 for $k<0$ or $k>n$. With $k$
|
||||
replaced by $\min(k,n-k)$, computed exactly by the recurrence $\binom{n-k+i}{i}=\binom{n-k+i-1}{i-1}(n-k+i)/i$, dividing
|
||||
by the common factor before each multiplication, so that no intermediate exceeds the result; cached for $n\le100$.
|
||||
\code{laerror} if the result does not fit into 64 bits (all $\binom nk$ fit for $n\le67$).}
|
||||
\lafunc{unsigned long long longpow(unsigned long long x, int i)}{$x^i$ by binary powering (modulo $2^{64}$, overflow not
|
||||
detected); returns 0 for $i<0$.}
|
||||
\lafunc{double zeta(double x)}{Riemann zeta function for real $x\ne1$. Integer arguments: exact values from Bernoulli
|
||||
numbers for $x\le0$ and $x=2,4,6,8$; otherwise (also for other integers): the defining sum for $x\ge9.1$, the
|
||||
Euler--Maclaurin summation formula with $N=10$ and ten Bernoulli terms for $-1.02\le x<9.1$ (including the neighbourhood of
|
||||
the pole, whose term $1/(x-1)$ is exact), and the reflection formula $\zeta(x)=2(2\pi)^{x-1}\sin(\pi x/2)\Gamma(1-x)\zeta(1-x)$
|
||||
for $x<-1.02$. Relative accuracy about $10^{-13}$ or better (except close to the trivial zeros). \code{laerror} for $x=1$.}
|
||||
\lafunc{double gamma(double x)}{$\Gamma(x)$ for $x\le171$: $(x-1)!$ by \code{double_factorial} for integers, otherwise
|
||||
upward recursion to $x\ge8$ and the Stirling series. \code{laerror} for $x>171$ and for non-positive integers.}
|
||||
\lafunc{double gammln(double x)}{$\ln|\Gamma(x)|$, for integers via \code{factorialln}; \code{laerror} for non-positive
|
||||
integers.}
|
||||
\lafunc{double bernoulli_number(int n)}{Bernoulli number $B_n$ with $B_1=-\frac12$, $B_{2k+1}=0$ for $k\ge1$; $B_{2k}$ from
|
||||
a table up to $B_{10}$, otherwise from $\zeta(2k)$ by $B_{2k}=(-1)^{k+1}\,2\,(2k)!\,\zeta(2k)/(2\pi)^{2k}$ (cached).
|
||||
\code{laerror} for $n<0$.}
|
||||
\lafunc{unsigned long long simplicial(int d, int n)}{$d$-dimensional simplicial (figurate) number
|
||||
$s(d,n)=\binom{n+d-1}{d}$, i.e.\ the number of non-decreasing $d$-tuples of indices from $n$ values (used for the indexing of
|
||||
symmetric tensors, chapter~\ref{ch:tensor}). $s(d,n)=0$ for $n\le0$, $1$ for $d=0$ or $n=1$, $n$ for $d=1$. Computed by the
|
||||
recurrence $s(d,n)=s(d,n-1)+s(d-1,n)$ into a table growing on demand; \code{laerror} for $d\ge16$ or $n\ge1024$.}
|
||||
\lafunc{int inverse_simplicial(int d, unsigned long long s)}{Largest $n$ with \code{simplicial(d,n)}${}\le s$ (Newton
|
||||
iteration on the polynomial $s(d,n)$ followed by an integer correction); 0 for $s=0$. \code{laerror} for $d\ge16$.}
|
||||
\end{lareference}
|
||||
|
||||
%=====================================================================
|
||||
\section{Simple algorithms for embedded use}\label{sec:utilities:simple}
|
||||
\laheader{simple.h, simple.cc}
|
||||
|
||||
\header{simple.h} contains small algorithms on C arrays of compile-time size, independent of BLAS, LAPACK and of the
|
||||
rest of \LA{}. It is intended for embedded computers and can be compiled separately; it is installed with the library but
|
||||
\emph{not} included by \header{la.h}. Everything is in namespace \code{LA_Simple}. With \code{AVOID_STDSTREAM} defined,
|
||||
\header{iostream} is not included and the stream operator is omitted. \header{simple.cc} is currently empty. The element
|
||||
type \code{T} is a real floating-point type (\code{abs} and comparisons are used for pivoting).
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{template<typename T, int n, int m> T simple_gaussj(T (&a)[n][n], T (&b)[m][n])}{Gauss--Jordan elimination with
|
||||
full pivoting. On input \code{a} is the $n\times n$ matrix $A$ and \code{b} contains $m$ right-hand sides as its
|
||||
\emph{rows} \code{b[l][0..n-1]}. On return \code{a} contains $A^{-1}$, the rows of \code{b} the solutions, and the
|
||||
function returns $\det A$. For a singular matrix it returns 0 and \code{a}, \code{b} are left partially transformed.
|
||||
\code{T (&a)[n][n]} binds e.g.\ to \code{Mat3<double>::elements()}.}
|
||||
\lafunc{template<typename T, int n> class simple_linfit}{Linear least-squares fit $y\approx\sum_{i=0}^{n-1}c_if_i(x)$
|
||||
by accumulating the normal equations; the data points are not stored. Public members: \code{T fitmat[n][n]},
|
||||
\code{T rhsmat[1][n]} (accumulated $\sum f_if_j$, $\sum f_iy$), \code{T fitcoef[n]} (result $c_i$), \code{int npoints}.}
|
||||
\lafunc{simple_linfit()}{Empty fit, coefficients zero.}
|
||||
\lafunc{void clear(bool keepresults=false)}{Discard the accumulated data (and the coefficients unless \code{keepresults}).}
|
||||
\lafunc{void input(const T (&funcs)[n], const T y)}{Add a data point given by the values $f_i(x)$ and $y$.}
|
||||
\lafunc{T solve(const T preserve=0)}{Solves the normal equations by \code{simple_gaussj}, stores the coefficients in
|
||||
\code{fitcoef} and returns the determinant of the normal matrix (0 if singular). If fewer than $n$ points have been input it
|
||||
returns 0 and leaves \code{fitcoef} unchanged. With \code{preserve==0} the accumulated data are cleared afterwards (the next
|
||||
fit starts from scratch); with \code{preserve!=0} they are kept and, if \code{preserve!=1}, multiplied by \code{preserve},
|
||||
which gives an exponentially decaying weight of old points in a running fit (\code{npoints} is not reset then).}
|
||||
\lafunc{const T &operator[](int i) const}{Coefficient $c_i$.}
|
||||
\lafunc{std::ostream& operator<<(std::ostream &o, const simple_linfit<T,n> &f)}{Prints the coefficients separated by spaces.}
|
||||
\end{lareference}
|
||||
|
||||
Example (from \header{t.cc}), fit of a parabola:
|
||||
\begin{lstlisting}
|
||||
simple_linfit<double,3> fit;
|
||||
double funcs[3];
|
||||
for(int i=0; i<100; ++i)
|
||||
{
|
||||
double x = 10*RANDDOUBLESIGNED();
|
||||
funcs[0]=1; funcs[1]=x; funcs[2]=x*x;
|
||||
fit.input(funcs, 2*x*x-3*x+1);
|
||||
}
|
||||
double det = fit.solve();
|
||||
cout <<"det= "<<det<<" fit "<<fit<<endl; //1 -3 2
|
||||
\end{lstlisting}
|
||||
\lawarning{\header{simple.h} defines and afterwards undefines a macro \texttt{SWAP}; a user macro of this name defined before
|
||||
the inclusion is lost.}
|
||||
|
||||
%=====================================================================
|
||||
\section{Error handling reference}\label{sec:utilities:errors}
|
||||
\laheader{laerror.h, laerror.cc}
|
||||
|
||||
The concept (errors are reported by \code{laerror}, which throws an exception after printing diagnostics; checks enabled by
|
||||
\code{DEBUG}) is described in section~\ref{sec:concepts:errors}. All entities are in namespace \code{LA}.
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{laerror(const char *message)}{Macro expanding to \code{LA::laerror2(message, __PRETTY_FUNCTION__)} with GNU
|
||||
compilers and \code{LA::laerror2(message, __func__)} otherwise. Usable in user code, also outside namespace \code{LA}.}
|
||||
\lafunc{void laerror2(const char *message, const char *function)}{Flushes and prints a newline on \code{std::cout} and
|
||||
\code{std::cerr}, then on both streams \code{LA:ERROR - message} and \code{Called from function} (``unspecified'' if
|
||||
\code{message} is \code{NULL}); the system error message by \code{perror} if \code{errno} is nonzero; the command line (from
|
||||
\header{/proc/self/cmdline}); user, host name and working directory (via \code{system()}); a listing of the working
|
||||
directory if \code{LA_error_list_files} is true. Finally it throws \code{LAerror(message)}.}
|
||||
\lafunc{class LAerror : public std::exception}{Exception thrown by \code{laerror2}. Public member \code{std::string msg}, a copy
|
||||
of the message (so it stays valid also for messages built in local buffers, e.g.\ by the BLAS error handlers); constructor
|
||||
\code{LAerror(const char *s)} (\code{NULL} gives ``unspecified''); \code{what()} returns the message, so an uncaught error or a
|
||||
handler catching \code{std::exception} shows it.}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const LAerror &x)}{Prints \code{x.msg}.}
|
||||
\lafunc{bool LA_error_list_files}{Default \code{false}; if set, \code{laerror2} also runs \code{ls -l -t .}.}
|
||||
\lafunc{bool _LA_count_check}{Default \code{true}. In \code{DEBUG} builds the non-const \code{operator[]} and
|
||||
\code{operator()} of \code{NRVec}, \code{NRMat} and \code{NRSMat} call \code{laerror} if the reference count is larger
|
||||
than 1 (section~\ref{sec:concepts:refcount}) only while this variable is true; setting it to false switches this check off
|
||||
(the check is in inline header code, so it affects code compiled with \code{DEBUG}).}
|
||||
\lafunc{const bool _LA_warn_empty_copyonwrite}{Compile-time \code{false} in the library; if it were true,
|
||||
\code{copyonwrite()} of an empty object would print a warning. Not user-adjustable.}
|
||||
\lafunc{int traceback(int flags)}{Prints a stack trace of the running process by forking \header{/usr/bin/gdb} in batch
|
||||
mode attached to the process (allowed by \code{prctl(PR_SET_PTRACER)}); output goes to \code{stderr}. \code{flags} is
|
||||
currently unused. Linux specific; requires gdb. C linkage.}
|
||||
\lafunc{void sigtraceback(int sig, int flags)}{Installs a one-shot handler for signal \code{sig} which flushes
|
||||
\code{stdout}, calls \code{traceback(flags)} and re-raises the signal (default action, e.g.\ core dump); if the handler is
|
||||
entered again it calls \code{_exit(1)}. C linkage.}
|
||||
\lafunc{int endianity()}{Index of the byte holding the least significant byte of an \code{int}: 0 on little-endian machines,
|
||||
nonzero (\code{sizeof(int)-1}) on big-endian ones. Used e.g.\ by \code{bitvector} to refuse big-endian platforms.}
|
||||
\end{lareference}
|
||||
|
||||
Errors detected by BLAS and LAPACK (\code{xerbla_}), ATLAS (\code{ATL_xerbla}) and CBLAS (\code{cblas_xerbla},
|
||||
\code{cblas_errprn}) are redirected to \code{laerror} by replacement handlers defined in \header{laerror.cc}. The CBLAS ones
|
||||
are omitted when the library is configured without CBLAS (\code{NONCBLAS}), \code{cblas_xerbla} also when
|
||||
\code{AVOID_DUPLICATE_CBLAS_XERBLA} is defined (for CBLAS implementations providing their own).
|
||||
|
||||
Typical use at the start of \code{main()} (\header{t.cc}):
|
||||
\begin{lstlisting}
|
||||
sigtraceback(SIGSEGV,1);
|
||||
sigtraceback(SIGABRT,1);
|
||||
sigtraceback(SIGBUS,1);
|
||||
sigtraceback(SIGFPE,1);
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
%=====================================================================
|
||||
\section{The single include file la.h}\label{sec:utilities:la}
|
||||
\laheader{la.h}
|
||||
|
||||
\header{la.h} is meant as the single include file for the end user. It includes the headers of all classes and modules
|
||||
(traits, error handling, \code{AuxStorage}, vectors, dense, symmetric and sparse matrices, the non-class functions,
|
||||
iterative solvers, \code{DIIS}, matrix exponential, four-index transformation, permutations, polynomials, continued
|
||||
fractions, tensors, optimization, numbers, bitvector, Vec3/Mat3, quaternions, \header{miscfunc.h} and the version
|
||||
declaration), and \header{la_random.h} indirectly. It does \emph{not} include \header{simple.h}, \header{graph.h},
|
||||
\header{reg.h} and \header{regsurf.h}, which have to be included explicitly when needed.
|
||||
|
||||
\header{la.h} puts \code{using namespace LA;} into the global namespace and defines the abbreviations
|
||||
\begin{lstlisting}
|
||||
typedef NRMat<int> NRIMat; typedef NRVec<int> NRIVec;
|
||||
typedef NRMat<double> NRDMat; typedef NRVec<double> NRDVec;
|
||||
typedef NRMat<std::complex<double> > NRCMat;
|
||||
typedef NRVec<std::complex<double> > NRCVec;
|
||||
\end{lstlisting}
|
||||
Code which wants to avoid the global \code{using} directive can include the individual headers instead.
|
||||
|
||||
%=====================================================================
|
||||
\section{Version information}\label{sec:utilities:version}
|
||||
|
||||
The library contains the string \code{extern const char LA::version[]} (declared in \header{LA_version.h}, included by
|
||||
\header{la.h}). Its definition \header{version.cc} is regenerated at every \code{make} by the script
|
||||
\header{get_git_version} and contains the output of \code{git describe}, the HEAD commit hash and date, the compilation date,
|
||||
host and user name. A program can print it to record which library build produced its results:
|
||||
\begin{lstlisting}
|
||||
cout <<"LA version = "<<LA::version<<endl;
|
||||
\end{lstlisting}
|
||||
|
||||
%=====================================================================
|
||||
\section{Number-theoretic utilities}\label{sec:utilities:numbers}
|
||||
\laheader{numbers.h, numbers.cc}
|
||||
|
||||
Elementary number theory with naive algorithms, suitable for small numbers (trial division).
|
||||
All functions are templates in the integer type \code{N} but are instantiated only for
|
||||
\code{uint64_t} (the inline ones \code{isprime}, \code{isoddprime}, \code{eulerphi(const N&)}
|
||||
therefore also link only for \code{uint64_t}).
|
||||
\begin{lareference}
|
||||
\lafunc{N primefactor(const N &x, const N &last=0)}{Smallest prime factor of $x$ which is $\ge$\code{last} (assuming $x$ has no smaller odd factors); returns $x$ for $x\le2$ and for primes.}
|
||||
\lafunc{bool isprime(const N &x); bool isoddprime(const N &x)}{$x$ prime; $x$ odd prime.}
|
||||
\lafunc{class FACTORIZATION<N> : public std::list<std::pair<N,N>>}{List of (prime, exponent) pairs in increasing order.}
|
||||
\lafunc{FACTORIZATION<N> factorization(const N &x)}{Prime factorization (empty for $x\le1$).}
|
||||
\lafunc{N nextprime(N x)}{Smallest prime $>x$ (2 for $x<2$).}
|
||||
\lafunc{std::ostream &operator<<(std::ostream &s, const FACTORIZATION<N> &x)}{Prints \code{(p,e) (p,e) ...}.}
|
||||
\lafunc{N eulerphi(const FACTORIZATION<N> &f); N eulerphi(const N &x)}{Euler's totient function.}
|
||||
\lafunc{N pow(const N &x, N i)}{$x^i$ by repeated squaring (overflow not checked).}
|
||||
\lafunc{N multmod(N x, N y, const N &m); N powmod(const N &x, N i, const N &m)}{$xy \bmod m$ by shift-and-add (avoids overflow of $xy$ up to about $m<2^{63}$), $x^i\bmod m$.}
|
||||
\end{lareference}
|
||||
Example: \code{uint64_t n=360; cout << factorization(n) << eulerphi(n);} prints
|
||||
\code{(2,3) (3,2) (5,1) 96}.
|
||||
|
||||
%=====================================================================
|
||||
\section{Graph algorithms}\label{sec:utilities:graph}
|
||||
\laheader{graph.h, graph.cc}
|
||||
|
||||
A small collection of graph algorithms. There is no graph class: the functions are templates in
|
||||
the adjacency matrix type \code{G}, which must provide \code{nrows()}, an element access
|
||||
\code{operator()(i,j)} convertible to \code{bool}, a copy constructor/assignment and
|
||||
\code{submatrix(const NRVec<int>&)} returning a \code{G}. They are instantiated only for
|
||||
\code{G = NRSMat<char>} (undirected graphs, chapter~\ref{ch:smat}). Vertices are numbered from 0;
|
||||
diagonal elements are ignored.
|
||||
\begin{lareference}
|
||||
\lafunc{NRVec<int> neighbors(const G &adjacency, const int v); NRVec<int> nonneighbors(const G &adjacency, const int v)}{Sorted lists of vertices adjacent / not adjacent to $v$ (excluding $v$).}
|
||||
\lafunc{NRVec<int> findclique(const G &adjacency, const int v=0)}{A large clique found by the recursive Ramsey heuristic of Boppana and Halld\'orsson \cite{utilities:boppana1992} started at vertex $v$; returns the sorted vertex list (empty for an empty graph). Exponential worst case in recursion; intended for small graphs.}
|
||||
\lafunc{NRVec<int> complement(const G &adjacency, const NRVec<int> &vertexlist)}{Vertices not contained in \code{vertexlist}.}
|
||||
\lafunc{NRVec<int> cliquecover(const G &adjacency, int *ncliques=NULL)}{Greedy clique cover by repeated \code{findclique} on the remaining subgraph; returns for each vertex the number of its clique (counted from 1) and optionally the number of cliques.}
|
||||
\end{lareference}
|
||||
\begin{lstlisting}
|
||||
NRSMat<char> adj;
|
||||
cin >> adj;
|
||||
NRVec<int> clique = findclique(adj);
|
||||
NRVec<int> cover = cliquecover(adj);
|
||||
NRPerm<int> p(cover.size());
|
||||
cover.sort(0,p,true); //permutation grouping the cliques
|
||||
NRSMat<char> adjperm = adj.permuted(p); //block structure
|
||||
\end{lstlisting}
|
||||
+393
@@ -0,0 +1,393 @@
|
||||
\chapter{Vectors: NRVec}\label{ch:vec}
|
||||
|
||||
\laheader{vec.h, vec.cc}
|
||||
|
||||
\section{Purpose and main idea}\label{sec:vec:purpose}
|
||||
|
||||
\code{NRVec<T>} is the basic dense vector class template of \LA{}: a contiguous array of \code{nn} elements of type \code{T},
|
||||
indexed from zero, with reference-counted storage (copy on demand). It is used directly as the vector type of linear algebra
|
||||
(BLAS-1 and BLAS-2 operations for \code{double} and \code{std::complex<double>}), as a general resizable container of arbitrary
|
||||
elements (including nested \LA{} objects such as \code{NRVec<NRVec<int> >}), and as the storage backbone of other classes
|
||||
(\code{NRMat<T>} and \code{NRSMat<T>} can be viewed as vectors without copying, see Sec.~\ref{sec:vec:construct}).
|
||||
\code{NRVec_from1<T>} (Sec.~\ref{sec:vec:from1}) is a thin derived class with indexing from one.
|
||||
|
||||
The class is explicitly instantiated in \texttt{vec.cc} for the element types \code{double}, \code{char}, \code{short}, \code{int},
|
||||
\code{long}, \code{long long}, their \code{unsigned} variants, and for \code{std::complex<double>}. Members defined in
|
||||
\texttt{vec.h} can be used with any other element type (user classes, nested \LA{} types, \code{float}, \dots); members defined
|
||||
only in \texttt{vec.cc} (\code{NRVec(const NRMat&)}, \code{fprintf}, \code{fscanf}, unary \code{operator-}, \code{operator<},
|
||||
\code{operator>}, \code{find}, \code{findthr}, \code{permuteme(const CyclePerm<int>&)}, \code{conjugateme}, \code{negateme},
|
||||
\code{negateconjugateme}, \code{sum}) are available only for the instantiated types. Numerical members based on BLAS are intended
|
||||
for floating-point element types only (real and complex), not for integers; at present they exist for \code{double} and
|
||||
\code{std::complex<double>} (see the remarks in the reference below).
|
||||
|
||||
\section{Storage and class-specific concepts}\label{sec:vec:storage}
|
||||
|
||||
The object holds the size \code{nn}, a data pointer \code{v} and a pointer \code{count} to the reference counter
|
||||
(plus a location tag in GPU builds, see Sec.~\ref{sec:concepts:gpu}). A default-constructed vector has \code{nn==0}, \code{v==0},
|
||||
\code{count==0}.
|
||||
|
||||
\code{NRVec} follows the copy-on-demand scheme of Sec.~\ref{sec:concepts:refcount}: copy construction and \code{operator=} share
|
||||
the data, \code{operator|=} makes a deep copy (Sec.~\ref{sec:concepts:assign}), member functions that modify the data call
|
||||
\code{copyonwrite()} themselves, \code{resize()} detaches shared data, and an explicit \code{copyonwrite()} is needed only before
|
||||
writing through \code{operator[]} or a raw pointer. In DEBUG builds the non-const \code{operator[]} raises \code{laerror} if the
|
||||
reference count is larger than one and \code{_LA_count_check} is set. For element types that are not plain data
|
||||
(\code{LA_traits<T>::is_plaindata()} false, e.g.\ nested vectors), \code{copyonwrite()} recursively calls \code{copyonwrite()}
|
||||
on the elements, see Sec.~\ref{sec:concepts:traits}.
|
||||
|
||||
The element-level member \code{swap(int,int)} does not call \code{copyonwrite()} by design, like \code{operator[]}: call
|
||||
\code{copyonwrite()} once before using it on a vector that may share its data (Sec.~\ref{sec:concepts:refcount}).
|
||||
|
||||
Elements are stored contiguously, so \code{(T*)x} or \code{&x[0]} can be passed to C/Fortran routines expecting an array.
|
||||
The nested type \code{ROWTYPE} is \code{T}.
|
||||
|
||||
\section{Construction, assignment, conversion}\label{sec:vec:construct}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{NRVec()}{Empty vector (size 0, nothing allocated).}
|
||||
\lafunc{explicit NRVec(const int n, const GPUID loc = undefined)}{Vector of \code{n} elements, \emph{not initialized} (for
|
||||
plain data). \code{loc} is the memory location in GPU builds, ignored otherwise.}
|
||||
\lafunc{NRVec(const T &a, const int n, const GPUID loc = undefined)}{Vector of \code{n} elements all equal to \code{a}. Note the
|
||||
order of arguments: value first, size second.}
|
||||
\lafunc{template<int SIZE> NRVec(const T (&a)[SIZE])}{Copy of a C array; also accepts a braced list, e.g.\
|
||||
\code{NRVec<double> x({1.,2.,3.})}.}
|
||||
\lafunc{NRVec(const T *a, const int n)}{Copy of \code{n} elements from memory at \code{a}.}
|
||||
\lafunc{NRVec(T *a, const int n, bool skeleton)}{With \code{skeleton==false} same as the previous one. With \code{skeleton==true}
|
||||
the vector \emph{wraps} the external memory \code{a} without copying; the reference count is set to 2 so that the memory is never
|
||||
freed by \LA{}. Consequently any modifying member function makes a private copy first (the external memory is \emph{not}
|
||||
modified), and DEBUG-mode \code{operator[]} for writing raises an error. Use it for read-only views of foreign data.}
|
||||
\lafunc{NRVec(const Vec3<T> &rhs)}{Copy of the three components of a \code{Vec3} (from \texttt{vecmat3.h}, see
|
||||
Chapter~\ref{ch:vecmat3}).}
|
||||
\lafunc{NRVec(const NRVec &rhs)}{Copy constructor, shares data.}
|
||||
\lafunc{explicit NRVec(const std::list<T> l)}{Copy of the elements of a \code{std::list} (the list is passed by value).}
|
||||
\lafunc{NRVec(const typename LA_traits_complex<T>::NRVec_Noncomplex_type &rhs, bool imagpart=false)}{Complex vector from a real
|
||||
one: \code{rhs} becomes the real part (or the imaginary part if \code{imagpart}), the other part is zero. Implemented only for
|
||||
\code{NRVec<std::complex<double> >} from \code{NRVec<double>}.}
|
||||
\lafunc{explicit NRVec(const NRSMat<T> &S)}{View of the packed storage of a symmetric matrix of order $n$ as a vector of
|
||||
$n(n+1)/2$ elements. \emph{Shares} the data with \code{S} (no copy).}
|
||||
\lafunc{explicit NRVec(const NRMat<T> &rhs)}{View of the row-major storage of an $n\times m$ matrix as a vector of $nm$ elements.
|
||||
\emph{Shares} the data with \code{rhs}; if the library is compiled with \code{MATPTR}, the data are copied instead.}
|
||||
\lafunc{explicit NRVec(const SparseMat<T> &rhs)}{Dense vector from a sparse matrix having one row or one column (otherwise
|
||||
\code{laerror}); duplicate entries are summed. Defined in \texttt{sparsemat.cc} for \code{double} and \code{std::complex<double>}.}
|
||||
\lafunc{~NRVec()}{Decrements the reference count, frees the data when it reaches zero.}
|
||||
\lafunc{NRVec& operator=(const NRVec &rhs)}{Shallow assignment (shares data), see Sec.~\ref{sec:concepts:assign}.}
|
||||
\lafunc{NRVec& operator|=(const NRVec &rhs)}{Deep copy; \code{rhs} must be allocated (checked in DEBUG).}
|
||||
\lafunc{NRVec& operator=(const T &a)}{Sets all elements to \code{a} (keeps the size); calls copyonwrite.}
|
||||
\lafunc{void copyonwrite(bool detachonly=false, bool deep=true)}{If the data are shared, allocate a private copy.
|
||||
\code{detachonly=true} allocates private storage without copying the contents (they are undefined afterwards).
|
||||
With \code{deep} and non-plain-data \code{T}, elements are made unique recursively even if this object is not shared.
|
||||
Calling it on an empty vector is harmless (prints a warning if \code{_LA_warn_empty_copyonwrite} is set).}
|
||||
\lafunc{int getcount() const}{Current reference count (0 for an empty vector).}
|
||||
\lafunc{void resize(const int n, const bool preserve=false, const bool preserve_at_end=false)}{Change size; detaches from shared data.
|
||||
Without \code{preserve} the new contents are undefined (no-op if the size does not change and the data are not shared).
|
||||
With \code{preserve} the first $\min(n_\text{old},n)$ elements are kept and new elements are zeroed (by \code{memset});
|
||||
with \code{preserve_at_end} the last elements are kept and aligned to the end, the new ones being at the beginning.
|
||||
\code{resize(0)} deallocates.}
|
||||
\lafunc{void dealloc(void)}{Same as \code{resize(0)}.}
|
||||
\lafunc{void clear()}{Set all elements to zero (\code{LA_traits<T>::clear}); detaches shared plain data.}
|
||||
\lafunc{void swap(NRVec &rhs)}{Exchange contents (size, data, counter) of two vectors in $O(1)$.}
|
||||
\lafunc{template<typename U> NRVec<std::complex<U> > complexify(const NRVec<U> &rhs)}{(free function) Complex vector with
|
||||
real part \code{rhs}. CPU only (NOT\_GPU) in the generic version; specialized for \code{double} via BLAS.}
|
||||
\lafunc{template<typename T, typename S> void NRVec_convert(NRVec<T> &a, const NRVec<S> &b)}{(free function) Resize \code{a} and
|
||||
set \code{a[i] = (T)b[i]}.}
|
||||
\end{lareference}
|
||||
\section{Element access}\label{sec:vec:access}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{T & operator[](const int i); const T & operator[](const int i) const}{Element $i$, $0\le i<n$. DEBUG builds check the
|
||||
range, allocation, and (non-const version) the reference count, see Sec.~\ref{sec:vec:storage}.}
|
||||
\lafunc{operator T*(); operator const T*() const}{Pointer to the data (DEBUG: error if unallocated). Call \code{copyonwrite()}
|
||||
before writing through the pointer.}
|
||||
\lafunc{int size() const}{Number of elements.}
|
||||
\lafunc{const NRVec subvector(const int from, const int to) const}{Copy of elements \code{from}\dots\code{to} (inclusive). If this is
|
||||
the whole vector, a shallow copy is returned. Plain data only.}
|
||||
\lafunc{const NRVec subvector(const NRVec<int> &selection) const}{Gather into a new vector: \code{r[i] = x[selection[i]]}.
|
||||
Index range is always checked.}
|
||||
\lafunc{void subvector_from(const NRVec<int> &selection, const NRVec<T> &rhs)}{Gather into \code{*this}:
|
||||
\code{x[i] = rhs[selection[i]]}; \code{x.size()} must equal \code{selection.size()} (otherwise \code{laerror}); calls copyonwrite.}
|
||||
\lafunc{void storesubvector(const int from, const NRVec &rhs)}{Copy \code{rhs} into positions \code{from}\dots\code{from+rhs.size()-1};
|
||||
plain data only; calls copyonwrite.}
|
||||
\lafunc{void storesubvector(const NRVec<int> &selection, const NRVec &rhs)}{Scatter into \code{*this}:
|
||||
\code{x[selection[i]] = rhs[i]}; sizes of \code{selection} and \code{rhs} must agree; calls copyonwrite.}
|
||||
\lafunc{void storesubvector_to(const NRVec<int> &selection, NRVec &rhs) const}{Scatter \code{*this} into \code{rhs}:
|
||||
\code{rhs[selection[i]] = x[i]}; \code{x.size()} must equal \code{selection.size()}; calls \code{rhs.copyonwrite()}.}
|
||||
\lafunc{NRVec reverse() const}{Copy with reversed order of elements.}
|
||||
\lafunc{NRVec concat(const NRVec &rhs) const; NRVec concatscaled(const NRVec &rhs, const T alpha=1) const}{New vector
|
||||
$(x, \mathtt{rhs})$, resp.\ $(x, \alpha\,\mathtt{rhs})$. If one of the vectors is empty, the other one is returned (shared; for
|
||||
\code{concatscaled} with an empty \code{*this} and $\alpha\neq1$ a scaled copy of \code{rhs}).}
|
||||
\lafunc{void concatme(const NRVec &rhs); void concatmescaled(const NRVec &rhs, const T alpha=1)}{Append \code{rhs}
|
||||
(resp.\ $\alpha\,\mathtt{rhs}$) to \code{*this} in place (via \code{resize} with preservation).}
|
||||
\lafunc{void append(const T &a); void prepend(const T &a)}{Add one element at the end / beginning. Each call reallocates;
|
||||
inefficient when repeated.}
|
||||
\lafunc{const int find(const T &val) const}{Index of the first element equal to \code{val}, $-1$ if none.}
|
||||
\lafunc{const int findthr(const T &val, const typename LA_traits<T>::normtype &thr=0) const}{Index of the first element with
|
||||
$|x_i-\mathtt{val}|<\mathtt{thr}$, $-1$ if none (note the strict inequality: the default \code{thr=0} never matches).}
|
||||
\end{lareference}
|
||||
|
||||
|
||||
\section{Arithmetic and BLAS-backed operations}\label{sec:vec:arith}
|
||||
|
||||
All compound operators call \code{copyonwrite()}. Dimensions of vector operands are checked only in DEBUG builds.
|
||||
For \code{double} and \code{std::complex<double>} the operations marked BLAS are specialized to call CBLAS; the generic versions are
|
||||
element-wise loops (CPU only).
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{NRVec& operator+=(const NRVec &rhs); NRVec& operator-=(const NRVec &rhs)}{Element-wise sum/difference (BLAS axpy).}
|
||||
\lafunc{NRVec& operator*=(const NRVec &rhs); NRVec& operator/=(const NRVec &rhs)}{Element-wise (Hadamard) product/quotient.}
|
||||
\lafunc{const NRVec operator+(const NRVec &rhs) const; const NRVec operator-(const NRVec &rhs) const}{Sum/difference. There are
|
||||
no binary element-wise \code{*} and \code{/} of two vectors; \code{x*y} is the scalar product.}
|
||||
\lafunc{NRVec& operator+=(const T &a); NRVec& operator-=(const T &a)}{Add/subtract \code{a} to/from every element (BLAS).}
|
||||
\lafunc{NRVec& operator*=(const T &a); NRVec& operator/=(const T &a)}{Scale every element (BLAS scal; division as
|
||||
multiplication by $1/a$ for the specialized types).}
|
||||
\lafunc{const NRVec operator+(const T &a) const; ... operator-, operator*, operator/}{\code{x+a}, \code{x-a}, \code{x*a},
|
||||
\code{x/a} element-wise with a scalar.}
|
||||
\lafunc{const NRVec operator+(const T &a, const NRVec &x); const NRVec operator*(const T &a, const NRVec &x)}{(free functions) Scalar on the
|
||||
left: $a+x$ and $ax$. The noncommutative \code{a-x} and \code{a/x} are deliberately not available (declared \code{= delete},
|
||||
a compile error); write \code{-x+a} explicitly.}
|
||||
\lafunc{const NRVec operator-() const}{Negated copy.}
|
||||
\lafunc{NRVec& negateme()}{$x\leftarrow -x$ in place.}
|
||||
\lafunc{NRVec& conjugateme(); NRVec conjugate() const}{Complex conjugation in place / conjugated copy (no-op for real types).}
|
||||
\lafunc{NRVec& negateconjugateme()}{$x\leftarrow -x^*$ in place (specialized for \code{std::complex<double>} and
|
||||
\code{std::complex<float>}).}
|
||||
\lafunc{const T operator*(const NRVec &rhs) const}{Scalar product $\sum_i x_i^*\, y_i$ (conjugating \code{*this} for complex types;
|
||||
BLAS dot/dotc).}
|
||||
\lafunc{const T dot(const T *a, const int stride = 1, bool conjugate=true) const}{$\sum_i \tilde x_i\, a_{i\cdot\mathrm{stride}}$,
|
||||
where $\tilde x_i=x_i^*$ if \code{conjugate} and \code{T} is complex, else $x_i$; \code{a} must have sufficient length.}
|
||||
\lafunc{const T dot(const NRVec &rhs, bool conjugate=true) const}{Same with a vector (no dimension check).}
|
||||
\lafunc{void axpy(const T alpha, const NRVec &x)}{$\mathtt{this} \leftarrow \mathtt{this} + \alpha\,\mathtt{x}$ (BLAS axpy).
|
||||
Only \code{double} and \code{std::complex<double>}.}
|
||||
\lafunc{void axpy(const T alpha, const T *x, const int stride=1)}{Same with strided raw data; CPU only.
|
||||
Only \code{double} and \code{std::complex<double>}.}
|
||||
\lafunc{void gemv(const T beta, const NRMat<T> &a, const char trans, const T alpha, const NRVec &x)}{$y\leftarrow\alpha\,\mathrm{op}(A)x+\beta y$
|
||||
with $y=$ \code{*this}; \code{trans} is \code{'n'} ($A$), \code{'t'} ($A\Tr$) or for complex \code{'c'} ($A\Hc$), case insensitive.
|
||||
\code{*this} must already have the correct size (checked in DEBUG builds) and defined contents if $\beta\neq0$.}
|
||||
\lafunc{void gemv(const T beta, const NRSMat<T> &a, const char trans, const T alpha, const NRVec &x)}{Same for a packed
|
||||
symmetric (real) or Hermitian (complex) matrix (BLAS spmv/hpmv); \code{trans}: for complex \code{'t'} gives $a\Tr x = a^* x$, \code{'n'} and \code{'c'} give $a x$; ignored for real matrices.}
|
||||
\lafunc{void gemv(const T beta, const SparseMat<T> &a, const char trans, const T alpha, const NRVec &x, const bool treat_as_symmetric = false)}{Same
|
||||
for a sparse matrix (defined in \texttt{sparsemat.cc}, see Chapter~\ref{ch:sparse}); with \code{treat_as_symmetric} only one
|
||||
triangle is assumed to be stored.}
|
||||
\lafunc{void gemv(const Component_type beta, const NRMat_Noncomplex_type &a, const char trans, const Component_type alpha, const NRVec &x);
|
||||
void gemv(const Component_type beta, const NRSMat_Noncomplex_type &a, const char trans, const Component_type alpha, const NRVec &x)}{(Types
|
||||
from \code{LA_traits_complex<T>}.) Complex vector times \emph{real} dense or packed-symmetric matrix with real coefficients;
|
||||
real and imaginary parts are processed separately.}
|
||||
\lafunc{const NRVec operator*(const NRMat<T> &mat) const}{Row vector times matrix, $x\Tr A$ (gemv with \code{'t'}; no conjugation).
|
||||
For $A x$ see \code{NRMat::operator*} in Chapter~\ref{ch:mat}.}
|
||||
\lafunc{const NRVec operator*(const NRSMat<T> &mat) const}{Row vector times packed symmetric/hermitian matrix, $x\Tr S$ (for hermitian $S$ computed as $S\Tr x = S^* x$ by \code{gemv} with \code{'t'}).}
|
||||
\lafunc{const NRVec operator*(const SparseMat<T> &mat) const}{Row vector times sparse matrix; CPU only.}
|
||||
\lafunc{const NRMat<T> otimes(const NRVec<T> &rhs, const bool conjugate = false, const T &scale = 1) const}{Outer product
|
||||
$A_{ij}=\mathrm{scale}\cdot x_i\, y_j$, with $y_j^*$ if \code{conjugate} (complex only); BLAS ger/geru/gerc.}
|
||||
\lafunc{const NRMat<T> operator|(const NRVec<T> &rhs) const}{Same as \code{otimes(rhs,true)}, i.e.\ $x y\Hc$.}
|
||||
\lafunc{const NRVec otimes2vec(const NRVec<T> &rhs, const bool conjugate = false, const T &scale = 1) const}{Outer product stored
|
||||
as a vector of length $n_x n_y$ (row-major, index $i n_y + j$).}
|
||||
\end{lareference}
|
||||
|
||||
The real-matrix \code{gemv}, \code{normalize}, \code{otimes} and \code{otimes2vec} exist only for \code{double} and
|
||||
\code{std::complex<double>}; for the instantiated integer types (and complex integer types) they are defined to raise
|
||||
\code{laerror}.
|
||||
|
||||
\section{Norms, reductions, extrema}\label{sec:vec:norms}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{const typename LA_traits<T>::normtype norm(int length= -1, int offset=0, int stride=1) const}{Euclidean norm
|
||||
(BLAS nrm2) of \code{length} elements (default all) starting at \code{offset} with \code{stride}.
|
||||
Only \code{double} and \code{std::complex<double>}.}
|
||||
\lafunc{NRVec& normalize(typename LA_traits<T>::normtype* norm = 0)}{Divide by the Euclidean norm, optionally storing the norm in
|
||||
\code{*norm}; DEBUG: error for a zero vector. Only \code{double} and \code{std::complex<double>}.}
|
||||
\lafunc{const NRVec unitvector() const}{Normalized copy.}
|
||||
\lafunc{const typename LA_traits<T>::normtype asum() const}{$\sum_i |x_i|$ (BLAS asum; for complex
|
||||
$\sum_i |\mathrm{Re}\,x_i|+|\mathrm{Im}\,x_i|$). Only \code{double} and \code{std::complex<double>}.}
|
||||
\lafunc{inline int iamax() const}{Zero-based index of the element of largest absolute value (BLAS i?amax; for complex the 1-norm
|
||||
$|\mathrm{Re}|+|\mathrm{Im}|$). Only \code{double} and \code{std::complex<double>}. \code{laerror} for an empty vector.}
|
||||
\lafunc{inline const T amax() const; inline const T amin() const}{The element (with sign) of largest / smallest absolute value
|
||||
(complex: 1-norm). Only \code{double} and \code{std::complex<double>}. \code{laerror} for an empty vector.}
|
||||
\lafunc{const T max() const; const T min() const}{Largest / smallest element (0 for an empty vector). Only real types
|
||||
(instantiated for the integer types, \code{float}, \code{double}).}
|
||||
\lafunc{const T sum() const}{$\sum_i x_i$.}
|
||||
\lafunc{const T sumsqr() const}{$\sum_i x_i^2$, 0 for an empty vector. No conjugation for complex types; use \code{norm()} for $\sqrt{\sum_i|x_i|^2}$.}
|
||||
\lafunc{const T prod() const}{$\prod_i x_i$, 1 for an empty vector.}
|
||||
\lafunc{NRVec<typename LA_traits<T>::normtype> abs() const}{Vector of $|x_i|$.}
|
||||
\lafunc{NRVec<typename LA_traits<T>::normtype> diffabs(const NRVec &rhs) const}{Vector of $|x_i|-|y_i|$.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Other member functions}\label{sec:vec:other}
|
||||
|
||||
\subsection*{Comparison}
|
||||
\begin{lareference}
|
||||
\lafunc{const bool operator==(const NRVec &rhs) const; const bool operator!=(const NRVec &rhs) const}{Equal size and elements.
|
||||
For plain data the comparison is bytewise (\code{memcmp}), so e.g.\ \code{0.0} and \code{-0.0} differ.}
|
||||
\lafunc{const bool operator<(const NRVec &rhs) const; ... operator>, operator<=, operator>=}{Lexicographic order using
|
||||
\code{LA_traits<T>::smaller/bigger}; a proper prefix is smaller. Raises \code{laerror} for complex elements.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection*{Sorting and permutations}
|
||||
\begin{lareference}
|
||||
\lafunc{int sort(int direction = 0, int from = 0, int to = -1, int *perm = NULL, bool stable=false)}{Quicksort of elements
|
||||
\code{from}\dots\code{to} (\code{-1}: last) in place, ascending for \code{direction==0}, descending otherwise; returns the
|
||||
parity (0/1) of the applied permutation. If \code{perm} is given, \code{perm[from..to]} (initialized by the caller) is permuted
|
||||
along with the elements. \code{stable} selects a stable variant. Calls copyonwrite.}
|
||||
\lafunc{int sort(int direction, NRPerm<int> &perm, bool stable=false)}{Sort the whole vector; \code{perm} (of the same size)
|
||||
is set to the identity and returned with \code{perm[i]} = original (1-based) position of the element now at position $i$ (1-based).}
|
||||
\lafunc{void printsorted(std::ostream &s, int direction=1, bool absvalue=false, bool stable=false, typename LA_traits<T>::normtype thr=0, int topmax=0, const NRMat<int> *indexmap=NULL) const}{Print
|
||||
lines ``\emph{index value}'' in sorted order (default descending) without modifying the vector. \code{absvalue}: sort by $|x_i|$;
|
||||
\code{thr}: skip elements with $|x_i|<\mathrm{thr}$; \code{topmax}: print at most this many lines (0 = all); \code{indexmap}:
|
||||
print \code{(*indexmap)[i][0]} and \code{(*indexmap)[i][1]} instead of the index $i$.}
|
||||
\lafunc{bool bigger(int i, int j) const; bool smaller(int i, int j) const; void swap(int i, int j)}{Element comparison and
|
||||
exchange used by the sorting templates (\texttt{qsort.h}). \code{swap(i,j)} is element-level access and, like \code{operator[]}, does not call
|
||||
copyonwrite: call it before, outside the loop (Sec.~\ref{sec:vec:storage}).}
|
||||
\lafunc{const NRVec permuted(const NRPerm<int> &p, const bool inverse=false) const}{Permuted copy: \code{r[p[i]-1] = x[i-1]}, or
|
||||
with \code{inverse} \code{r[i-1] = x[p[i]-1]} ($p$ is 1-based, see Chapter~\ref{ch:permutation}).}
|
||||
\lafunc{void permuteme(const NRPerm<int> &p, const bool inverse=false); void permuteme(const CyclePerm<int> &p)}{The same in place;
|
||||
for a cycle $(c_1 \dots c_L)$ the element at $c_{k}$ moves to $c_{k+1}$ and the one at $c_L$ to $c_1$.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection*{Element-wise function application}
|
||||
\begin{lareference}
|
||||
\lafunc{NRVec& call_on_me(T (*_F)(const T &))}{$x_i\leftarrow F(x_i)$ for a plain function pointer. This non-template overload
|
||||
is kept on purpose: when the name of an \emph{overloaded} function is passed, the fixed parameter type selects the right overload
|
||||
(the template below cannot deduce its argument from an overload set). Note that the function must take \code{const T &}, so e.g.\
|
||||
\code{std::sqrt} does not match it.}
|
||||
\lafunc{template<typename F> NRVec& call_on_me(F _F)}{$x_i\leftarrow F(x_i)$ for any callable (functor, lambda, function with
|
||||
another compatible signature). For a single non-overloaded function both overloads match and the non-template one is chosen.}
|
||||
\lafunc{template<typename F> NRVec& call_on_me(F _F, const NRVec &rhs)}{Binary version $x_i\leftarrow F(x_i, \mathtt{rhs}_i)$; sizes
|
||||
checked in DEBUG.}
|
||||
\end{lareference}
|
||||
All three are CPU only and call copyonwrite.
|
||||
|
||||
\subsection*{Random numbers}
|
||||
\begin{lareference}
|
||||
\lafunc{void randomize(const typename LA_traits<T>::normtype &x)}{Fill with uniform random numbers from $[-x,x)$ (real and
|
||||
imaginary parts independently for complex), using \code{RANDDOUBLESIGNED} (Chapter~\ref{ch:utilities}). Only \code{double} and
|
||||
\code{std::complex<double>}.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection*{Input/output}
|
||||
See Sec.~\ref{sec:concepts:io} for the general conventions.
|
||||
\begin{lareference}
|
||||
\lafunc{void put(int fd, bool dimensions=1, bool transp=0, bool orca_format=false) const}{Binary output to file descriptor.
|
||||
With \code{dimensions}: header \code{int n}, then \code{int 1} (padding), or with \code{orca_format} \code{int sizeof(T)};
|
||||
followed by the elements (\code{LA_traits<T>::multiput}, recursive for nested types). \code{transp} is unused.}
|
||||
\lafunc{void get(int fd, bool dimensions=1, bool transp=0, bool orca_format=false)}{Binary input matching \code{put}; with
|
||||
\code{dimensions} the vector is resized, otherwise the current size is used (and copyonwrite called). \code{orca_format} checks
|
||||
the element size.}
|
||||
\lafunc{void fprintf(FILE *f, const char *format, const int modulo) const}{Formatted output via \code{lawritemat} as a
|
||||
$1\times n$ matrix (Chapter~\ref{ch:nonclass}).}
|
||||
\lafunc{void fscanf(FILE *f, const char *format)}{Read the dimension and then the elements with the given \code{scanf} format
|
||||
(e.g.\ \texttt{"\%lf"} for \code{double}); resizes.}
|
||||
\end{lareference}
|
||||
|
||||
\subsection*{Miscellaneous}
|
||||
\begin{lareference}
|
||||
\lafunc{GPUID getlocation() const; void moveto(const GPUID dest)}{Memory location (always \code{cpu} without \code{CUDALA}, then
|
||||
\code{moveto} is a no-op); see Sec.~\ref{sec:concepts:gpu}.}
|
||||
\lafunc{void setcoldim(int i); void simplify()}{Dummies for interface compatibility with other classes.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Related free functions}\label{sec:vec:free}
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{std::ostream & operator<<(std::ostream &s, const NRVec<T> &x)}{Text output: the size on one line, then the elements
|
||||
separated by spaces on the next line (converted to \code{LA_traits_io<T>::IOtype}, so e.g.\ \code{char} is printed as a number).}
|
||||
\lafunc{std::istream & operator>>(std::istream &s, NRVec<T> &x)}{Reads the format written by \code{<<}; resizes \code{x}.}
|
||||
\lafunc{template<typename T> void NRVec_simplify(NRVec<T> &x, const typename LA_traits<T>::normtype thr=0, bool alwayskeepfirst=false)}{For
|
||||
vectors of ``coefficient $\times$ element'' terms (\code{T} must provide sorting traits, \code{operator==} comparing only the element,
|
||||
and \code{LA_traits<T>::coefficient}, \code{abscoefficient}, \code{elementtype}): sort, merge equal elements by adding coefficients,
|
||||
drop terms with zero coefficient or $|\text{coefficient}|<\mathrm{thr}$ (the first term is kept if \code{alwayskeepfirst}), and
|
||||
shrink the vector. Public API; within the library it is presently used by \code{PermutationAlgebra::simplify()}.}
|
||||
\lafunc{template<typename T> typename LA_traits<T>::normtype MYABS(const T &x)}{\code{std::abs} with pass-through specializations
|
||||
for unsigned integer types; auxiliary.}
|
||||
\end{lareference}
|
||||
\code{complexify} and \code{NRVec_convert} are listed in Sec.~\ref{sec:vec:construct}. The header also defines constants
|
||||
\code{CONE}, \code{CMONE}, \code{CZERO} (complex $1,-1,0$ for CBLAS calls) and the internal macros \code{NRVECMAT_OPER},
|
||||
\code{NRVECMAT_OPER_NONCOMMUTATIVE}, \code{NRVECMAT_OPER2} generating the binary operators from the compound ones.
|
||||
|
||||
\section{Vectors indexed from one: NRVec\_from1}\label{sec:vec:from1}
|
||||
|
||||
\code{NRVec_from1<T>} is derived publicly from \code{NRVec<T>} and differs only in indexing: \code{operator[]} takes
|
||||
$1\le i\le n$ (same DEBUG checks as \code{NRVec}), and \code{iamax()} returns a 1-based index. It is used e.g.\ for permutations
|
||||
(Chapter~\ref{ch:permutation}). Everything else is inherited unchanged and \emph{remains zero-based} (e.g.\ \code{subvector},
|
||||
\code{find}, \code{sort(direction,from,to,...)}, the raw pointer).
|
||||
|
||||
\begin{lareference}
|
||||
\lafunc{NRVec_from1(); explicit NRVec_from1(const int n); NRVec_from1(const T &a, const int n); NRVec_from1(const T *a, const int n)}{As
|
||||
for \code{NRVec}.}
|
||||
\lafunc{template<int SIZE> NRVec_from1(const T (&a)[SIZE]); explicit NRVec_from1(const std::list<T> l)}{As for \code{NRVec}.}
|
||||
\lafunc{NRVec_from1(const NRVec<T> &rhs)}{Implicit conversion from the base class (shares data).}
|
||||
\lafunc{const T& operator[](const int i) const; T& operator[](const int i)}{Element $i$, 1-based.}
|
||||
\lafunc{int iamax() const}{1-based index of the element of largest absolute value.}
|
||||
\end{lareference}
|
||||
|
||||
\lawarning{\code{NRVec_from1} does not redeclare \code{operator=(const T&)}; the implicitly declared copy assignment hides it, so
|
||||
\code{f = 0.;} does not compile for \code{NRVec_from1<double> f}. Use the \code{(value, size)} constructor or assign through a base
|
||||
class reference.}
|
||||
|
||||
\section{Examples}\label{sec:vec:examples}
|
||||
|
||||
\begin{lstlisting}
|
||||
#include "la.h"
|
||||
using namespace LA;
|
||||
|
||||
double sq(const double &a) {return a*a;}
|
||||
float sq(const float &a) {return a*a;}
|
||||
|
||||
int main()
|
||||
{
|
||||
NRVec<double> x({1.,2.,3.});
|
||||
NRVec<double> y(x); // shares data with x
|
||||
y *= 2.; // y gets its own copy, x unchanged
|
||||
double d = x*y; // scalar product = 28
|
||||
NRVec<double> z = x + y*0.5;
|
||||
z.axpy(-1.,x); // z = x
|
||||
std::cout << d << " " << z.norm() << std::endl;
|
||||
|
||||
NRVec<double> w(0.,5); // five zeros
|
||||
NRVec<int> sel({4,0,2});
|
||||
w.storesubvector(sel,x); // scatter: w[4]=x[0], w[0]=x[1], w[2]=x[2]
|
||||
NRVec<double> g(3);
|
||||
g.subvector_from(sel,w); // gather: g[i]=w[sel[i]], i.e. g == x
|
||||
std::cout << w << g;
|
||||
|
||||
x.call_on_me(sq); // overloaded name: non-template overload
|
||||
x.call_on_me([](const double &a){return a+1.;}); // lambda: template overload
|
||||
x.call_on_me([](const double &a, const double &b){return a>b?a:b;}, y); // binary
|
||||
|
||||
x.copyonwrite(); // needed before writing via operator[]
|
||||
x[0] = -1.;
|
||||
|
||||
NRVec_from1<double> f(1.,3);
|
||||
f[3] = 5.; // last element
|
||||
std::cout << f << f.iamax() << std::endl; // prints 3
|
||||
|
||||
NRVec<std::complex<double> > c(x,true); // purely imaginary
|
||||
std::cout << c << complexify(x);
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
Sorting with the permutation returned:
|
||||
\begin{lstlisting}
|
||||
NRVec<double> v(12);
|
||||
v.randomize(1.);
|
||||
NRPerm<int> p(v.size());
|
||||
NRVec<double> s(v);
|
||||
s.sort(0,p); // ascending; s copied before sorting, v unchanged
|
||||
// s[i-1] == v[p[i]-1] for i=1..n
|
||||
v.printsorted(std::cout,1,true); // descending by absolute value
|
||||
\end{lstlisting}
|
||||
|
||||
\section{Pitfalls}\label{sec:vec:pitfalls}
|
||||
|
||||
\begin{itemize}
|
||||
\item \code{NRVec<double> x(n)} is not initialized; use \code{NRVec<double> x(0.,n)} or \code{x.clear()}.
|
||||
The argument order of the filling constructor is \emph{(value, size)}.
|
||||
\item After \code{y=x} the data are shared: call \code{y.copyonwrite()} before \code{y[i]=...}. In DEBUG builds reading an element
|
||||
of a shared non-const vector via \code{operator[]} also triggers the error; read through a const reference.
|
||||
\item \code{NRVec(const NRMat&)} and \code{NRVec(const NRSMat&)} share the data with the matrix (unless \code{MATPTR}); writing into
|
||||
the vector after \code{copyonwrite()} does not affect the matrix, but the matrix and the vector are not independent before that.
|
||||
\item A skeleton vector \code{NRVec(ptr,n,true)} never writes into the foreign memory through modifying member functions; they
|
||||
silently make a copy.
|
||||
\item \code{x*y} is the (conjugated) scalar product, not an element-wise product; use \code{x*=y} for that.
|
||||
\item \code{a-x} and \code{a/x} with a scalar \code{a} on the left do not compile (deleted); write \code{-x+a}.
|
||||
\item \code{resize()} without \code{preserve} leaves the contents undefined.
|
||||
\item \code{norm}, \code{normalize}, \code{asum}, \code{amax}, \code{amin}, \code{iamax}, \code{axpy}, \code{randomize},
|
||||
\code{gemv}, \code{otimes} are intended for floating-point types only and are presently available for \code{double} and
|
||||
\code{std::complex<double>} (link error or \code{laerror} otherwise, by design for integer types); \code{max}/\code{min} only for real types.
|
||||
\item \code{NRVec_from1} indexing is 1-based only in \code{operator[]} and \code{iamax()}.
|
||||
\end{itemize}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
\chapter{3D vectors and matrices: Vec3, Mat3}\label{ch:vecmat3}
|
||||
\laheader{vecmat3.h, vecmat3.cc}
|
||||
|
||||
Fixed-size 3D vectors and matrices; plain data, no reference counting (unlike the classes of chapter~\ref{ch:vec} and \ref{ch:mat}).
|
||||
|
||||
\section{Purpose}
|
||||
\code{Vec3<T>} and \code{Mat3<T>} are small fixed-size classes for real 3-dimensional
|
||||
vectors and $3\times3$ matrices, intended for geometry, rotations and Euler angles. They are
|
||||
compatible with the quaternion functions of chapter~\ref{ch:quaternion}
|
||||
(rotation matrices, SO(3) parametrization). They contain only a C array and all operations
|
||||
are hand-unrolled; no BLAS/LAPACK is used.
|
||||
|
||||
The files are written so that they can be compiled separately from the rest of \LA{}
|
||||
(e.g.\ for microcontrollers). The following macros control this:
|
||||
\begin{itemize}
|
||||
\item \code{AVOID_STDSTREAM} -- do not include \code{<iostream>}, no stream operators;
|
||||
\item \code{NO_NUMERIC_LIMITS} -- do not use \code{std::numeric_limits}; \code{DBL_EPSILON}
|
||||
is then defined as the single-precision epsilon;
|
||||
\item \code{QUAT_NO_RANDOM} -- omit \code{randomize()} (which needs \header{la_random.h});
|
||||
\item \code{QUAT_NO_DOUBLE} -- instantiate only \code{float} (otherwise \code{float} and \code{double}).
|
||||
\end{itemize}
|
||||
The templates are explicitly instantiated in \header{vecmat3.cc} for \code{float} and \code{double} only.
|
||||
|
||||
The classes live in namespace \code{LA_Vecmat3}; the header ends with a global
|
||||
\code{using namespace LA_Vecmat3;}, so the names are visible without qualification
|
||||
after including \header{vecmat3.h} (or \header{la.h}, which includes it).
|
||||
\code{LA_traits} specializations declare both classes plain data with a no-op
|
||||
\code{copyonwrite}, so they can be used as elements of \code{NRVec}, \code{NRMat} etc.
|
||||
|
||||
\section{Storage}
|
||||
\code{Vec3<T>} holds the public array \code{T q[3]}, \code{Mat3<T>} the public array
|
||||
\code{T q[3][3]} in row-major (C) order, i.e.\ \code{q[i][j]} is row $i$, column $j$.
|
||||
There is no reference counting; the compiler-generated copy constructor and assignment
|
||||
copy the data. \code{elements()} returns a reference to the C array, conversion to \code{T*}
|
||||
gives a pointer to the first element.
|
||||
|
||||
\section{Vec3 reference}
|
||||
\begin{lareference}
|
||||
\lafunc{Vec3(); Vec3(const T x, const T u=0, const T v=0)}{Uninitialized vector; vector $(x,u,v)$.}
|
||||
\lafunc{Vec3(const T *x); Vec3(const T (&a)[3])}{Copy three elements from memory or from a C array (also allows brace initialization \code{Vec3<double> x({1,2,3})}).}
|
||||
\lafunc{T (&elements())[3]; const T (&elements() const)[3]}{Reference to the underlying array.}
|
||||
\lafunc{operator T*(); operator const T*() const}{Pointer to the data.}
|
||||
\lafunc{T &operator[](const int i)}{Element access (no range check), const version available.}
|
||||
\lafunc{void clear()}{Set to zero (memset, \code{T} must be plain data).}
|
||||
\lafunc{Vec3 &operator*=(const T); Vec3 &operator/=(const T); Vec3 operator*(const T) const; Vec3 operator/(const T) const}{Scaling by a scalar.}
|
||||
\lafunc{T sum() const; T asum() const; T sumsqr() const; T prod() const}{$\sum_i q_i$, $\sum_i |q_i|$, $\sum_i q_i^2$, $\prod_i q_i$.}
|
||||
\lafunc{Vec3 operator-() const; Vec3 &operator+=(const Vec3 &); Vec3 &operator-=(const Vec3 &); Vec3 operator+(const Vec3 &) const; Vec3 operator-(const Vec3 &) const}{Vector algebra.}
|
||||
\lafunc{Vec3 operator*(const Vec3 &rhs) const}{\emph{Vector (cross) product} $\mathbf{q}\times\mathbf{rhs}$.}
|
||||
\lafunc{T dot(const Vec3 &rhs) const}{Scalar product.}
|
||||
\lafunc{Vec3 elementwise_product(const Vec3 &rhs) const}{Componentwise product.}
|
||||
\lafunc{T normsqr() const; T norm() const}{Squared and Euclidean norm.}
|
||||
\lafunc{Vec3 &normalize()}{Divide by the norm (no check for zero).}
|
||||
\lafunc{Vec3 &fast_normalize()}{For \code{float} uses the ``fast inverse square root'' bit trick \code{fast_sqrtinv(float)} (approximate); for other types identical to \code{normalize()}.}
|
||||
\lafunc{Vec3 operator*(const Mat3<T> &rhs) const}{Row vector times matrix, $\mathbf{q}^{\mathsf T} M$.}
|
||||
\lafunc{Vec3 timesT(const Mat3<T> &rhs) const}{$\mathbf{q}^{\mathsf T} M^{\mathsf T}$, i.e.\ $M\mathbf{q}$.}
|
||||
\lafunc{Mat3<T> outer(const Vec3 &rhs) const}{Outer (tensor) product $\mathbf{q}\,\mathbf{rhs}^{\mathsf T}$.}
|
||||
\lafunc{void addouter(Mat3<T> &m, const Vec3 &rhs, const T weight) const}{$m \mathrel{+}= w\,\mathbf{q}\,\mathbf{rhs}^{\mathsf T}$.}
|
||||
\lafunc{void inertia(Mat3<T> &itensor, const T weight) const}{Adds the contribution of a point mass \code{weight} at position $\mathbf{q}$ to the inertia tensor: $I \mathrel{+}= w\,(r^2 \mathbf{1} - \mathbf{q}\mathbf{q}^{\mathsf T})$.}
|
||||
\lafunc{void randomize(const T x)}{Elements uniformly random in $[-x,x)$ via \code{RANDDOUBLESIGNED} (chapter~\ref{ch:utilities}); for \code{float} and \code{double}.}
|
||||
\lafunc{int fprintf(FILE *f, const char *format) const; int sprintf(char *f, const char *format) const}{C-style output, \code{format} must contain three conversions.}
|
||||
\lafunc{int fscanf(FILE *f, const char *format); int sscanf(const char *f, const char *format)}{C-style input, \code{format} contains three conversions (e.g.\ \code{"\%lf \%lf \%lf"}); returns the number of items read.}
|
||||
\end{lareference}
|
||||
|
||||
Free function: \code{T hypot3(const Vec3<T> &c, const Vec3<T> &d)} -- Euclidean distance $|\mathbf c-\mathbf d|$.
|
||||
|
||||
\section{Mat3 reference}
|
||||
\begin{lareference}
|
||||
\lafunc{Mat3(); Mat3(const T (&a)[3][3]); Mat3(const T *x)}{Uninitialized; from a C array (also brace initialization \code{Mat3<double> y({{1,2,3},{4,5,6},{7,8,9}})}); from 9 elements in row-major order.}
|
||||
\lafunc{Mat3(const T x00, const T x01, const T x02, const T x10, const T x11, const T x12, const T x20, const T x21, const T x22)}{From nine elements, row by row.}
|
||||
\lafunc{Mat3(const T x); Mat3 &operator=(const T &x)}{Scalar matrix $x\mathbf{1}$. A single \emph{integer} constructor argument is forbidden
|
||||
(deleted), since through the conversion \code{NRMat(const Mat3&)} it would silently turn e.g.\ \code{NRMat<double> a(5)} into a $3\times3$ matrix;
|
||||
write \code{Mat3<double>(5.)}.}
|
||||
\lafunc{void indentity()}{Set to the unit matrix (sic, the name is misspelled in the code).}
|
||||
\lafunc{T (&elements())[3][3]; operator T*()}{Reference to the array, pointer to the data (const versions exist).}
|
||||
\lafunc{T *operator[](const int i); T &operator()(const int i, const int j)}{Row pointer (so \code{m[i][j]} works) and element access, const versions exist.}
|
||||
\lafunc{void clear()}{Zero matrix.}
|
||||
\lafunc{Mat3 &operator+=(const T); Mat3 &operator-=(const T); Mat3 operator+(const T) const; Mat3 operator-(const T) const}{Add/subtract a multiple of the unit matrix (diagonal only).}
|
||||
\lafunc{Mat3 &operator*=(const T); Mat3 &operator/=(const T); Mat3 operator*(const T) const; Mat3 operator/(const T) const}{Scaling.}
|
||||
\lafunc{void randomize(const T x, const bool symmetric=false)}{Random elements in $[-x,x)$, optionally symmetric; for \code{float} and \code{double}.}
|
||||
\lafunc{Mat3 operator-() const; Mat3 &operator+=(const Mat3 &); Mat3 &operator-=(const Mat3 &); Mat3 operator+(const Mat3 &) const; Mat3 operator-(const Mat3 &) const}{Matrix algebra.}
|
||||
\lafunc{Mat3 operator*(const Mat3 &rhs) const; Mat3 timesT(const Mat3 &rhs) const; Mat3 Ttimes(const Mat3 &rhs) const; Mat3 TtimesT(const Mat3 &rhs) const}{$AB$, $AB^{\mathsf T}$, $A^{\mathsf T}B$, $A^{\mathsf T}B^{\mathsf T}$.}
|
||||
\lafunc{Vec3<T> operator*(const Vec3<T> &rhs) const; Vec3<T> Ttimes(const Vec3<T> &rhs) const}{$A\mathbf v$ and $A^{\mathsf T}\mathbf v$.}
|
||||
\lafunc{T trace() const; T determinant() const}{Trace and determinant.}
|
||||
\lafunc{void transposeme(); Mat3 transpose() const}{Transpose in place / return the transpose.}
|
||||
\lafunc{Mat3 inverse(T *det = NULL) const}{Inverse by the adjugate formula; optionally returns the determinant. No check for singularity (division by zero determinant).}
|
||||
\lafunc{Vec3<T> linear_solve(const Vec3<T> &rhs, T *det = NULL) const}{Solves $A\mathbf x=\mathbf{rhs}$ by Cramer's rule (alternative to \code{simple_gaussj}, section~\ref{sec:utilities:simple}); no singularity check.}
|
||||
\lafunc{int fprintf(FILE *f, const char *format) const; int fscanf(FILE *f, const char *format)}{C-style I/O row by row, \code{format} contains three conversions.}
|
||||
\lafunc{void symmetrize()}{Replace off-diagonal pairs by their average.}
|
||||
\lafunc{bool eivec_sym(Vec3<T> &w, Mat3 &v, const bool sortdown=false) const}{Eigenvalues \code{w} and eigenvectors (\emph{columns} of \code{v}) of a real symmetric matrix by the cyclic Jacobi method (at most 50 sweeps); symmetry is not checked. Eigenvalues sorted ascending, or descending if \code{sortdown}. Returns \code{false} on success, \code{true} if not converged. (An analytic Cardano-based solver is present in the source in a disabled \texttt{\#if 0} block, abandoned for numerical instability.)}
|
||||
\lafunc{T norm(const T scalar = 0) const}{Frobenius norm of $A - \mathrm{scalar}\cdot\mathbf 1$.}
|
||||
\lafunc{void qrd(Mat3 &q, Mat3 &r)}{QR decomposition $A=QR$ by three Givens rotations; \emph{destroys} \code{*this} (non-const).}
|
||||
\lafunc{void svd(Mat3 &u, Vec3<T> &w, Mat3 &v, bool proper_rotations=false) const}{Singular value decomposition $A = U\,\mathrm{diag}(w)\,V^{\mathsf T}$ following \cite{vecmat3:mcadams2011} (eigenvectors of $A^{\mathsf T}A$ by \code{eivec_sym}, then QR of $AV$). Singular values in descending order. If \code{proper_rotations} is false, a negative $w_2$ is made positive by changing the sign of the last column of $U$; if true, $U$ and $V$ are proper rotations ($\det U=\det V=+1$, the sign is moved into $w_2$, which may then be negative), as needed e.g.\ for the Kabsch alignment without mirroring.}
|
||||
\lafunc{void diagmultl(const Vec3<T> &rhs); void diagmultr(const Vec3<T> &rhs)}{$A\leftarrow\mathrm{diag}(\mathbf{rhs})A$ and $A\leftarrow A\,\mathrm{diag}(\mathbf{rhs})$.}
|
||||
\lafunc{Mat3 svdinverse(const T thr=1000*DBL_EPSILON) const}{Pseudoinverse via SVD; singular values below the \emph{absolute} threshold \code{thr} are treated as zero.}
|
||||
\end{lareference}
|
||||
|
||||
\section{Free functions: I/O, Euler angles, projection}
|
||||
\begin{lareference}
|
||||
\lafunc{std::ostream &operator<<(std::ostream &s, const Vec3<T> &x); std::istream &operator>>(std::istream &s, Vec3<T> &x)}{Three numbers separated by spaces (no dimensions, no newline).}
|
||||
\lafunc{std::ostream &operator<<(std::ostream &s, const Mat3<T> &x); std::istream &operator>>(std::istream &s, Mat3<T> &x)}{Nine numbers, one row per line on output.}
|
||||
\lafunc{void euler2rotmat(const T *eul, Mat3<T> &a, const char *type, bool transpose=0, bool direction=0, bool reverse=0)}{Rotation matrix from three Euler angles \code{eul[0..2]}; see below.}
|
||||
\lafunc{void rotmat2euler(T *eul, const Mat3<T> &a, const char *type, bool transpose=0, bool direction=0, bool reverse=0)}{Inverse of \code{euler2rotmat} with the same options.}
|
||||
\lafunc{void perspective(T *proj_xy, const Vec3<T> &point, const Mat3<T> &rot_angle, const Vec3<T> &camera, const Vec3<T> &plane_to_camera)}{Perspective projection of a 3D point onto a 2D plane: $\mathbf d = R\,\mathbf{point}-\mathbf{camera}$, $\mathrm{proj}_i = (e_z/d_z)\, d_i + e_i$, $i=x,y$, where $\mathbf e$=\code{plane_to_camera}.}
|
||||
\end{lareference}
|
||||
|
||||
\paragraph{Euler angle conventions.}
|
||||
\code{type} is a three-character string naming the rotation axes: one of the six proper
|
||||
Euler sequences \code{"xzx" "xyx" "yxy" "yzy" "zyz" "zxz"} or the six Tait--Bryan sequences
|
||||
\code{"xzy" "xyz" "yxz" "yzx" "zyx" "zxy"}. With default options the result is the product of
|
||||
active elementary rotations
|
||||
\[
|
||||
a = R_{\mathrm{type[0]}}(\mathrm{eul}_0)\,R_{\mathrm{type[1]}}(\mathrm{eul}_1)\,R_{\mathrm{type[2]}}(\mathrm{eul}_2),
|
||||
\]
|
||||
following the table in the Wikipedia article ``Euler angles''. The options modify this:
|
||||
\code{transpose} returns $a\Tr$; \code{direction} changes the sign of all angles;
|
||||
\code{reverse} swaps the roles of \code{eul[0]} and \code{eul[2]}. In \code{rotmat2euler},
|
||||
the middle angle is returned in $[0,\pi]$ for proper Euler sequences and in $[-\pi/2,\pi/2]$ for
|
||||
Tait--Bryan ones; the degenerate (gimbal lock) case is not treated specially. An unknown
|
||||
\code{type} leaves the output untouched without an error. The macro \code{Euler_case(a,b,c)}
|
||||
maps the three axis characters to an integer used in the internal switch.
|
||||
Reference in new issue
Block a user