6-1. OpenFermion basics

In this chapter, we introduce a method to convert the Hamiltonian of an interacting electron system into a form that is easy for quantum computers to handle using OpenFermion[1], a python library for quantum chemistry calculation. OpenFermion provides connectivity to the open source libraries for quantum chemical calculations Psi4 and PySCF. Even if you do not understand the details of how to use these libraries, you can obtain the Hamiltonian of the electron system that appears in quantum chemical calculations simply by entering the structure of the molecule. Here, we use PySCF.

[ ]:
## If various libraries are not installed, please execute.
## If you run on Google Colaboratory, ignore the following message waring, 'You must restart the runtime in order to use newly installed versions.'
## Resuming runtime will lead to a crash.
!pip install qulacs pyscf openfermion openfermionpyscf


## Run only if you are in Google Colaboratory or in a local environment where Qulacs is not installed
## Qulacs errors will now be output normally.
!pip3 install wurlitzer
%load_ext wurlitzer
[1]:
#Import required libraries
#If you get an error, install openfermion later than v.1.0.0
import numpy as np
import matplotlib.pyplot as plt
from openfermion.chem import MolecularData
from openfermion.transforms import get_fermion_operator, jordan_wigner, bravyi_kitaev
from openfermion.linalg import get_sparse_operator
from openfermion.ops import FermionOperator
from openfermionpyscf import run_pyscf
from pyscf import fci

Calculation of hydrogen molecule

In openfermion, we enter data describing molecules into MolecularData class.

[2]:
#define constants
basis = "sto-3g"  #basis set
multiplicity = 1  #spin multiplicity
charge = 0        #total charge for the molecule
distance = 0.65
geometry = [("H",(0,0,0)),("H", (0,0,distance))]  #xyz coordinates for atoms
description = str(distance)  #description for the psi4 output file

molecule = MolecularData(geometry, basis, multiplicity, charge, description)

Variable description

The meaning of the variables appearing in the above code is explained below.

basis: basis function

Set basis functions for representing molecular orbitals. There are various basis sets such as sto-3g, 6-31G.

sto-3g (Slater Type Orbital - 3 gaussian) used here is a basis function that approximates Slater type orbital with 3 gaussians.

Slater type orbital is an orbital that resembles the solution of a hydrogen atom. As a radial function, we use

\[R_{nl}(r) = r^{n-l} \exp \left(-\frac{Z-s}{na_0}r\right),\]

In angular direction, we use spherical harmonic function \(Y_{lm}(\theta,\phi)\).

sto-3g uses a function which approximates this radial wavefunction \(R_{nl}(r)\) with three gaussians.

multiplicity: spin multiplicity

Since electrons have a spin of 1/2, the spin multiplicity is 2 when one electron exists in isolation. However, in the case of a hydrogen molecule, electrons form a singlet in the ground state, and the total spin is 0. Since spin 0 has only one state, the spin multiplicity is set to 1 in this case.

charge: total charge

Input the total charge. When considering an ion, it becomes + or −.

geometry: nuclear configuration

Specify atomic species and their coordinates in x,y,z.

description

The output results calculated by pyscf are saved in the directory where the openfermion library is saved. This is a variable that determines the name of the file.

Calculatin with PySCF

Input the MolecularData set above to the function run_pyscf and perform quantum chemical calculations with PySCF. It should finish in a few seconds.

[3]:
molecule = run_pyscf(molecule,run_scf=1,run_fci=1)

HF & Full-CI energy

Let’s take a look at the Hartree-Fock energy and Full-CI energy (=exact basis energy) calculated by PySCF. (1 Hartree = 27.2116 eV)

[4]:
print("HF energy: {} (Hartree)".format(molecule.hf_energy))
print("FCI energy: {} (Hartree)".format(molecule.fci_energy))
HF energy: -1.1129965456691684 (Hartree)
FCI energy: -1.1299047843229135 (Hartree)

One-Electron integral \(h_{ij}\)・Two-Electron integral \(h_{ijkl}\)

Quantities such as the one-electron and two-electron integrals are also stored in the MolecularData class.

[5]:
print(molecule.one_body_integrals)
[[-1.30950987e+00  1.98461056e-17]
 [ 8.14840166e-17 -4.10026381e-01]]
[6]:
print(molecule.two_body_integrals)
[[[[ 6.91904405e-01 -4.16333634e-17]
   [-2.77555756e-17  1.76318452e-01]]

  [[-2.77555756e-17  1.76318452e-01]
   [ 6.79683914e-01  0.00000000e+00]]]


 [[[-4.16333634e-17  6.79683914e-01]
   [ 1.76318452e-01  8.32667268e-17]]

  [[ 1.76318452e-01  8.32667268e-17]
   [ 0.00000000e+00  7.14671111e-01]]]]

Hamiltonian in second quantized form

openfermion computes the Hamiltonian of the second quantized form from these integrals.

\[H = \sum_i h_{ij}c_i^\dagger c_j + \sum_{ijkl} h_{ijkl} c_i^\dagger c_j^\dagger c_k c_l\]

(For the second quantization, see e.g. reference [2]). The Hamiltonian can be calculated by calling the get_molecular_hamiltonian method.

For example, (3,1)represents \(c_3^\dagger\), and (1,0) represents \(c_1\) .

[7]:
print(molecule.get_molecular_hamiltonian())
() 0.8141187860307693
((0, 1), (0, 0)) -1.309509868464871
((1, 1), (1, 0)) -1.309509868464871
((2, 1), (2, 0)) -0.4100263808117837
((3, 1), (3, 0)) -0.4100263808117837
((0, 1), (0, 1), (0, 0), (0, 0)) 0.3459522026149022
((0, 1), (0, 1), (2, 0), (2, 0)) 0.0881592258051036
((0, 1), (1, 1), (1, 0), (0, 0)) 0.3459522026149022
((0, 1), (1, 1), (3, 0), (2, 0)) 0.0881592258051036
((0, 1), (2, 1), (0, 0), (2, 0)) 0.0881592258051036
((0, 1), (2, 1), (2, 0), (0, 0)) 0.3398419569652305
((0, 1), (3, 1), (1, 0), (2, 0)) 0.0881592258051036
((0, 1), (3, 1), (3, 0), (0, 0)) 0.3398419569652305
((1, 1), (0, 1), (0, 0), (1, 0)) 0.3459522026149022
((1, 1), (0, 1), (2, 0), (3, 0)) 0.0881592258051036
((1, 1), (1, 1), (1, 0), (1, 0)) 0.3459522026149022
((1, 1), (1, 1), (3, 0), (3, 0)) 0.0881592258051036
((1, 1), (2, 1), (0, 0), (3, 0)) 0.0881592258051036
((1, 1), (2, 1), (2, 0), (1, 0)) 0.3398419569652305
((1, 1), (3, 1), (1, 0), (3, 0)) 0.0881592258051036
((1, 1), (3, 1), (3, 0), (1, 0)) 0.3398419569652305
((2, 1), (0, 1), (0, 0), (2, 0)) 0.33984195696523034
((2, 1), (0, 1), (2, 0), (0, 0)) 0.0881592258051036
((2, 1), (1, 1), (1, 0), (2, 0)) 0.33984195696523034
((2, 1), (1, 1), (3, 0), (0, 0)) 0.0881592258051036
((2, 1), (2, 1), (0, 0), (0, 0)) 0.0881592258051036
((2, 1), (2, 1), (2, 0), (2, 0)) 0.35733555551906837
((2, 1), (3, 1), (1, 0), (0, 0)) 0.0881592258051036
((2, 1), (3, 1), (3, 0), (2, 0)) 0.35733555551906837
((3, 1), (0, 1), (0, 0), (3, 0)) 0.33984195696523034
((3, 1), (0, 1), (2, 0), (1, 0)) 0.0881592258051036
((3, 1), (1, 1), (1, 0), (3, 0)) 0.33984195696523034
((3, 1), (1, 1), (3, 0), (1, 0)) 0.0881592258051036
((3, 1), (2, 1), (0, 0), (1, 0)) 0.0881592258051036
((3, 1), (2, 1), (2, 0), (3, 0)) 0.35733555551906837
((3, 1), (3, 1), (1, 0), (1, 0)) 0.0881592258051036
((3, 1), (3, 1), (3, 0), (3, 0)) 0.35733555551906837

Convert to tractable operators for quantum computers

The easiest to handle on a quantum computer are the Pauli operators \(I, X, Y, Z\) and their tensor products. Therefore, in order to treat the Hamiltonian of ordinary electrons with a quantum computer, convert the Hamiltonian of the second quantization form

\[H_{fermion} = \sum_i h_{ij}c_i^\dagger c_j + \sum_{ijkl} h_{ijkl} c_i^\dagger c_j^\dagger c_k c_l\]

to

\[H_{qubit} = \sum_{P\in \{I,X,Y,Z\}^{\otimes n}} h_{P} P\]

Various transformation methods have been proposed, but here we use the simplest one, called the Jordan-Wigner transformation. In the Jordan-Wigner transformation, the molecular orbital \(i\) corresponds to the \(i\)-th qubit. The state that the molecular orbital is occupied by an electron is represented with \(|1\rangle\), otherwise \(|0\rangle\).

Under such a convention, constructing the Pauli operator so as to satisfy the anticommutation relation of the following creation-annihilation operator of fermion,

\[\{c^\dagger_i, c^\dagger_j\} = c^\dagger_i c^\dagger_j + c^\dagger_j c^\dagger_i = 0, \: \{c_i, c_j\} = 0, \: \{c^\dagger_i, c_j\} = \delta_{ij}\]

we obtain

\[a^{\dagger}_{j} \leftrightarrow \frac{X_j-iY_j}{2}\otimes Z_{j-1}\otimes Z_{j-2} \cdots Z_{1}\]

For transformation methods other than the Jordan-Wigner transformation, see [2][3].

Jordan-Wigner transformation is implemented in openfermion. By passing FermionOperator to jordan_wigner function, it will return QubitOperator corresponding to the Jordan-Wigner transformation of that operator. Below, the FermionOperator is created from the MolecularData of the hydrogen molecule created above, and the Jordan-Wigner transform is used to convert the Hamiltonian of the hydrogen molecule into a form that can be easily handled by a quantum computer.

[8]:
jw_hamiltonian = jordan_wigner(get_fermion_operator(molecule.get_molecular_hamiltonian()))
print(jw_hamiltonian)
(0.0377511039464572+0j) [] +
(-0.0440796129025518+0j) [X0 X1 Y2 Y3] +
(0.0440796129025518+0j) [X0 Y1 Y2 X3] +
(0.0440796129025518+0j) [Y0 X1 X2 Y3] +
(-0.0440796129025518+0j) [Y0 Y1 X2 X3] +
(0.18601648886230573+0j) [Z0] +
(0.1729761013074511+0j) [Z0 Z1] +
(0.12584136558006342+0j) [Z0 Z2] +
(0.16992097848261523+0j) [Z0 Z3] +
(0.18601648886230576+0j) [Z1] +
(0.16992097848261523+0j) [Z1 Z2] +
(0.12584136558006342+0j) [Z1 Z3] +
(-0.26941693141632095+0j) [Z2] +
(0.17866777775953419+0j) [Z2 Z3] +
(-0.26941693141632095+0j) [Z3]

Let’s calculate the Hartree-Fock (HF) energy from this Hamiltonian. In the Jordan-Wigner transform, the qubit \(\left|0\right\rangle, \left|1\right\rangle\) and the orbital occupancy numbers correspond one-to-one. Therefore, in order to calculate the HF energy, calculate the expected value for \(\left|1100\right\rangle\). Because theare 2 electrons, the state is filled with 1 twice from the bottom.

[9]:
# function for computing the tensor product
def kron_N(*ops):
    tmp = ops[0]
    for op in ops[1:]:
        tmp = np.kron(tmp,op)
    return tmp

bra0 = np.array([[1,0]])
bra1 = np.array([[0,1]])
HFbra = kron_N(bra1, bra1, bra0, bra0)
HFket = HFbra.T
print(HFbra)
jw_matrix = get_sparse_operator(jw_hamiltonian)
print(np.real(HFbra.dot(jw_matrix.dot(HFket))), molecule.hf_energy)
[[0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]]
[[-1.11299655]] -1.1129965456691684

It can be confirmed that it almost matches the calculation of pyscf.

Now let’s diagonalize the Hamiltonian and see that the result agrees with the Full-CI (exact solution) energy.

[10]:
eigenenergies, eigenvecs = np.linalg.eigh(jw_matrix.toarray())
print(eigenenergies[0], molecule.fci_energy)
-1.129904784322913 -1.1299047843229135

It can be confirmed that this also almost matches. The ground state wavefunction \(\left|\psi_g\right\rangle\) is

[11]:
print(eigenvecs[:,0])
[ 0.        +0.j  0.        +0.j  0.        +0.j  0.09545811+0.j
  0.        +0.j  0.        +0.j  0.        +0.j  0.        +0.j
  0.        +0.j  0.        +0.j  0.        +0.j  0.        +0.j
 -0.99543345+0.j  0.        +0.j  0.        +0.j  0.        +0.j]

\(\left|\psi_g \right\rangle \approx - 0.995\left|1100\right\rangle + 0.0955 \left|0011\right\rangle\). It turns out that the Hartree-Fock solution \(\left|1100\right\rangle\) is slightly combined with the two-electron excited state \(\left|0011\right\rangle\).

Reference

[1] R. Babbush and J. McClean, “Announcing OpenFermion: The Open Source Package for Quantum Computers”, Google AI Blog, https://ai.googleblog.com/2017/10/announcing-openfermion-open-source.html
[2] A. Tranter, P. J. Love, F. Mintert, and P. V. Convey, “A Comparison of the Bravyi and Jordan-Wigner Transformations for the Quantum Simulation of Quantum Chemistry“, J. Chem. Theory Comput. 2018, 14, 11, 5617-5630
[3] S. McArdle, S. Endo, A. Aspuru-Guzik, S. Benjamin, X. Yuan, “Quantum computational chemistry”, arXiv:1808.10402