Computing Electron Integrals and Molecular Hamiltonians#

This tutorial is for demonstrating how to obtain the molecular orbital (MO) electron integrals (\(h_{ij}\) and \(h_{ijkl}\)) as well as the molecular Hamiltonian (\(H\)).

Here, we adopt the physicists’ convention for the electron integrals, so that they are related to the Hamiltonian by the equation:

\begin{equation} H = E_{\text{nuc}} + \sum_{i, j = 1}^{N} h_{ij} c_i^{\dagger} c_j + \frac{1}{2} \sum_{i, j, k, l = 1}^{N} h_{ijkl} c_i^{\dagger} c_j^{\dagger} c_k c_l, \nonumber \end{equation}

where

  • \(E_{\text{nuc}}\) is the nuclear repulsion energy.

  • \(h_{ij}\) is the 1-electron MO integral (physicist’s convention).

  • \(h_{ijkl}\) is the 2-electron MO integral (physicist’s convention).

  • \(c_i^{\dagger}\), \(c_i\) are the fermionic creation and annihilation operators on the i-th spin orbtial.

  • \(N\) is the number of spin oribtals.

Prerequisite#

QURI Parts modules used in this tutorial: quri-parts-chemquri-parts-pyscf, and quri-parts-openfermion. You can install them as follows:

[1]:
!pip install "quri_parts[chem]"
!pip install "quri_parts[pyscf]"
!pip install "quri_parts[openfermion]"

Quick Overview#

First, let’s have a quick overview of the steps necessary for constructing the molecular Hamiltonian for a given molecule.

Step 1: Define the molecule#

[2]:
from pyscf import gto, scf

h2o_atom_list = [['H', [0, 0, 0]], ['O', [2, 0, 1]], ['H',  [0, 0, 2]]]
h2o_mol = gto.M(atom=h2o_atom_list, verbose=0)
h2o_mf = scf.RHF(h2o_mol).run()
h2o_mo_coeff = h2o_mf.mo_coeff  # The mo coefficient of the H2O molecule.

Step 2: Compute the MO elctron integrals#

[3]:
from quri_parts.pyscf.mol import get_spin_mo_integrals_from_mole
from quri_parts.chem.mol import ActiveSpace

full_space, mo_eint_set = get_spin_mo_integrals_from_mole(h2o_mol, h2o_mo_coeff)
active_space, active_space_mo_eint_set = get_spin_mo_integrals_from_mole(
    h2o_mol,
    h2o_mo_coeff,
    ActiveSpace(6, 4)
)

Step 3: Obtain the Qubit Hamiltonian#

[4]:
from quri_parts.openfermion.mol import get_qubit_mapped_hamiltonian

# Full space qubit hamiltonian
full_space_jw_hamiltonian, mapping = get_qubit_mapped_hamiltonian(
    full_space, mo_eint_set
)

# Active space qubit hamiltonian
active_space_jw_hamiltonian, mapping = get_qubit_mapped_hamiltonian(
    active_space, active_space_mo_eint_set,
)

Defining the Molecule#

First, let’s create the molecule we are interested in. In later part of this tutorial, we will be using quri-parts-pyscf to perform the computation for electron integrals. So, we create the molecule using the pyscf library.

[5]:
from pyscf import gto

h2o_atom_list = [['H', [0, 0, 0]], ['O', [2, 0, 1]], ['H',  [0, 0, 2]]]
h2o_mol = gto.M(atom=h2o_atom_list, verbose = 0)

Another key component of computing the MO electron integral is the MO coefficients, which can also be computed using the pyscf library.

[6]:
from pyscf import scf

h2o_mf = scf.RHF(h2o_mol).run()
h2o_mo_coeff = h2o_mf.mo_coeff  # The MO coefficient of the H2O molecule.

Computing the MO electron integrals#

Having prepared the molecule and the corresponding electron integrals, we may now compute the MO electron integrals. In QURI Parts, the molecular orbital electron integrals (MO eInts) are represented by a SpinMOeIntSet object.

[7]:
from quri_parts.pyscf.mol import get_spin_mo_integrals_from_mole

full_space, mo_eint_set = get_spin_mo_integrals_from_mole(h2o_mol, h2o_mo_coeff)

The mo_eint_set variable we created above is a SpinMOeIntSet that contains the nuclear repulsion energy \(E_{\text{nuc}}\) and the electron integrals \(h_{ij}\) and \(h_{ijkl}\). We may access them with:

[8]:
nuclear_energy = mo_eint_set.const
mo_1e_int = mo_eint_set.mo_1e_int.array
mo_2e_int = mo_eint_set.mo_2e_int.array

The full_space variable is an ActiveSpace object that contains the number of active spatial orbitals and active electrons involved in the system, which we introduce briefly in the next section.

[9]:
n_spatial_orbitals = full_space.n_active_orb
n_spatial_electrons = full_space.n_active_ele

Active Space and the active space MO electron integrals#

In quri-parts, the active space is represented by the ActiveSpace object.

[10]:
from quri_parts.chem.mol import ActiveSpace

active_space = ActiveSpace(n_active_ele=6, n_active_orb=4)

To obtain the active space MO electron integrals, we pass in the ActiveSpace object we just created into the get_spin_mo_integrals_from_mole function.

[11]:
active_space, active_space_mo_eint_set = get_spin_mo_integrals_from_mole(h2o_mol, h2o_mo_coeff, active_space)

active_space_core_energy = active_space_mo_eint_set.const
active_space_1e_int = active_space_mo_eint_set.mo_1e_int.array
active_space_2e_int = active_space_mo_eint_set.mo_2e_int.array

Computing the molecular Hamiltonian#

After obtaining the MO electron integrals, we may start to construct the molecular Hamiltonian. We introduce the procedures of computing the fermionic Hamiltonian as well as the qubit Hamiltonian.

Obtaining the fermionic Hamiltonian and converting it to the qubit Hamiltonian#

The fermionic Hamiltonian can be directly constructed using the mo_eint_set or active_space_mo_eint_set we obtained before

[12]:
from quri_parts.openfermion.mol import get_fermionic_hamiltonian

full_space_fermionic_hamiltonian = get_fermionic_hamiltonian(mo_eint_set)
active_space_fermionic_hamiltonian = get_fermionic_hamiltonian(active_space_mo_eint_set)

To perform any further computation with QURI Parts, e.g. estimate Hamiltonian expectation value for a quantum state, we need to perform fermion-qubit mapping to the fermionic hamiltonian we just obtained. We also provide the operator_from_of_fermionic_op function for this purpose.

[13]:
from quri_parts.openfermion.mol import operator_from_of_fermionic_op
from quri_parts.openfermion.transforms import jordan_wigner

# Full space qubit hamiltonian

full_space_jw_hamiltonian, full_space_mapping = operator_from_of_fermionic_op(
    full_space_fermionic_hamiltonian,
    full_space,
    sz=None,                # Default to None
    fermion_qubit_mapping=jordan_wigner  # Default to jordan wigner.
)

# Active space qubit hamiltonian

active_space_jw_hamiltonian, active_space_mapping = operator_from_of_fermionic_op(
    active_space_fermionic_hamiltonian,
    active_space,
    sz=None,                # Default to None
    fermion_qubit_mapping=jordan_wigner  # Default to jordan wigner.
)

The full_space_jw_hamiltonian and active_space_jw_hamiltonian are the hamiltonian we desired. The full_space_mapping and active_space_mapping are objects that are able to perform fermion-qubit mapping for other operators and states in further computations. Their usage can be found in the Fermion-Qubit Mapping Hamiltonian Tutorial.

Shortcut for obtaining the qubit Hamiltonian#

After obtaining the active space and the MO electron integrals, we may obtain the qubit Hamiltonian directly without going through the fermionic Hamiltonian. This can be done by the function

[14]:
from quri_parts.openfermion.mol import get_qubit_mapped_hamiltonian

# Full space qubit hamiltonian
full_space_jw_hamiltonian, full_space_mapping = get_qubit_mapped_hamiltonian(
    full_space,
    mo_eint_set,
    sz=None,                # Default to None
    fermion_qubit_mapping=jordan_wigner  # Default to jordan wigner.
)

# Active space qubit hamiltonian
active_space_jw_hamiltonian, active_space_mapping = get_qubit_mapped_hamiltonian(
    active_space,
    active_space_mo_eint_set,
    sz=None,                # Default to None
    fermion_qubit_mapping=jordan_wigner  # Default to jordan wigner.
)