The latest version of this notebook is available on https://github.com/qiskit/qiskit-tutorial.
Antonio Mezzacapo[1], Richard Chen[1], Marco Pistoia[1], Shaohan Hu[1], Peng Liu[1], Stephen Wood[1], Jay Gambetta[1]
One of the most compelling possibilities of quantum computation is the the simulation of other quantum systems. Quantum simulation of quantum systems encompasses a wide range of tasks, including most significantly:
Simulation of the time evolution of quantum systems.
Computation of ground state properties.
These applications are especially useful when considering systems of interacting fermions, such as molecules and strongly correlated materials. The computation of ground state properties of fermionic systems is the starting point for mapping out the phase diagram of condensed matter Hamiltonians. It also gives access to the key question of electronic structure problems in quantum chemistry - namely, reaction rates. The focus of this notebook is on molecular systems, which are considered to be the ideal bench test for early-stage quantum computers, due to their relevance in chemical applications despite relatively modest sizes. Formally, the ground state problem asks the following:
For some physical Hamiltonian H, find the smallest eigenvalue $E_G$, such that $H|\psi_G\rangle=E_G|\psi_G\rangle$, where $|\Psi_G\rangle$ is the eigenvector corresponding to $E_G$.
It is known that in general this problem is intractable, even on a quantum computer. This means that we cannot expect an efficient quantum algorithm that prepares the ground state of general local Hamiltonians. Despite this limitation, for specific Hamiltonians of interest it might be possible, given physical constraints on the interactions, to solve the above problem efficiently. Currently, at least four different methods exist to approach this problem:
In this notebook we focus on the last method, as this is most likely the simplest to be realized on near-term devices.
The general idea is to define a parameterization $|\psi(\boldsymbol\theta)\rangle$ of quantum states, and minimize the energy
$$E(\boldsymbol\theta) = \langle \psi(\boldsymbol\theta)| H |\psi(\boldsymbol\theta)\rangle,$$The key ansatz is that the number of parameters $|\boldsymbol\theta^*|$ that minimizes the energy function scales polynomially with the size (e.g., number of qubits) of the target problem.
Then, any local fermionic Hamiltonian can be mapped into a sum over Pauli operators $P_i$,
$$H\rightarrow H_P = \sum_i^M w_i P_i,$$and the energy corresponding to the state $|\psi(\boldsymbol\theta\rangle$, $E(\boldsymbol\theta)$, can be estimated by sampling the individual Pauli terms $P_i$ (or sets of them that can be measured at the same time) on a quantum computer:
$$E(\boldsymbol\theta) = \sum_i^M w_i \langle \psi(\boldsymbol\theta)| P_i |\psi(\boldsymbol\theta)\rangle.$$Last, some optimization technique must be devised in order to find the optimal value of parameters $\boldsymbol\theta^*$, such that $|\psi(\boldsymbol\theta^*)\rangle\equiv|\psi_G\rangle$.
The Hamiltonians describing systems of interacting fermions can be expressed in second quantization language, considering fermionic creation (annihilation) operators $a^\dagger_\alpha(a_\alpha)$, relative to the $\alpha$-th fermionic mode. In the case of molecules, the $\alpha$ labels stand for the different atomic or molecular orbitals. Within the second-quantization framework, a generic molecular Hamiltonian with $M$ orbitals can be written as $$H =H_1+H_2=\sum_{\alpha, \beta=0}^{M-1} t_{\alpha \beta} \, a^\dagger_{\alpha} a_{\beta} +\frac{1}{2} \sum_{\alpha, \beta, \gamma, \delta = 0}^{M-1} u_{\alpha \beta \gamma \delta}\, a^\dagger_{\alpha} a^\dagger_{\gamma} a_{\delta} a_{\beta},$$ with the one-body terms representing the kinetic energy of the electrons and the potential energy that they experience in the presence of the nuclei, $$ t_{\alpha\beta}=\int d\boldsymbol x_1\Psi_\alpha(\boldsymbol{x}_1) \left(-\frac{\boldsymbol\nabla_1^2}{2}+\sum_{i} \frac{Z_i}{|\boldsymbol{r}_{1i}|}\right)\Psi_\beta (\boldsymbol{x}_1),$$ and their interactions via Coulomb forces $$ u_{\alpha\beta\gamma\delta}=\int\int d \boldsymbol{x}_1 d \boldsymbol{x}_2 \Psi_\alpha^*(\boldsymbol{x}_1)\Psi_\beta(\boldsymbol{x}_1)\frac{1}{|\boldsymbol{r}_{12}|}\Psi_\gamma^*(\boldsymbol{x}_2)\Psi_\delta(\boldsymbol{x}_2),$$ where we have defined the nuclei charges $Z_i$, the nuclei-electron and electron-electron separations $\boldsymbol{r}_{1i}$ and $\boldsymbol{r}_{12}$, the $\alpha$-th orbital wavefunction $\Psi_\alpha(\boldsymbol{x}_1)$, and we have assumed that the spin is conserved in the spin-orbital indices $\alpha,\beta$ and $\alpha,\beta,\gamma,\delta$.
We consider in this notebook the optimization of two potential energy surfaces, for the hydrogen and lithium hydride molecules, obtained using the STO-3G basis. The molecular Hamiltonians are computed as a function of their interatomic distance, then mapped to two-(H$_2$) and four-(LiH$_2$) qubit problems, via elimination of core and high-energy orbitals and removal of $Z_2$ symmetries.
In order to find the optimal parameters $\boldsymbol\theta^*$, we set up a closed optimization loop with a quantum computer, based on some stochastic optimization routine. Our choice for the variational ansatz is a deformation of the one used for the optimization of classical combinatorial problems, with the inclusion of $Z$ rotation together with the $Y$ ones. The optimization algorithm for fermionic Hamiltonians is similar to the one for combinatorial problems, and can be summarized as follows:
Note that, as opposed to the classical case, in the case of a quantum chemistry Hamiltonian one has to sample over non-computational states that are superpositions, and therefore take advantage of using a quantum computer in the sampling part of the algorithm. Motivated by the quantum nature of the answer, we also define a variational trial ansatz in this way:
$$|\psi(\boldsymbol\theta)\rangle = [U_\mathrm{single}(\boldsymbol\theta) U_\mathrm{entangler}]^m |+\rangle$$where $U_\mathrm{entangler}$ is a collection of cPhase gates (fully entangling gates), $U_\mathrm{single}(\boldsymbol\theta) = \prod_{i=1}^n Y(\theta_{i})Z(\theta_{n+i})$ are single-qubit $Y$ and $Z$ rotation, $n$ is the number of qubits and $m$ is the depth of the quantum circuit.
References and additional details:
[1] A. Kandala, A. Mezzacapo, K. Temme, M. Takita, M. Brink, J. M. Chow, and J. M. Gambetta, Hardware-efficient Variational Quantum Eigensolver for Small Molecules and Quantum Magnets, Nature 549, 242 (2017), and references therein.
In [7]:
# useful additional packages
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
from qiskit_aqua_chemistry import AquaChemistry
import warnings
warnings.filterwarnings('ignore')
# setup aqua_chemistry logging
import logging
from qiskit_aqua_chemistry._logging import set_logging_config, build_logging_config
# set_logging_config(build_logging_config(logging.DEBUG)) # choose INFO, DEBUG to see the log
In [2]:
from qiskit import IBMQ
IBMQ.load_accounts()
In this first part of the notebook we show the optimization of the H$_2$ Hamiltonian in the STO-3G basis at the bond length of 0.735 Angstrom. After mapping it to a four-qubit system with a parity transformation, two spin-parity symmetries are modded out, leading to a two-qubit Hamiltonian. The energy of the mapped Hamiltonian obtained is then minimized using the variational ansatz described in the introduction, and a stochastic perturbation simultaneous approximation (SPSA) gradient descent method. We stored the precomputed one- and two-body integrals and other molecular information in the hdf5 file.
In [3]:
# First, we use classical eigendecomposition to get ground state energy (including nuclear repulsion energy) as reference.
aqua_chemistry_dict = {
'driver': {'name': 'HDF5'},
'HDF5': {'hdf5_input': 'H2/H2_equilibrium_0.735_sto-3g.hdf5'},
'operator': {'name':'hamiltonian',
'qubit_mapping': 'parity',
'two_qubit_reduction': True},
'algorithm': {'name': 'ExactEigensolver'}
}
solver = AquaChemistry()
result = solver.run(aqua_chemistry_dict)
print('Ground state energy (classical): {:.12f}'.format(result['energy']))
# Second, we use variational quantum eigensolver (VQE)
aqua_chemistry_dict['algorithm']['name'] = 'VQE'
aqua_chemistry_dict['optimizer'] = {'name': 'SPSA', 'max_trials': 350}
aqua_chemistry_dict['variational_form'] = {'name': 'RYRZ', 'depth': 3, 'entanglement':'full'}
aqua_chemistry_dict['backend'] = {'name': 'statevector_simulator'}
solver = AquaChemistry()
result = solver.run(aqua_chemistry_dict)
print('Ground state energy (quantum) : {:.12f}'.format(result['energy']))
print("====================================================")
# You can also print out other info in the field 'printable'
for line in result['printable']:
print(line)
In [4]:
# select H2 or LiH to experiment with
molecule='H2'
aqua_chemistry_dict = {
'driver': {'name': 'HDF5'},
'HDF5': {'hdf5_input': ''},
'operator': {'name':'hamiltonian',
'qubit_mapping': 'parity',
'two_qubit_reduction': True},
'algorithm': {'name': ''},
'optimizer': {'name': 'SPSA', 'max_trials': 350},
'variational_form': {'name': 'RYRZ', 'depth': 3, 'entanglement':'full'}
}
# choose which backend want to use
# backend = {'name': 'statevector_simulator'}
backend = {'name': 'qasm_simulator', 'shots': 1024}
algos = ['ExactEigensolver', 'VQE']
if molecule == 'LiH':
mol_distances = np.arange(0.6, 5.1, 0.1)
aqua_chemistry_dict['operator']['freeze_core'] = True
aqua_chemistry_dict['operator']['orbital_reduction'] = [-3, -2]
aqua_chemistry_dict['optimizer']['max_trials'] = 2500
aqua_chemistry_dict['variational_form']['depth'] = 5
else:
mol_distances = np.arange(0.2, 4.1, 0.1)
energy = np.zeros((len(algos), len(mol_distances)))
for j, algo in enumerate(algos):
aqua_chemistry_dict['algorithm']['name'] = algo
if algo == 'ExactEigensolver':
aqua_chemistry_dict.pop('backend', None)
elif algo == 'VQE':
aqua_chemistry_dict['backend'] = backend
print("Using {}".format(algo))
for i, dis in enumerate(mol_distances):
print("Processing atomic distance: {:1.1f} Angstrom".format(dis), end='\r')
aqua_chemistry_dict['HDF5']['hdf5_input'] = "{}/{:1.1f}_sto-3g.hdf5".format(molecule, dis)
result = solver.run(aqua_chemistry_dict)
energy[j][i] = result['energy']
print("\n")
In [5]:
for i, algo in enumerate(algos):
plt.plot(mol_distances, energy[i], label=algo)
plt.xlabel('Atomic distance (Angstrom)')
plt.ylabel('Energy')
plt.legend()
plt.show()