Running on Quantinuum Backends

In this tutorial, we demonstrate how to perform a simple quantum chemical calculation on Quantinuum hardware.

Since this tutorial focuses on practical quantum computation, we will perform a simple calculation: the single point (i.e. not optimized/not variationally solved) total energy evaluation of the H2 molecule in the Unitary Coupled Cluster (UCC) ansatz for a set value of the ansatz variational parameter.

This tutorial will require that you have credentials for Quantinuum Systems, which can be obtained by contacting Quantinuum support. You will require credits (HQCs) to run on emulators and hardware. Users can access Quantinuum backends through pytket-quantinuum or via Quantinuum Nexus

The steps below are such:

  • Define the system

  • Perform computation with emulated hardware noise (QuantinuumBackend emulator + machine noise profile)

  • Demonstrate error mitigation methods on emulated hardware (PMSV)

1. Define the system

# Preload the Hamiltonian for H2
# see the Aer tutorial for details
from inquanto.express import load_h5

h2 = load_h5("h2_sto3g.h5", as_tuple=True)
hamiltonian = h2.hamiltonian_operator

from inquanto.spaces import FermionSpace
from inquanto.states import FermionState
from inquanto.symmetry import PointGroup
from inquanto.ansatzes import FermionSpaceStateExpChemicallyAware

# Define fermion space, state, and map the fermionic operator to qubits
space = FermionSpace(
    4, point_group=PointGroup("D2h"), orb_irreps=["Ag", "Ag", "B1u", "B1u"]
)

state = FermionState([1, 1, 0, 0])
qubit_hamiltonian = hamiltonian.qubit_encode()

exponents = space.construct_single_ucc_operators(state)
## the above adds nothing due to the symmetry of the system
exponents += space.construct_double_ucc_operators(state)
# Construct an efficient ansatz
ansatz = FermionSpaceStateExpChemicallyAware(exponents, state)

p = ansatz.state_symbols.construct_from_array([0.4996755931358105])
print(p)

# Import an InQuanto Computable for measuring an expecation value. 
# The operator is the qubit Hamiltonian, and the wavefunction is the ansatz.
from inquanto.computables import ExpectationValue

print(hamiltonian)
expectation0 = ExpectationValue(ansatz, hamiltonian.qubit_encode())

# Analyze the ansatz circuit
from pytket.circuit.display import render_circuit_jupyter
from pytket import Circuit, OpType

render_circuit_jupyter(ansatz.get_circuit(p))  # this is the uncompiled ansatz circuit

print("2-qubit GATES:  {}".format(ansatz.circuit_resources()['gates_2q']))
print(ansatz.state_circuit)
{d0: 0.4996755931358105}
<inquanto.operators._chemistry_integral_operator.ChemistryRestrictedIntegralOperator object at 0x1431b8230>
2-qubit GATES:  4
<tket::Circuit, qubits=4, gates=31>

2. Machine emulation for quantum noise

Running emulator experiments before hardware experiments is a crucial step in the development and optimization of quantum algorithms and applications. Emulators provide a controlled environment where one can fine-tune algorithms, explore error mitigation strategies, and gain valuable insights about the behavior of quantum circuits without some constraints of physical hardware.

Below, we provide instructions for conducting experiments utilizing a Quantinuum emulator. To utilize hardware instead of the emulator one only needs to change their choice of device when instantiating the QuantinuumBackend.

Note that we use the pytket-quantinuum extension to access a Quantinuum backend.

QuantinuumBackend is a pytket backend that calls a Quantinuum device (“H2-1”, “H2-2”) or its emulator with the corresponding noise profile (“H2-1E”, “H2-2E”). The emulators are run remotely on a server. Accessing the backend retrieves information from your Quantinuum account. More information can be found on the pytket-quantinuum page.

For comparison in the figure below, we have also plotted the exact energy (-0.5876463677224993 Ha) for H\(_2\) and the user should also compare these results to result from the first part of this tutorial.

from pytket.extensions.quantinuum import QuantinuumBackend

# Initialize the backend, make sure to login with your credentials. 
# Change the machine name and add the label and the group if necessary.
machine = "H2-1E"
backend = QuantinuumBackend(device_name=machine)

# The QuantinuumBackend has additional arguements for accessing resources and labelling circuits
# label (Optional[str], optional) – Job labels used if Circuits have no name, defaults to “job”
# group (Optional[str], optional) – string identifier of a collection of jobs, can be used for usage tracking.

# Running the next line (device_state) will require logging into your 
# quantinuum account. This can also be called with backend.login()
print(machine, "status:", QuantinuumBackend.device_state(device_name=machine))
H2-1E status: online
from inquanto.protocols import PauliAveraging
from pytket.partition import PauliPartitionStrat

# here we demonstrate building the 
noisy_protocol = PauliAveraging(
    backend,
    shots_per_circuit=10000,
    pauli_partition_strategy=PauliPartitionStrat.CommutingSets,
)
noisy_protocol.build(p, ansatz, hamiltonian.qubit_encode())
noisy_protocol.compile_circuits()

# you can inspect compiled measurement circuits contained in the protocol that was run on the backend
# note that the gateset of this circuit is different to the ansatz 

render_circuit_jupyter(noisy_protocol.get_circuits()[1])
noisy_handles = noisy_protocol.launch()
# Retrieve the results of the 10000 shot job once it has completed
noisy_results = noisy_handles.get_results()

Here we employ the BackendResultBootsrap class of inquanto.protocols to resample the results of 10000 executed shots for a number of different sample sizes. First, we asynchronously launch and retrieve the job with 10000 shots then apply the resampling.

from inquanto.protocols import BackendResultBootstrap

sample_sizes = [10, 50, 100, 500, 1000, 5000]
noisy_H2_1E_energies = []

for i in sample_sizes:
    # One sample, seed = 0 (default), with i number of shots
    resample = BackendResultBootstrap(1, 0, i)
    resampled_result = resample.get_sampled_results(noisy_results)
    noisy_H2_1E_energies.append(noisy_protocol.retrieve(resampled_result[0]).evaluate_expectation_value(
        ansatz, hamiltonian.qubit_encode()))

# Append the expectation value of the 10000 shots result
noisy_H2_1E_energies.append(noisy_protocol.evaluate_expectation_value(
    ansatz, hamiltonian.qubit_encode()
))
import matplotlib.pyplot as plt
# we plot the results of our sampling
plt.rcParams["figure.figsize"] = (12, 4)

shots_set = sample_sizes.copy()
shots_set.append(10000)

plt.hlines(
    y=-0.5876463677224993,
    xmin=10,
    xmax=50000,
    ls="--",
    colors="black",
    label="Exact Energy",
)
plt.plot(shots_set, noisy_H2_1E_energies, label="H2-1E")
plt.xscale("log")
plt.ylim([-1.3, -0.3])
plt.xlabel("Number of shots")
plt.ylabel("Energy (Ha)")
plt.title(
    "Convergence behavior of the expectation value for "
    + r"$\theta=$%.5f" % list(p.values())[0]
)
plt.legend()
<matplotlib.legend.Legend at 0x11a681a90>
../_images/1ef1a7639de87322bb3f400af2f5d0e25e483c8d525980d18fb84e82b392893b.png

3. Noise mitigation methods in Quantinuum emulation

We can use noise mitigation techniques to reduce the impact of noise in our expectation value. In this case we will ‘purify’ results by discarding a shot if it has a certain error. There are many other mitigation methods.

Specifically, we will define the symmetries of the Qubit Operators in the system to use PMSV (Partition Measurement Symmetry Verification). As a result, noise mitigation improves the accuracy of the energy obtained from the quantum hardware compared to the unmitigated results and the exact energy of H2.

State Preparation and Measurement (SPAM) correction, which calibrates the calculation for system noise, can also be used.

from inquanto.protocols.averaging._mitigation import PMSV
from inquanto.mappings import QubitMappingJordanWigner


stabilizers = QubitMappingJordanWigner().operator_map(
    space.symmetry_operators_z2_in_sector(state)
)

mitms_pmsv = PMSV(stabilizers)

miti_protocol = PauliAveraging(
        backend,
        shots_per_circuit=10000,
        pauli_partition_strategy=PauliPartitionStrat.CommutingSets,
    )
miti_protocol.build(
        p, ansatz, hamiltonian.qubit_encode(), noise_mitigation=mitms_pmsv
    )
miti_protocol.compile_circuits()
handles = miti_protocol.launch()
miti_results = handles.get_results()
miti_H2_1E_energies = []

# Once again we apply BackendResultBootstrap to resample 10000 shots
for i in sample_sizes:
    # One sample, seed = 0 (default), with i number of shots
    resample = BackendResultBootstrap(1, 0, i)
    resampled_result = resample.get_sampled_results(miti_results)
    miti_H2_1E_energies.append(miti_protocol.retrieve(resampled_result[0]).evaluate_expectation_value(
        ansatz, hamiltonian.qubit_encode()))

# Append the expectation value of the 10000 shots result
miti_H2_1E_energies.append(miti_protocol.evaluate_expectation_value(
    ansatz, hamiltonian.qubit_encode()
))
#statevector 
plt.hlines(
    y=-0.5876463677224993,
    xmin=10,
    xmax=50000,
    ls="--",
    colors="black",
    label="Exact Energy",
)
plt.plot(shots_set, noisy_H2_1E_energies, label="H2-1E")
plt.plot(shots_set, miti_H2_1E_energies, label="H2-1E mitigated")
plt.xscale("log")
plt.ylim([-1.3, -0.3])
plt.xlabel("Number of shots")
plt.ylabel("Energy (Ha)")
plt.title(
    "Convergence behavior of the expectation value for "
    + r"$\theta=$%.5f" % list(p.values())[0]
)
plt.legend()
<matplotlib.legend.Legend at 0x11a4aea80>
../_images/b5436dc5f97e3f0ebf0f5d08fea730ccdc0190b4822076d1fc0446dcb856f4f0.png

One may also explore the impact of SPAM on enhancing the convergence behavior of the expectation value. In the case of this simple system with a shallow circuit, the energy converges with slightly improved efficiency and/or heightened precision upon employing noise mitigation techniques.

Transitioning to hardware experiments is straightforward, regardless of whether pytket-quantinuum or qnexus backends are used. The user simply changes the device name (e.g., from “H2-1E” to “H2-1”), and the circuit will be run on the physical quantum device provided sufficient credits and circuit syntax.