Wayne H Nixalo – 2018/6/26
A codealong of the OpenFermion ProjectQ demo example.
In [1]:
%matplotlib inline
%reload_ext autoreload
%autoreload 2
We now demonstrate how one can use both OpenFermion and ProjectQ to run a simple VQE example using a Unitary Coupled Cluster ansatz. It demonstrates a simple way to evaluate the energy, optimize the energy wrt the ansatz and build the corresponding compiled quantum circuit. It utilizes OpenFermion to prepare the Hamiltonians as well as initial parameters and ProjectQ to build and simulate the circuit.
In [2]:
import os
import numpy as np
from scipy.optimize import minimize as scipy_minimize
from openfermion.config import *
from openfermionprojectq import *
from openfermion.hamiltonians import MolecularData
from openfermion.transforms import jordan_wigner # hi jordan
from openfermion.utils import uccsd_singlet_paramsize
from projectq.ops import X, All, Measure
from projectq.backends import CommandPrinter, CircuitDrawer
Here we load H$_2$ from a precomputed molecule file found in the test data directory, and initialize the ProjectQ circuit compiler to a standard setting thta uses a first-order Trotter decomposition to break up the exponentials of non-commuting operators.
In [3]:
# Load the molecule
filename = os.path.join(DATA_DIRECTORY, 'H2_sto-3g_singlet_0.7414')
molecule = MolecularData(filename=filename)
# Use a Jordan-Wiger encoding, and compress to remove 0 imaginary components
qubit_hamiltonian = jordan_wigner(molecule.get_molecular_hamiltonian())
qubit_hamiltonian.compress()
compiler_engine = uccsd_trotter_engine()
wait where is it getting data_dir... how does it know where it is?
In [4]:
DATA_DIRECTORY
Out[4]:
Ohhh. The package must an absolute path to its root folder when you build it. So if you move the library it you have to rebuild it. Got it.
$$E(θ) = \big\langle Ψ(θ)\lvert H \lvert Ψ(θ) \big\rangle \,\,\,\,\, (1)$$The Variational Quantum Eigensolver (or VQE) works by parameterizeing a wavefunction $\lvert Ψ(θ) \big\rangle$ through some quantum circuit and minimizing the nergy wrt that angle, which is defined by
To perform the VQE loop with a simple molecule, it helps to wrap the evaluation of the energy into a simple objective function that takes the parameters of the circuit and returns the enregy. Here we define that function using ProjectQ to handle the qubits and the simulation.
In [5]:
def energy_objective(packed_amplitudes):
"""Evaluate the energy of a UCCSD singlet wavefunction w/ packed_amplitudes
Args:
packed_amplitudes(ndarray): Compact array that stores the unique
amplitudes for a UCCSD singlet wavefunction.
Returns:
energy(float): Energy corresponding to the given amplitudes
"""
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
# Set Jordan-Wigner initial state w/ correct number of electrons
wavefunction = compiler_engine.allocate_qureg(molecule.n_qubits)
for i in range(molecule.n_electrons):
X | wavefunction[i]
# Build the circuit and act it on the wavefunction
evolution_operator = uccsd_singlet_evolution(packed_amplitudes,
molecule.n_qubits,
molecule.n_electrons)
evolution_operator | wavefunction
compiler_engine.flush()
# Evaluate the energy and reset wavefunction
energy = compiler_engine.backend.get_expectation_value(qubit_hamiltonian, wavefunction)
All(Measure) | wavefunction
compiler_engine.flush()
return energy
While we could plug this objective function into any optimizer, SciPy offers a convenient framework within the Python ecosystem. We'll choose as starting amplitudes the classical CCSD values that can be loaded from the molecule if desired. The optimal energy is found and compared to the exact values to verify that our simulation was successful.
In [6]:
n_amplitudes = uccsd_singlet_paramsize(molecule.n_qubits, molecule.n_electrons)
initial_amplitudes = [0, 0.05677]
initial_energy = energy_objective(initial_amplitudes)
# Run VQE Optimization to find new CCSD parameters
opt_result = scipy_minimize(energy_objective, initial_amplitudes,
method='CG', options={'disp':True})
opt_energy, opt_amplitudes = opt_result.fun, opt_result.x
print("\nOptimal UCCSD Singlet Energy: {}".format(opt_energy))
print("Optimal UCCSD Singlet Amplitudes: {}".format(opt_amplitudes))
print("Classical CCSD Energy: {} Hartrees".format(molecule.ccsd_energy))
print("Exact FCI Energy: {} Hartrees".format(molecule.fci_energy))
print("Initial Energy of UCCSD with CCSD amplitudes: {} Hartrees".format(initial_energy))
As we can see, the optimization terminates extremely quickly because the classical coupled cluster amplitudes were (for this molecule) already optimal. We can now use ProjectQ to compile this simulation circuit to a set of 2-body quantum gates.
In [7]:
compiler_engine = uccsd_trotter_engine(CommandPrinter())
wavefunction = compiler_engine.allocate_qureg(molecule.n_qubits)
for i in range(molecule.n_electrons):
X | wavefunction[i]
# Build the circuit and act it on the wavefunction
evolution_operator = uccsd_singlet_evolution(opt_amplitudes,
molecule.n_qubits,
molecule.n_electrons)
evolution_operator | wavefunction
compiler_engine.flush()
I have no idea what I did, but it felt cool.