Compiling Circuits for EeroQ via Cirq
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 cirq-superstaq, our Superstaq client for Cirq; you can try it out by running pip install cirq-superstaq:
[1]:
# Required imports
try:
import cirq
import cirq_superstaq as css
except ImportError:
print("Installing cirq-superstaq...")
%pip install --quiet 'cirq-superstaq[examples]'
print("Installed cirq-superstaq.")
print("You may need to restart the kernel to import newly installed packages.")
import cirq
import cirq_superstaq as css
import numpy as np
To interface Superstaq via Cirq, we must first instantiate a service provider in cirq-superstaq with Service(). We then supply a Superstaq API key (which you can get from https://superstaq.infleqtion.com) by either providing the API key as an argument of Service, i.e., css.Service(api_key="token"), or by setting it as an environment variable. (see more details
here).
[2]:
# Instantiate a cirq superstaq service
service = css.Service()
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 cirq-superstaq.
[3]:
dd_gate = css.DDPowGate(exponent=1)
print(cirq.Circuit(dd_gate.on(cirq.q(0), cirq.q(1))))
[3]:
0: ───DD───
│
1: ───DD───[4]:
# Unitary definition of the DD gate
cirq.unitary(dd_gate)
[4]:
array([[ 0.-1.j, 0.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, -1.+0.j, 0.+0.j],
[ 0.+0.j, -1.+0.j, 0.+0.j, 0.+0.j],
[ 0.+0.j, 0.+0.j, 0.+0.j, 0.-1.j]])
Below is the EeroQ protocol for a CZ Gate.

Single Circuit Compilation
With that gateset, we can compile to the EeroQ Wonderlake device by calling the compile() method and setting the target argument to "eeroq_wonderlake_qpu".
[5]:
# Define the `cirq` circuit to compile
circuit = cirq.Circuit(cirq.CZ(cirq.q(0), cirq.q(1)))
# Compile to native gateset
compiler_output = service.compile(circuit, target="eeroq_wonderlake_qpu")
# Call `.circuit` on the compiler output to get the corresponding output circuit
compiled_circuit = compiler_output.circuit
# Visualize the compiled circuit
print(compiled_circuit)
0+: ───────│──────────────────────────────
│
0-: ───X───│───DD────────S──────DD────────
│ │ │
1+: ───────│───┼─────────Z──────┼─────────
│ │ │
1-: ───X───│───DD^0.25───S^-1───DD^0.25───
To verify that the compiled circuit achieves the same unitary action, we can manually compute the corresponding unitary matrix and compare it to the unitary of the original circuit:
[ ]:
def compute_unitary(circuit: cirq.Circuit) -> object:
"""Helper function to compute the n x n unitary of a 2n electron EeroQ circuit."""
unitary = cirq.unitary(circuit[1:]).reshape((4,) * cirq.num_qubits(circuit))
mat = unitary[tuple(slice(1, 3) for _ in range(cirq.num_qubits(circuit)))]
dim = round(np.sqrt(mat.size))
mat = mat.reshape(dim, dim)
return mat
[7]:
mat = compute_unitary(compiled_circuit)
mat / mat[0][0]
[7]:
array([[ 1.-0.00000000e+00j, -0.+0.00000000e+00j, -0.+0.00000000e+00j,
-0.+0.00000000e+00j],
[-0.+0.00000000e+00j, 1.+7.85046229e-17j, -0.+0.00000000e+00j,
-0.+0.00000000e+00j],
[-0.+0.00000000e+00j, -0.+0.00000000e+00j, 1.+7.85046229e-17j,
-0.+0.00000000e+00j],
[-0.+0.00000000e+00j, -0.+0.00000000e+00j, -0.+0.00000000e+00j,
-1.-0.00000000e+00j]])
[8]:
cirq.allclose_up_to_global_phase(cirq.unitary(circuit), mat)
[8]:
True
As we can see, the initial circuit and compiled circuit have equivalent unitaries, despite the difference in circuit width.
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 circuit given a gate_domain:
[9]:
qubits = cirq.LineQubit.range(2)
bell_circuit = cirq.Circuit(cirq.H(qubits[0]), cirq.CNOT(qubits[0], qubits[1]))
print(bell_circuit)
0: ───H───@───
│
1: ───────X───
[10]:
# The gateset to choose from
gate_domain = {
cirq.X: 1,
cirq.Y: 1,
cirq.Z: 1,
cirq.S: 1,
cirq.T: 1,
cirq.H: 1,
cirq.rx(1.23): 1,
cirq.ry(2.34): 1,
cirq.CZ: 2,
cirq.CX: 2,
cirq.CX**0.5: 2,
cirq.SWAP: 2,
cirq.ISWAP: 2,
css.ZZSwapGate(1.23): 2,
css.Barrier(3): 3,
}
[11]:
n, depth, op_density = (4, 8, 0.8)
qubits = cirq.LineQubit.range(n)
rand_circuit = cirq.testing.random_circuit(qubits, depth, op_density, gate_domain=gate_domain)
print(rand_circuit)
0: ───X───────────H───Ry(0.745π)────────────────────────iSwap───
│ │
1: ───┼───iSwap────────────────────Rx(0.392π)───Y───────┼───────
│ │ │
2: ───┼───iSwap────────────────────H────────────────×───┼───────
│ │ │
3: ───@─────────────────────────────────────────────×───iSwap───
[12]:
# Barriers inserted for visualization
rand_circuit.insert(depth // 2, css.barrier(*qubits))
# Pass in a list of circuits to `compile`
compiled_outputs = service.compile([bell_circuit, rand_circuit], "eeroq_wonderlake_qpu")
# To get the list of compiled circuits from the compiled outputs list, call `circuits` instead of just `circuit` which is called for a single circuit input
compiled_circuits = compiled_outputs.circuits
Here’s the compiled Bell circuit,
[13]:
print(compiled_circuits[0])
0+: ───────│───S^-1───│───DD────────S──────│─────────────Z────────────────│───DD────────Z───
│ │ │ │ │ │
0-: ───X───│──────────│───DD^-0.5──────────│───DD────────S──────DD────────│───DD────────────
│ │ │ │ │ │
1+: ───────│───S──────│───DD────────S^-1───│───┼─────────S^-1───┼─────────│───DD────────S───
│ │ │ │ │ │ │ │
1-: ───X───│──────────│───DD^-0.5──────────│───DD^0.25───S^-1───DD^0.25───│───DD^-0.5───────
And the compiled random circuit, fully expressed in the native gateset of EeroQ:
[14]:
print(compiled_circuits[1])
┌──────────────┐ ┌──────────────┐
0+: ───────│───S────────│───DD────────S^-1──────│──────────────────────S─────────────────────────│───DD──────────S^-1──────│─────────────Z──────────────────│──────────────────────│───Z^0.75─────│───DD──────────Z^-0.75────│────────────────────────────────────│─────────────────────────│──────────────────────────────────│─────────────────────────│──────────────────────────────────│──────────────────────────│─────────────T^-1─────────────────│───DD────────T────────│─────────────Z^0.75──────────────│───DD────────T^-1─────
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
0-: ───X───│────────────│───DD^-0.5─────────────│────DD────────────────S───────DD────────────────│───DD^-0.745─────────────│────────────────────────────────│──────────────────────│──────────────│───DD^-0.5────────────────│────────────────────────────────────│─────────────────────────│──────────────────────────────────│─────────────────────────│──────────────────────────────────│──────────────────────────│───DD────────S──────────DD────────│───DD^-0.5────────────│───DD────────S─────────DD────────│───DD^-0.5────────────
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
1+: ───────│───Z^0.75───│───DD────────Z^-0.75───│────┼─────────────────T^-1────┼─────────────────│───DD──────────T─────────│─────────────Z^0.75─────────────│───DD────────T^-1─────│───Z──────────│───DD──────────Z──────────│─────────────Z──────────────────────│─────────────────────────│──────────────────────────────────│─────────────────────────│──────────────────────────────────│──────────────────────────│───┼────────────────────┼─────────│──────────────────────│───┼───────────────────┼─────────│──────────────────────
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
1-: ───X───│────────────│───DD^-0.5─────────────│────┼──────DD─────────S───────┼──────DD─────────│───DD^-0.5───────────────│───DD────────S────────DD────────│───DD^-0.5────────────│──────────────│───DD^-0.608──────────────│────────────────────────────────────│─────────────────────────│──────────────────────────────────│─────────────────────────│──────────────────────────────────│──────────────────────────│───┼────────────────────┼─────────│──────────────────────│───┼───────────────────┼─────────│──────────────────────
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
2+: ───────│───T^-1─────│───DD────────T─────────│────┼──────┼──────────T^-1────┼──────┼──────────│───DD──────────Z^-0.75───│───┼─────────Z^0.75───┼─────────│───DD────────Z^0.75───│───Z^0.276────│───DD──────────Z^-0.276───│─────────────Z^(-15/16)─────────────│───DD────────Z^(15/16)───│─────────────Z^(1/16)─────────────│───DD────────Z^(-1/16)───│─────────────Z^0.229──────────────│───DD──────────Z^-0.229───│───┼─────────Z^-0.309───┼─────────│──────────────────────│───┼───────────────────┼─────────│──────────────────────
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
2-: ───X───│────────────│───DD^-0.5─────────────│────┼──────DD^0.25────S^-1────┼──────DD^0.25────│───DD^-0.5───────────────│───DD^0.25───S^-1─────DD^0.25───│───DD^-0.5────────────│──────────────│───DD^-0.762──────────────│───DD────────S────────────DD────────│───DD^-0.5───────────────│───DD────────S──────────DD────────│───DD^-0.5───────────────│───DD────────S──────────DD────────│───DD^-0.321──────────────│───┼────────────────────┼─────────│──────────────────────│───┼───────────────────┼─────────│──────────────────────
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
3+: ───────│────────────│───────────────────────│────┼─────────────────Z───────┼─────────────────│─────────────────────────│────────────────────────────────│──────────────────────│───Z^-0.724───│───DD──────────Z^0.724────│───┼─────────Z^(-15/16)───┼─────────│───DD────────Z^(-1/16)───│───┼─────────Z^(1/16)───┼─────────│───DD────────Z^(15/16)───│───┼─────────Z^-0.507───┼─────────│───DD──────────Z^-0.493───│───┼─────────Z^0.65─────┼─────────│───DD────────Z^0.35───│───┼─────────Z^-0.35───┼─────────│───DD────────Z^0.75───
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
3-: ───X───│────────────│───────────────────────│────DD^0.25───────────S^-1────DD^0.25───────────│─────────────────────────│────────────────────────────────│──────────────────────│──────────────│───DD^-0.762──────────────│───DD^0.25───S^-1─────────DD^0.25───│───DD^-0.5───────────────│───DD^0.25───S^-1───────DD^0.25───│───DD^-0.5───────────────│───DD^0.25───S^-1───────DD^0.25───│───DD^-0.599──────────────│───DD^0.25───S^-1───────DD^0.25───│───DD^-0.5────────────│───DD^0.25───S^-1──────DD^0.25───│───DD^-0.5────────────
└──────────────┘ └──────────────┘
As earlier, we can also double check the equivalence of these compiled circuits by comparing their respective unitaries:
[15]:
for uncompiled_circuit, compiled_circuit in zip([bell_circuit, rand_circuit], compiled_circuits):
mat = compute_unitary(compiled_circuit)
cirq.testing.assert_allclose_up_to_global_phase(
cirq.unitary(uncompiled_circuit), mat, atol=1e-8
)
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 create_job() to instruct Superstaq to ideally sample the circuit. Let us generate a random circuit again to demonstrate:
[16]:
# Example random circuit
n, depth, op_density = (2, 3, 0.8)
qubits = cirq.LineQubit.range(n)
circuit = cirq.testing.random_circuit(qubits, depth, op_density, gate_domain=gate_domain)
circuit += cirq.measure(*qubits)
print(circuit)
0: ───ZZSwap(0.392π)───X───iSwap───M───
│ │ │ │
1: ───ZZSwap(0.392π)───@───iSwap───M───
[ ]:
# Specify EeroQ target to service
job = service.create_job(
circuit, repetitions=1000, target="eeroq_wonderlake_qpu", method="dry-run"
) # Specify "dry-run" as the method to run an ideal Superstaq execution
# Get the counts from the measurement
print(job.counts(0))
{'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.
[18]:
noisy_job = service.create_job(
circuit, target="eeroq_wonderlake_qpu", repetitions=1000, method="noise-sim", error_rate=0.01
)
[19]:
# Get the counts from the measurement
noisy_job.counts(0)
[19]:
{'00': 914, '20': 8, '02': 18, '22': 3, '01': 17, '21': 15}
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.