Tutorial 1: Entanglement Forging for estimating the ground state energy of the H2 molecule

Entanglement forging is a method which allows us to represent expectation values of a 2n-qubit wavefunction as sums of multiple expectation values of n-qubit states, embedded in a classical optimization, thus doubling the size of the system that can be exactly simulated with a fixed number of qubits.

In the Circuit Knitting Toolbox, entanglement forging is implemented to estimate the ground state energy of a molecule. Entanglement forging is most easily run using the EntanglementForgingGroundStateSolver interface, which wraps most of the algorithm within a single function call, solve. Steps 1-4 describe the workflow within solve. See the explanatory material for more detailed information:

  1. Decompose:

    • Decompose the 2n-qubit ansatz (wavefunction) into many parameterized, n-qubit ansatze

    • Decompose the 2n-qubit observable (Hamiltonian) into many n-qubit observables

  2. Evaluate:

    • Use the n-qubit expectation values to reconstruct an estimation of the original 2n-qubit expectation value

  3. Reconstruct

    • Use the expectation values to reconstruct an estimation of the expectation value for the 2n-qubit system

  4. If the expectation value (energy) has not converged, update the ansatze parameters and continue with Step 2

Instantiate the ElectronicStructureProblem

First, we set up the \(\mathrm{H}_2\) molecule, specify the driver and converter, and instantiate an ElectronicStructureProblem, just like is done in the Qiskit Nature VQE tutorial.

[1]:
from qiskit_nature.drivers import Molecule
from qiskit_nature.drivers.second_quantization import PySCFDriver
from qiskit_nature.problems.second_quantization import ElectronicStructureProblem

molecule = Molecule(
    geometry=[
        ("H", [0.0, 0.0, 0.0]),
        ("H", [0.0, 0.0, 0.735]),
    ],
    charge=0,
    multiplicity=1,
)
driver = PySCFDriver.from_molecule(molecule=molecule, basis="sto3g")
problem = ElectronicStructureProblem(driver)

Configure the entanglement forging specific inputs

The ansatz for Entanglement Forging consists of a set of input bitstrings and a parameterized circuit. (See the explanatory material section of the documentation for additional background on the method.) For this demo, we will use the same bitstrings and ansatz for both the U and V subsystems, and we will use the TwoLocal circuit from Qiskit.

[2]:
from circuit_knitting_toolbox.entanglement_forging import EntanglementForgingAnsatz
from qiskit.circuit.library import TwoLocal

ansatz = EntanglementForgingAnsatz(
    circuit_u=TwoLocal(2, [], "cry", [[0, 1], [1, 0]], reps=1),
    bitstrings_u=[(1, 0), (0, 1)],
)

ansatz.circuit_u.draw()
[2]:
     ┌──────────────────────┐
q_0: ┤0                     ├
     │  TwoLocal(θ[0],θ[1]) │
q_1: ┤1                     ├
     └──────────────────────┘

Set up the Qiskit Runtime Service

The Qiskit Runtime Service provides access to Qiskit Runtime Primitives and quantum backends. See the Qiskit Runtime documentation for more information. Here, we specify the backend(s) to be used to evaluate the circuits. Backends could be simulator(s) and/or quantum device(s).

[3]:
from qiskit_ibm_runtime import QiskitRuntimeService, Options

# By default, use a local simulator to implement the Qiskit Runtime Primitives
service = None

# Uncomment the following line to instead use the Qiskit Runtime Service.
# service = QiskitRuntimeService(channel="ibm_quantum")

backend_names = ["ibmq_qasm_simulator"] * 2

# If a single set of options are passed, it will be applied to all backends
options = [Options(execution={"shots": 1000}), Options(execution={"shots": 2000})]

Set up EntanglementForgingGroundStateSolver

Next, we set up the EntanglementForgingGroundStateSolver. It is passed the ansatz, a classical optimizer (COBYLA, in this case), and an initial point.

  • The ansatz field is required.

  • If no optimizer is passed, SPSA with default settings will be used.

  • If a Qiskit Runtime Service is not passed, then a local simulator will be used with the Qiskit Primitives, and the backend_names argument will be ignored.

  • If multiple backend names are passed, the expectation value calculations at each iteration will be divided evenly among them and calculated in parallel.

  • If a single options argument is passed, it will be used for all backends. If a list of options is passed, they will be synchronized with the backends 1:1

  • If the initial_point field is not set, it will be initialized to all 0’s.

[4]:
import numpy as np
from qiskit.algorithms.optimizers import COBYLA
from circuit_knitting_toolbox.entanglement_forging import (
    EntanglementForgingGroundStateSolver,
)

optimizer = COBYLA(maxiter=100)

solver = EntanglementForgingGroundStateSolver(
    ansatz=ansatz,
    optimizer=optimizer,
    service=service,
    backend_names=backend_names,
    options=options,
    initial_point=[0.0, np.pi / 2],
)

Calculate the ground state energy

Once a user has set up their ElectronicStructureProblem, EntanglementForgingAnsatz, and other program options, the only thing remaining is call EntanglementForgingGroundStateSolver.solve.

[5]:
%%capture

results = solver.solve(problem)

Visualize the results

Visualize the convergence of the estimated ground state energy and the Schmidt coefficients as the ansatz parameters are optimized.

[6]:
import matplotlib.pyplot as plt

print("Energy:")
plt.plot([evaluation.eigenvalue for evaluation in results.history])
plt.xlabel("Iterations")
plt.show()

print("Schmidt Coefficients:")
plt.plot([abs(evaluation.eigenstate) for evaluation in results.history])
plt.xlabel("Iterations")
plt.yscale("log")
plt.show()

print("Parameters:")
plt.plot([evaluation.parameters for evaluation in results.history])
plt.xlabel("Iterations")
plt.show()
Energy:
../../_images/tutorials_entanglement_forging_tutorial_1_getting_started_12_1.png
Schmidt Coefficients:
../../_images/tutorials_entanglement_forging_tutorial_1_getting_started_12_3.png
Parameters:
../../_images/tutorials_entanglement_forging_tutorial_1_getting_started_12_5.png
[7]:
import qiskit.tools.jupyter

%qiskit_version_table

Version Information

Qiskit SoftwareVersion
qiskit-terra0.22.1
qiskit-aer0.11.1
qiskit-ibmq-provider0.19.2
qiskit0.39.1
qiskit-nature0.4.5
System information
Python version3.9.13
Python compilerClang 12.0.0
Python buildmain, Aug 25 2022 18:29:29
OSDarwin
CPUs8
Memory (Gb)32.0
Thu Nov 03 18:09:29 2022 CDT

This code is a Qiskit project.

© Copyright IBM 2022.

This code is licensed under the Apache License, Version 2.0. You may obtain a copy of this license in the LICENSE.txt file in the root directory of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this copyright notice, and modified files need to carry a notice indicating that they have been altered from the originals.