/libgnme

A C++ library for evaluating non-orthogonal matrix elements in electronic structure

Primary LanguageCMIT LicenseMIT

LibGNME

A C++ library for evaluating non-orthogonal matrix elements in electronic structure.

  • Hugh G. A. Burton (2022–)

If you incorporate this code in your work, please consider citing the following work:

  1. "Generalized nonorthogonal matrix elements. II: Extension to arbitrary excitations"; J. Chem. Phys. 157, 204109 (2022)
  2. "Generalized nonorthogonal matrix elements: Unifying Wick’s theorem and the Slater–Condon rules"; J. Chem. Phys. 154, 144109 (2021)

A wrapper for using libGNME in Python is available at github.com/BoothGroup/pygnme.

Installation

Prerequisites

The libGNME package requires a set of standard libraries:

  1. LAPACK, BLAS or Intel MKL, with $MKLROOT set.
  2. OpenMP
  3. CMake (version 3.12 or higher)

Compiling the library

The configure script can be run depending on the choice of compiler:

  1. ./configure [intel/gcc/pgi] --with-openmp
  2. cd build
  3. make install

Following installation, the test suite can be executed from the build/ directory by running ctest.

Compiling programs using LibGNME

As LibGNME produces dynamic libraries, compiling a program (e.g. mycalc.C) requires these to be linked at the compilation state. The example below illustrates this for a typical program mycalc.C

icpc -Wall -std=c++11 -fopenmp -I${GNMEROOT}/external/armadillo-10.1.2/include -I${GNMEROOT} -L${MKLROOT}/lib/intel64_lin -L${GNMEROOT}/lib -g mycalc.C -lgnme_wick -lgnme_utils -lmkl_core -lmkl_intel_thread -lmkl_intel_lp64  -omycalc

Here, ${GNMEROOT} is an environment variable that points towards the root directory of your LibGNME installation. Subsequently running your compiled progarm mycalc requires ${GNMEROOT}/lib to be included in the $LD_LIBRARY_PATH.

Code structure

The primary functionality of LibGNME is to compute matrix elements between non-orthogonal Slater determiants. This can be achieved using either extended nonorthogonal Wick's theory, or using the more computationally expensive Slater–Condon rules.

The code is split into three different libraries:

1. gnme_utils

Support functions including:

  • Generalised eigenvalue problem solver;
  • Biorthogonalisation using Lowdin pairing;
  • Two-electron integral transformation;
  • General linear algebra routines.

libgnme::bitset

To avoid dependencies on other libraries, LibGNME also comes with a built-in bitstring representation for electronic configurations. These bitsets store an electronic configuration using a std::vector data type, with the rightmost element representing the first molecular orbital. A bitset object can be constructed as:

#include <libgnme/bitset/utils.h>

// Initialise directly from std::vector<bool> representation
libgnme::bitset b(std::vector<bool>({0,0,0,1,1,1}));

// Initialise from integer value
size_t nbit = 6;
size_t nval = 7;
libgnme::bitset b(nval,6);

// Print the bitset 
b.print();

2. gnme_wick

Compute matrix elements using the generalised nonorthogonal Wick's theorem.

Here, the computation is divided into three objects:

  1. reference_state: Container to store the definition of reference orbitals and active space for bra or ket.
  2. wick_orbitals: Constructs a biorthogonalised set of orbitals from two reference_state objects, and compute corresponding contractions.
  3. wick_rscf and wick_uscf: Objects that build one- or two-electron matrix elements using wick_orbitals and atomic orbital integrals.

Once these objects have been defined, a given matrix element can be requested by defining the bra and ket excitation using either a bitset representation or a list of single particle excitations.

For example, consider the coupling term between two excitations from reference states with coefficients Cx and Cw, with ne electrons, nbsf basis functions, and nmo molecular orbitals. Note that the two reference states refx and refw are not required to have the same active space definition. Here, S and h1e are Armadillo matrices containing the AO overlap and one-electron integrals, respectively, and II is an Armadillo matrix containing the two-electron integrals with the indexing II(i*nbsf+j,k*nbsf+l) = (ij|kl). The Hamiltonian coupling is computed as:

// Setup the biorthogonalized orbital pair
libgnme::reference_state<double> refx(nbsf, nmo, nocca, nact, ncore, Cx);
libgnme::reference_state<double> refw(nbsf, nmo, nocca, nact, ncore, Cw);
libgnme::wick_orbitals<double,double> orbs(nbsf, nmo, neleca, Cx, Cw, S);

// Setup the matrix builder object
libgnme::wick_rscf<double,double,double> mb(orbs, S, enuc);

// Add one- and two-body contributions
mb.add_one_body(h1e);
mb.add_two_body(II);

// Define bitsets for occupations of bra and ket state
// This coupling corresponds to spin-down single excitation in bra (x) and beta double excitation in ket (w).
libgnme::bitset bxa(std::vector<bool>({0,0,0,1,1,1});
libgnme::bitset bxb(std::vector<bool>({0,1,0,0,1,1});
libgnme::bitset bwa(std::vector<bool>({0,0,0,1,1,1});
libgnme::bitset bwb(std::vector<bool>({0,1,1,0,0,1});

// Intialise temporary variables
double Hwx = 0.0, Swx = 0.0;
arma::mat RDM1(nmo, nmo, arma::fill::zeros);
arma::mat RDM2(nmo*nmo, nmo*nmo, arma::fill::zeros);

// Evaluate matrix element
mb.evaluate(bxa, bxb, bwa, bwb, Swx, Hwx);

// Evaluate 1- and 2-RDM
mb.evaluate(bxa, bxb, bwa, bwb, Swx, RDM1, RDM2);

The wick_uscf object differs only in that it can compute matrix elements for unrestricted reference states with different molecular orbitals for different spins. This object is initialised using two wick_orbtals objects for the high- and low-spin orbitals as e.g. libgnme::wick_uscf<double,double,double> mb(orba, orbb, S, enuc);

3. gnme_slater

Alternatively, the LibGNME library can also be used to compute matrix elements using the older generalised Slater–Condon rules. This achieved using the similar objects slater_rscf and slater_uscf objects that take the AO integrals as an input. For example, the nonorthogonal coupling term illustrated about could be computed as

// Setup the matrix builder object
libgnme::slater_rscf<double,double,double> mb(nbsf, nmo, neleca, nelecb, S, enuc);

// Add one- and two-body contributions
mb.add_one_body(h1e);
mb.add_two_body(II);

// Set occupation vectors
arma::uvec xocca({0,1,2}), xoccb({0,1,4});
arma::uvec wocca({0,1,2}), woccb({0,3,4});

// Intialise temporary variables
double Hwx = 0.0, Swx = 0.0;

// Evaluate matrix element
mb.evaluate(Cx.cols(xocca), Cx.cols(xoccb), Cw.cols(wocca), Cw.cols(woccb), Swx, Hwx);