Two Qubit Randomized Benchmarking in LabOne Q with Qiskit¶
In this notebook, you'll use the Qiskit Experiment Library to generate a two qubit randomized benchmarking experiment. You'll then export the generated experiment to OpenQASM, import your OpenQASM experiment into LabOne Q, compile, and simulate the output signals.
Python Imports¶
# LabOne Q:
from laboneq.simple import *
# plotting functionality
from laboneq.contrib.example_helpers.plotting.plot_helpers import *
from laboneq.pulse_sheet_viewer.pulse_sheet_viewer import show_pulse_sheet
# device setup and descriptor
from laboneq._utils import id_generator
from laboneq.dsl.utils import calibrate_devices
from laboneq.contrib.example_helpers.generate_descriptor import generate_descriptor
# open qasm importer
from laboneq.openqasm3.gate_store import GateStore
# qiskit
from qiskit import qasm3, transpile
from qiskit_experiments.library import randomized_benchmarking
# additional imports
from math import pi
Set up Qiskit-generated RB¶
You'll start by creating a Standard RB experiment from the Qiskit Experiment Library here. For two qubits for a few different clifford lengths.
Note that most circuits that can be generated in Qiskit and converted to OpenQASM could be adapted to be run in a similar way in LabOne Q!
# Use Qiskit Experiment Library to Generate RB
qiskit_experiment = randomized_benchmarking.StandardRB(
physical_qubits=[0, 1], lengths=[4, 8, 12]
).circuits()
qiskit_experiment[2].draw()
You can then use the Qiskit transpile
function to obtain your favorite set of basis gates.
# Choose basis gates
transpiled_circuit = transpile(
qiskit_experiment, basis_gates=["id", "sx", "x", "rz", "cx"]
)
transpiled_circuit[0].draw()
program_list = []
for circuit in transpiled_circuit:
program_list.append(qasm3.dumps(circuit))
print(program_list[0])
LabOne Q Experiment¶
Setup, Calibration & Configuration¶
You'll define your device setup and calibration below, as well as a function to generate a LabOne Q experiment using the built-in OpenQasm3Importer
.
generate_descriptor(
pqsc=["DEV10056"],
shfqc_6=["DEV12108"],
hdawg_8=["DEV8138"],
number_data_qubits=3,
number_flux_lines=3,
multiplex=True,
number_multiplex=3,
save=True,
filename="SeaCucumber_SHF_HD_PQSC",
include_cr_lines=True,
)
device_setup = DeviceSetup.from_yaml(
filepath="./Descriptors/SeaCucumber_SHF_HD_PQSC.yaml",
server_host="ip_address",
server_port="8004",
setup_name="my_setup_name",
)
q0 = Transmon.from_logical_signal_group(
"q0",
lsg=device_setup.logical_signal_groups["q0"],
parameters=TransmonParameters(
resonance_frequency_ge=6.15e9,
resonance_frequency_ef=5.85e9,
drive_lo_frequency=6.1e9,
readout_resonator_frequency=6.4e9,
readout_lo_frequency=6.3e9,
user_defined={
"cross_resonance_frequency": 200e6,
"amplitude_pi": 0.5,
"pulse_length": 50e-9,
"readout_len": 5e-7,
"readout_amp": 0.2,
"reset_length": 200e-9,
},
),
)
q1 = Transmon.from_logical_signal_group(
"q1",
lsg=device_setup.logical_signal_groups["q1"],
parameters=TransmonParameters(
resonance_frequency_ge=6.25e9,
resonance_frequency_ef=5.95e9,
drive_lo_frequency=6.1e9,
readout_resonator_frequency=6.4e9,
readout_lo_frequency=6.3e9,
user_defined={
"cross_resonance_frequency": -200e6,
"amplitude_pi": 0.6,
"pulse_length": 50e-9,
"readout_len": 5e-7,
"readout_amp": 0.2,
"reset_length": 200e-9,
},
),
)
qubits = [q0, q1]
for qubit in qubits:
device_setup.set_calibration(qubit.calibration())
# set calibration of cross resonance signal lines - not currently included in TransmonQubit calibration method
device_setup.logical_signal_groups[qubit.uid].logical_signals[
"drive_line_cr"
].calibration = SignalCalibration(
oscillator=Oscillator(
frequency=qubit.parameters.user_defined["cross_resonance_frequency"],
modulation_type=ModulationType.HARDWARE,
)
)
Transpilation Support (Gate Definitions)¶
You'll now define functions to generate pulses and gates from the OpenQASM program text.
def drive_pulse(qubit: Qubit, label, length=50e-9, amplitude=0.6):
"""Return a drive pulse for the given qubit.
In practice different drive pulses would be specified for each qubit and operation.
"""
return pulse_library.drag(
uid=f"{qubit.uid}_{label}",
length=qubit.parameters.user_defined["pulse_length"],
amplitude=qubit.parameters.user_defined["amplitude_pi"],
)
def drive_pulse_root(qubit: Qubit, label, length=50e-9, amplitude=0.6):
"""Return a root drive pulse for the given qubit.
In practice different drive pulses would be specified for each qubit and operation.
"""
return pulse_library.drag(
uid=f"{qubit.uid}_{label}",
length=qubit.parameters.user_defined["pulse_length"],
amplitude=(qubit.parameters.user_defined["amplitude_pi"]) / 2,
)
def rz(qubit: Qubit):
"""Return a parameterized Rz gate for the specified qubit.
The gate is a function that takes the angle to rotate and
returns a LabOne Q section that performs the rotation.
"""
def rz_gate(angle: float):
"""Rz(theta).
Theta is in radians - implements a virtual z-gate
"""
gate = Section(uid=id_generator(f"p_{qubit.uid}_rz_{int(180 * angle / pi)}"))
gate.play(
signal=qubit.signals["drive"],
pulse=None,
increment_oscillator_phase=angle,
)
return gate
return rz_gate
def measurement(qubit: Qubit):
"""Return a measurement operation of the specified qubit.
The operation is a function that takes the measurement handle (a string)
and returns a LabOne Q section that performs the measurement.
"""
def measurement_gate(handle: str):
"""Perform a measurement.
Handle is the name of where to store the measurement result. E.g. "meas[0]".
"""
measure_pulse = pulse_library.gaussian_square(
uid=f"{qubit.uid}_readout_pulse",
length=qubit.parameters.user_defined["readout_len"],
amplitude=qubit.parameters.user_defined["readout_amp"],
)
integration_kernel = pulse_library.const(
uid=f"{qubit.uid}_integration_kernel",
length=qubit.parameters.user_defined["readout_len"],
)
gate = Section(uid=id_generator(f"meas_{qubit.uid}_{handle}"))
gate.reserve(signal=qubit.signals["drive"])
gate.play(signal=qubit.signals["measure"], pulse=measure_pulse)
gate.acquire(
signal=qubit.signals["acquire"],
handle=handle,
kernel=integration_kernel,
)
return gate
return measurement_gate
def cx(control: Qubit, target: Qubit):
"""Return a controlled X gate for the specified control and target qubits.
The CX gate function takes no arguments and returns a LabOne Q section that performs
the controllex X gate.
"""
def cx_gate():
cx_id = f"cx_{control.uid}_{target.uid}"
gate = Section(uid=id_generator(cx_id))
# define X pulses for target and control
x180_pulse_control = drive_pulse(control, label="x180")
x180_pulse_target = drive_pulse(target, label="x180")
# define cancellation pulses for target and control
cancellation_control_n = pulse_library.gaussian_square(uid="CR-")
cancellation_control_p = pulse_library.gaussian_square(uid="CR+")
cancellation_target_p = pulse_library.gaussian_square(uid="q1+")
cancellation_target_n = pulse_library.gaussian_square(uid="q1-")
# play X pulses on both target and control
x180_both = Section(uid=id_generator(f"{cx_id}_x_both"))
x180_both.play(signal=control.signals["drive"], pulse=x180_pulse_control)
x180_both.play(signal=target.signals["drive"], pulse=x180_pulse_target)
gate.add(x180_both)
# First cross-resonance component
cancellation_p = Section(
uid=id_generator(f"{cx_id}_canc_p"), play_after=x180_both.uid
)
cancellation_p.play(signal=target.signals["drive"], pulse=cancellation_target_p)
cancellation_p.play(
signal=control.signals["flux"], pulse=cancellation_control_n
)
gate.add(cancellation_p)
# play X pulse on control
x180_control = Section(
uid=id_generator(f"{cx_id}_x_q0"), play_after=cancellation_p.uid
)
x180_control.play(signal=control.signals["drive"], pulse=x180_pulse_control)
gate.add(x180_control)
# Second cross-resonance component
cancellation_n = Section(
uid=id_generator(f"cx_{cx_id}_canc_n"), play_after=x180_control.uid
)
cancellation_n.play(signal=target.signals["drive"], pulse=cancellation_target_n)
cancellation_n.play(
signal=control.signals["flux"], pulse=cancellation_control_p
)
gate.add(cancellation_n)
return gate
return cx_gate
Two Qubit RB¶
You're almost ready to run your experiment!
Connect to Session¶
You'll need to start a LabOne Q session. Here, you'll run the session in emulation mode. If you've modified the descriptor to run on your own devices above, you could connect to them here instead.
my_session = Session(device_setup=device_setup)
my_session.connect(do_emulation=True)
Define Gates, Load QASM 3 Program, and Go!¶
Now, you'll map your OpenQASM gates to signals produced on the instruments using register_gate
and register_gate_section
functions.
Once you've done that, you can compile your experiment and plot the output using the LabOne Q simulator.
gate_store = GateStore()
# Note: the below may need to be updated to match the
# names of your qubits from your QASM circuit!
qubit_map = {"q[0]": q0, "q[1]": q1}
# Single qubit gates:
for oq3_qubit, l1q_qubit in qubit_map.items():
gate_store.register_gate(
"sx",
oq3_qubit,
drive_pulse_root(l1q_qubit, label="sx"),
signal=l1q_qubit.signals["drive"],
)
gate_store.register_gate(
"x",
oq3_qubit,
drive_pulse(l1q_qubit, label="x"),
signal=l1q_qubit.signals["drive"],
)
gate_store.register_gate_section("rz", (oq3_qubit,), rz(l1q_qubit))
gate_store.register_gate_section("measure", (oq3_qubit,), measurement(l1q_qubit))
# Two qubit gates:
gate_store.register_gate_section("cx", ("q[0]", "q[1]"), cx(q0, q1))
gate_store.register_gate_section("cx", ("q[1]", "q[0]"), cx(q1, q0))
exp = exp_from_qasm(program_list[0], qubits=qubit_map, gate_store=gate_store)
compiled_exp = my_session.compile(exp)
plot_simulation(compiled_exp, length=100e-6)
my_results = my_session.run(compiled_exp)
Draw the circuit from above¶
You can also draw the circuit corresponding to the simulated signals you just produced!
transpiled_circuit[0].draw()
Compile and draw more circuits in the list¶
You can do this for any circuit you've generated in the list.
exp_1 = exp_from_qasm(program_list[1], qubits=qubit_map, gate_store=gate_store)
compiled_exp_1 = my_session.compile(exp_1)
plot_simulation(compiled_exp_1, length=100e-6)
transpiled_circuit[1].draw()
exp_2 = exp_from_qasm(program_list[2], qubits=qubit_map, gate_store=gate_store)
compiled_exp_2 = my_session.compile(exp_2)
plot_simulation(compiled_exp_2, length=100e-6)
plot_simulation(compiled_exp_2, length=1000e-6)
transpiled_circuit[2].draw()