Compiling Circuits for EeroQ via Qiskit

Open in Colab Launch Binder

Below is a brief tutorial on Superstaq compilation for EeroQ Quantum Hardware whose quantum computer uses electrons bound to superfluid helium. For more information on EeroQ, visit their website here.

Imports and API Token

This example tutorial notebook uses qiskit-superstaq, our Superstaq client for Qiskit; you can try it out by running pip install qiskit-superstaq:

[ ]:
from __future__ import annotations
[1]:
# Required imports
try:
    import qiskit
    import qiskit_superstaq as qss
except ImportError:
    print("Installing qiskit-superstaq...")
    %pip install --quiet 'qiskit-superstaq[examples]'
    print("Installed qiskit-superstaq.")
    print("You may need to restart the kernel to import newly installed packages.")
    import qiskit
    import qiskit_superstaq as qss

import qiskit.circuit.random

To interface Superstaq via Qiskit, we must first instantiate a provider in qiskit-superstaq with SuperstaqProvider(). We then supply a Superstaq API token (or key) by either providing the API token as an argument of qss.SuperstaqProvider() or by setting it as an environment variable (see more details here)

[2]:
# Instantiate a qiskit superstaq provider
provider = qss.SuperstaqProvider()

EeroQ Gates

One of the native gates that EeroQ devices operate is the Dipole-Dipole (DD) gate, which couples two electrons. This gate can be applied to electrons within a qubit to perform rotation gates, or across qubits to perform entangling gates. The DD gate is available as a custom gate in qiskit-superstaq.

[3]:
dd_gate = qss.DDGate(1)
[ ]:
# Define a simple circuit with a DD gate
qc = qiskit.QuantumCircuit(2)
qc.append(dd_gate, [0, 1])
qc.draw("mpl")
../../_images/optimizations_eeroq_eeroq_qss_11_0.png

Below is the EeroQ protocol for a CZ Gate.

title

Single Circuit Compilation

With that gateset, we can compile to the EeroQ Wonderlake device by instantiating an EeroQ backend. We can then call the compile method on that backend.

We can compile to the EeroQ Wonderlake device by instantiating an EeroQ backend. We can then call the backend.compile method.

[5]:
# Create a two-qubit qiskit circuit
qc = qiskit.QuantumCircuit(2)
qc.cz(0, 1)
qc.measure_all()

# Draw circuit for visualization
qc.draw("mpl")
[5]:
../../_images/optimizations_eeroq_eeroq_qss_17_0.png
[6]:
# Create the backend to compile for
backend = provider.get_backend("eeroq_wonderlake_qpu")
[7]:
# Compile circuit and retrieve by calling `.circuit`
compiled_circuit = backend.compile(qc).circuit

Below we define some helper functions to remove any inactive qubits in the compiled circuit:

[ ]:
def count_gates(qc: qiskit.QuantumCircuit) -> dict[object, int]:
    gate_count = dict.fromkeys(qc.qubits, 0)
    for gate in qc.data:
        for qubit in gate.qubits:
            gate_count[qubit] += 1
    return gate_count


def remove_idle_wires(qc: qiskit.QuantumCircuit) -> qiskit.QuantumCircuit:
    qc_out = qc.copy()
    gate_count = count_gates(qc_out)
    for qubit, count in gate_count.items():
        if count == 0:
            qc_out.qubits.remove(qubit)
    return qc_out
[9]:
# Visualize the compiled circuit
remove_idle_wires(compiled_circuit).draw("mpl")
[9]:
../../_images/optimizations_eeroq_eeroq_qss_22_0.png

Multiple Circuit Compilation

We can repeat the above experiment with larger circuits to see how they compile. Instead of compiling a single circuit at a time, we can also compile a list of circuits in one-go. To illustrate this, let us create two circuits: a Bell circuit, and another randomly generated qiskit circuit:

[10]:
bell_qc = qiskit.QuantumCircuit(2)
bell_qc.h(0)
bell_qc.cx(0, 1)
bell_qc.draw("mpl")
[10]:
../../_images/optimizations_eeroq_eeroq_qss_25_0.png
[11]:
rand_qc = qiskit.circuit.random.random_circuit(2, 3, measure=False)
rand_qc.draw("mpl")
[11]:
../../_images/optimizations_eeroq_eeroq_qss_26_0.png
[12]:
# Pass in a list of circuits to `compile`
compiled_outputs = backend.compile([bell_qc, rand_qc])

# To get the list of compiled circuits from the compiled outputs list, call `circuits` instead of just `circuit` which was called for the single circuit input
compiled_circuits = compiled_outputs.circuits

Here’s the compiled Bell circuit,

[13]:
remove_idle_wires(compiled_circuits[0]).draw("mpl")
[13]:
../../_images/optimizations_eeroq_eeroq_qss_29_0.png

And the compiled random circuit, fully expressed in the native gateset of EeroQ:

[14]:
remove_idle_wires(compiled_circuits[1]).draw("mpl", fold=-1)
[14]:
../../_images/optimizations_eeroq_eeroq_qss_31_0.png

Using the Superstaq Simulator

Lastly, we will go over how to simulate a circuit to the EeroQ Wonderlake QPU. This feature is available to free trial users, and can be done by passing the "dry-run" method parameter when calling backend.run() to instruct Superstaq to ideally sample the circuit. Let us generate a random circuit again to demonstrate:

[15]:
# Example random circuit including measurements
qc = qiskit.circuit.random.random_circuit(2, 3, measure=True)
qc.draw("mpl")
[15]:
../../_images/optimizations_eeroq_eeroq_qss_33_0.png
[ ]:
# Specify "dry-run" as the method to run an ideal Superstaq execution
job = backend.run(qc, shots=1000, method="dry-run")

# Get the counts from the measurement
print(job.result(0).get_counts())
{'00': 1000}

We can additionally perform a noisy simulation of the circuit by setting the method argument to "noise-sim" and specifying an error rate.

[17]:
noisy_job = backend.run(qc, shots=1000, method="noise-sim", error_rate=0.01)
[18]:
print(noisy_job.result(0).get_counts())
{'00': 892, '12': 16, '10': 21, '20': 31, '22': 6, '02': 9}

With the effect of noise, we no longer just measure \(\ket{00}\) like in the dry-run simulation. Note that a measurement value of 2 refers to an out-of-codespace error.