OpenFermion Project Q Demo


Wayne H Nixalo – 2018/6/26

A codealong of the OpenFermion ProjectQ demo example.


In [1]:
%matplotlib inline
%reload_ext autoreload
%autoreload 2

Simulating a Variational Quantum Eigensolver using OpenFermion ProjectQ

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]:
'/Users/WayNoxchi/Deshar/Quantum/OpenFermion/src/openfermion/data'

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.

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

$$E(θ) = \big\langle Ψ(θ)\lvert H \lvert Ψ(θ) \big\rangle \,\,\,\,\, (1)$$

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))


Optimization terminated successfully.
         Current function value: -1.137270
         Iterations: 1
         Function evaluations: 12
         Gradient evaluations: 3

Optimal UCCSD Singlet Energy: -1.1372701746253246
Optimal UCCSD Singlet Amplitudes: [1.15179120e-09 5.65340671e-02]
Classical CCSD Energy: -1.1372701746527767 Hartrees
Exact FCI Energy: -1.137270174625328 Hartrees
Initial Energy of UCCSD with CCSD amplitudes: -1.137269814563729 Hartrees

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()


Allocate | Qureg[0]
Allocate | Qureg[1]
Allocate | Qureg[2]
Allocate | Qureg[3]
X | Qureg[0]
X | Qureg[1]
Rx(1.570796326795) | Qureg[0]
H | Qureg[2]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
Rz(1.152e-09) | Qureg[2]
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
H | Qureg[2]
Rx(10.995574287564) | Qureg[0]
H | Qureg[0]
Rx(1.570796326795) | Qureg[2]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
Rz(12.566370613207) | Qureg[2]
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
Rx(10.995574287564) | Qureg[2]
H | Qureg[0]
Rx(1.570796326795) | Qureg[0]
Rx(1.570796326795) | Qureg[1]
Rx(1.570796326795) | Qureg[2]
H | Qureg[3]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(0.028267033545) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
H | Qureg[3]
Rx(10.995574287564) | Qureg[2]
Rx(10.995574287564) | Qureg[1]
Rx(10.995574287564) | Qureg[0]
Rx(1.570796326795) | Qureg[0]
H | Qureg[1]
Rx(1.570796326795) | Qureg[2]
Rx(1.570796326795) | Qureg[3]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(12.538103580814) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
Rx(10.995574287564) | Qureg[3]
Rx(10.995574287564) | Qureg[2]
H | Qureg[1]
Rx(10.995574287564) | Qureg[0]
H | Qureg[0]
H | Qureg[1]
Rx(1.570796326795) | Qureg[2]
H | Qureg[3]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(12.538103580814) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
H | Qureg[3]
Rx(10.995574287564) | Qureg[2]
H | Qureg[1]
H | Qureg[0]
H | Qureg[0]
Rx(1.570796326795) | Qureg[1]
Rx(1.570796326795) | Qureg[2]
Rx(1.570796326795) | Qureg[3]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(12.538103580814) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
Rx(10.995574287564) | Qureg[3]
Rx(10.995574287564) | Qureg[2]
Rx(10.995574287564) | Qureg[1]
H | Qureg[0]
Rx(1.570796326795) | Qureg[0]
H | Qureg[1]
H | Qureg[2]
H | Qureg[3]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(0.028267033545) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
H | Qureg[3]
H | Qureg[2]
H | Qureg[1]
Rx(10.995574287564) | Qureg[0]
Rx(1.570796326795) | Qureg[0]
Rx(1.570796326795) | Qureg[1]
H | Qureg[2]
Rx(1.570796326795) | Qureg[3]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(0.028267033545) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
Rx(10.995574287564) | Qureg[3]
H | Qureg[2]
Rx(10.995574287564) | Qureg[1]
Rx(10.995574287564) | Qureg[0]
H | Qureg[0]
Rx(1.570796326795) | Qureg[1]
H | Qureg[2]
H | Qureg[3]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(0.028267033545) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
H | Qureg[3]
H | Qureg[2]
Rx(10.995574287564) | Qureg[1]
H | Qureg[0]
H | Qureg[0]
H | Qureg[1]
H | Qureg[2]
Rx(1.570796326795) | Qureg[3]
CX | ( Qureg[0], Qureg[1] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(12.538103580814) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[0], Qureg[1] )
Rx(10.995574287564) | Qureg[3]
H | Qureg[2]
H | Qureg[1]
H | Qureg[0]
Rx(1.570796326795) | Qureg[1]
H | Qureg[3]
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(1.152e-09) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
H | Qureg[3]
Rx(10.995574287564) | Qureg[1]
H | Qureg[1]
Rx(1.570796326795) | Qureg[3]
CX | ( Qureg[1], Qureg[2] )
CX | ( Qureg[2], Qureg[3] )
Rz(12.566370613207) | Qureg[3]
CX | ( Qureg[2], Qureg[3] )
CX | ( Qureg[1], Qureg[2] )
Rx(10.995574287564) | Qureg[3]
H | Qureg[1]

I have no idea what I did, but it felt cool.