Files
2026-11-05 17:23:57 +01:00

323 lines
27 KiB
TeX

\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>} and the same with \code{float} 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>} and \code{fourindex<unsigned char,float>}.
\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>} and \code{<unsigned char,float>}; 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}