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-chem
, quri-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.
)