Variational Quantum Eigensolver

A common application of the Variational Quantum Eigensolver (VQE) algorithm is to compute the ground state energy of a molecular system. The code below demonstrates how to perform classical preprocessing for a \(H_2\) molecule (i.e. obtain the integrals from a Hartree-Fock computation to build the molecular Hamiltonian), prepare the initial Hartree-Fock state on the quantum register, add the parameterized UCCSD ansatz to the kernel, and select the COBYLA optimizer. We are then ready to call cudaq:vqe to estimate the minimum energy of the system.

[1]:
# !pip install openfermionpyscf matplotlib scipy
[2]:
import cudaq
import matplotlib.pyplot as plt
from scipy.optimize import minimize
import numpy as np

np.random.seed(42)

cudaq.set_target("nvidia")

The problem of interest here is a chain of hydrogen atoms seperated along the z-axis at a fixed interval called the bond distance.

The interatomic electrostatic forces due to the electrons and protons and the shielding by the neutrons creates a chemical system whose energy can be minimised to find a stable configuration.

Let us first begin by defining the molecule and other metadata about the problem.

[3]:
# Number of hydrogen atoms.
hydrogen_count = 2

# Distance between the atoms in Angstroms.
bond_distance = 0.7474

# Define a linear chain of Hydrogen atoms
geometry = [('H', (0, 0, i * bond_distance)) for i in range(hydrogen_count)]

molecule, data = cudaq.chemistry.create_molecular_hamiltonian(
    geometry, 'sto-3g', 1, 0)

electron_count = data.n_electrons
qubit_count = 2 * data.n_orbitals

We now generate a Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz from the template provided by CUDA Quantum.

[1]:
kernel, angles = cudaq.make_kernel(list)
qubits = kernel.qalloc(qubit_count)

# Prepare the Hartree Fock State.
kernel.x(qubits[0])
kernel.x(qubits[1])

# Adds parameterized gates based on the UCCSD ansatz.
kernel.apply_call(cudaq.kernels.uccsd, qubits, angles, electron_count,
                  qubit_count)

parameter_count = cudaq.kernels.uccsd_num_parameters(electron_count,
                                                     qubit_count)
-1.1371745102369863

Using CUDA Quantum Optimizers

We use the builtin optimizers within CUDA Quantum for the minimization procedure.

[5]:
optimizer = cudaq.optimizers.COBYLA()

energy, parameters = cudaq.vqe(kernel,
                               molecule,
                               optimizer,
                               argument_mapper=lambda parameters:
                               (parameters, qubit_count, electron_count),
                               parameter_count=parameter_count)

print(energy)
-1.1371756094989427

Integration with Third-Party Optimizers

We can also integrate popular libraries like scipy with CUDA Quantum.

[6]:
# Define a function to minimize
def to_minimize(theta):

    exp_val = cudaq.observe(kernel, molecule, theta).expectation()

    return exp_val


exp_vals = []


def callback(xk):
    exp_vals.append(to_minimize(xk))


# Initial variational parameters.
x0 = np.random.uniform(low=0, high=2 * np.pi, size=parameter_count)

# Use the scipy optimizer to minimize the function of interest
result = minimize(to_minimize,
                  x0,
                  method='COBYLA',
                  callback=callback,
                  options={'maxiter': 40})

plt.plot(exp_vals)
plt.xlabel('Epochs')
plt.ylabel('Energy')
plt.title('VQE')
plt.show()
../../../_images/examples_python_tutorials_vqe_11_0.png