Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

#+TITLE: MP2
#+PROPERTY: header-args :tangle mp2.c :comments org


This code reads molecular integrals and orbital energies from a [[https://github.com/trex-coe/trexio][TREXIO]]
file and computes the [[https://en.wikipedia.org/wiki/M%C3%B8ller%E2%80%93Plesset_perturbation_theory][Moller-Plesset 2nd-order correction]] to the
energy for closed-shell systems:

\[
E_{\text{MP2}} = \sum_{ij}^{\text{occ}} \sum_{ab}^{\text{virt}}
   \frac{ \langle i j | a b \rangle ( 
          2 \langle i j | a b \rangle - \langle i j | b a \rangle )}
        {e_i + e_j - e_a - e_b}
\]

where:

- $e_p$ is the energy of the p-th orbital (the p-th eigenvalue of the
  Fock matrix)
- $i,j$ label /occupied/ orbitals
- $a,b$ label /virtual/ orbitals

The number of occupied orbitals is half the number of electrons,
because each orbital contains two electrons. Unoccupied orbitals
are called virtual orbitals.

As the reference used here is a Hartree-Fock ground state, the
~n_occ~ first orbitals are occupied (lowest in energy) and the
remaining are virtual. Therefore, we have $e_i < e_a$.

* Compilation

#+begin_src bash :tangle no
gcc -g -O2 mp2.c -ltrexio  -o mp2
#+end_src

#+RESULTS:

* Includes

#+begin_src c
#include <stdio.h>
#include <trexio.h>
#include <stdint.h>
#include <err.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#+end_src

* Reordering function

  There are permutation symmetries in the indices of integrals, which
  are exploited to reduce the storage of the integrals in the TREXIO file.

  \[
  \langle i j | k l \rangle = 
  \langle k j | i l \rangle = 
  \langle k l | i j \rangle = 
  \langle i l | k j \rangle = 
  \langle j i | l k \rangle = 
  \langle j k | l i \rangle = 
  \langle l k | j i \rangle = 
  \langle l i | j k \rangle 
  \]

  The following function swaps the four indices such that any of the 8
  possible combinations gives the same quartet.

#+begin_src c
void reorder(int *i_, int *j_, int *k_, int *l_) {
  {
    assert (*i_ >= 0); assert (*j_ >= 0);
    assert (*k_ >= 0); assert (*l_ >= 0);

    const int i = *i_; const int j = *j_;
    const int k = *k_; const int l = *l_;

    if (k<i) {
      ,*k_ = i ; *i_ = k;
    }
    if (l<j) {
      ,*l_ = j ; *j_ = l;
    }
  }
  {
    const int i = *i_; const int j = *j_;
    const int k = *k_; const int l = *l_;

    if (j<i) {
      ,*i_ = j ; *j_ = i;
      ,*k_ = l ; *l_ = k;
    }
  }
}
#+end_src

  We chose an ordering where the smallest indices come first.
  in MP2, we always have 2 occupied indices and 2 virtual indices:
  $\langle i j | a b \rangle$, so it is now easy to filter out all the
  integrals which don't have 2 occupied indices on the left and 2
  virtual indices on the right.
  
* MP2 program

#+begin_src c
int main(int argc, char** argv)
{
#+end_src

** Open the TREXIO file

   The name of the TREXIO file should be given as a command-line argument.

#+begin_src c
  if (argc < 2) {
    fprintf(stderr, "usage: mp2 trexio_file.hdf5\n");
    exit(1);
  }

  trexio_exit_code rc = TREXIO_SUCCESS;
  trexio_t* trexio_file = trexio_open(argv[1], 'r', TREXIO_HDF5, &rc);

  if (rc != TREXIO_SUCCESS) {
    fprintf(stderr, "Error opening file %s", argv[1]);
    exit(1);
  }
  assert (trexio_file != NULL);
#+end_src

** Read parameters from TREXIO

   We need to read small scalar variables. The first ones are the
   number of up-spin and down-spin electrons to define the number of
   occupied orbitals ~n_occ~, and check that we are in a closed-shell system.

#+begin_src c
  int n_up = 0;
  rc = trexio_read_electron_up_num(trexio_file, &n_up);
  if (rc != TREXIO_SUCCESS) {
    fprintf(stderr, "Error reading n_up");
    exit(1);
  }
  assert(n_up > 0);

  int n_dn = 0;
  rc = trexio_read_electron_up_num(trexio_file, &n_dn);
  if (rc != TREXIO_SUCCESS) {
    fprintf(stderr, "Error reading n_dn");
    exit(1);
  }
  assert(n_up > 0);

  if (n_up != n_dn) {
    fprintf(stderr, "This code is works only for n_up = n_dn");
    exit(1);
  }

  const int n_occ = n_up;
#+end_src

  We also need to read the total number of molecular orbitals to
  compute the number of virtual orbitals ~n_virt~.
  
#+begin_src c
  int mo_num = 0;
  rc = trexio_read_mo_num(trexio_file, &mo_num);
  if (rc != TREXIO_SUCCESS) {
    fprintf(stderr, "Error reading mo_num");
    exit(1);
  }
  assert(mo_num > 0);

  const int n_virt = mo_num - n_up;
#+end_src

** Quantities involved in the MP2 equation
   
   We assume the TREXIO file contains electron repulsion integrals
   (ERI) in the molecular orbital basis, and orbital energies.

   We first read the orbital energies:
   
#+begin_src c
  double*  epsilon = malloc(mo_num * sizeof(double));
  rc = trexio_read_mo_energy(trexio_file, epsilon);
  if (rc != TREXIO_SUCCESS) {
    fprintf(stderr, "Error reading mo_energy");
    exit(1);
  }
#+end_src

   Then we read the electron repulsion integrals. They are stored in a
   sparse data format, so we obtain quartets of indices and values for
   non-zero integrals.
   
#+begin_src c
  int64_t  n_integrals;
  rc = trexio_read_mo_2e_int_eri_size(trexio_file, &n_integrals);
  if (rc != TREXIO_SUCCESS) {
    fprintf(stderr, "Error reading n_integrals");
    exit(1);
  }
  assert(n_integrals > 0);

  int* const index = malloc(4*n_integrals * sizeof(int));
  if (index == NULL) {
    fprintf(stderr, "Malloc failed for index");
    exit(1);
  }

  double* const value = malloc(n_integrals * sizeof(double));
  if (index == NULL) {
    fprintf(stderr, "Malloc failed for value");
    exit(1);
  }

  int64_t count = n_integrals;
  rc = trexio_read_mo_2e_int_eri(trexio_file, 0L, &count, index, value);
#+end_src

  We transform these arrays of indices and values into an array of
  double, where the index of the quartet ~(i,j,a,b)~ is located at
  address ~b-n_occ + n_virt*(a-n_occ + n_virt*(j + n_occ*i))~. Only
  required integrals are stored.

  By symmetry, the integral at ~(i,j,a,b)~ is equal to the integral at
  ~(j,i,b,a)~.  As integrals may be stored only once in the integrals
  file, to be sure we don't miss integrals we store the value at both
  addresses.
  
#+begin_src c
  size_t nmax = n_occ*n_occ*n_virt*n_virt;
  double* integral = malloc(nmax * sizeof(double));
  memset(integral, 0, nmax*sizeof(double));

  for (size_t kk=0; kk<n_integrals ; ++kk) {
    int i = index[4*kk+0];  assert (i >= 0);
    int j = index[4*kk+1];  assert (j >= 0);
    int a = index[4*kk+2];  assert (a >= 0);
    int b = index[4*kk+3];  assert (b >= 0);

    reorder(&i, &j, &a, &b);
    if (i >= n_occ || j >= n_occ || a < n_occ || b < n_occ ) {
      continue;
    } else {

      a -= n_occ;
      b -= n_occ;
      const size_t ijab = b + n_virt*(a + n_virt*(j + n_occ*i));
      const size_t jiba = a + n_virt*(b + n_virt*(i + n_occ*j));
      integral[ijab] = value[kk];
      integral[jiba] = value[kk];
    }
  }
#+end_src


* MP2 computation

#+begin_src c
  double Emp2 = 0.;

  for   (int i=0 ; i<n_occ ; ++i) {
    for (int j=0 ; j<n_occ ; ++j) {

      const size_t shift = n_virt*(j + n_occ*i);

      for   (int a=0 ; a<n_virt ; ++a) {
        for (int b=0 ; b<n_virt ; ++b) {

          const size_t ijab = b + n_virt*(a + shift);
          const size_t ijba = a + n_virt*(b + shift);

          Emp2 += ( integral[ijab]*(2.*integral[ijab]-integral[ijba]) ) /
            (epsilon[i] + epsilon[j] - epsilon[n_occ+a] - epsilon[n_occ+b]);

        }
      }

    }
  }
#+end_src

* Termination
  
  Print the result:

#+begin_src c
  printf("Emp2 = %15.12f\n", Emp2);
}
#+end_src

About

Prototype MP2 code

Resources

Stars

0 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages