323 lines
26 KiB
TeX
323 lines
26 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>} 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}
|