Getting Started - Defining your Experimental Setup¶
This guide shows you how to create the object describing the experiment setup, which are needed for running the experiment workflows defined in the Applications Library.
Reuse an existing DeviceSetup¶
If you already have a DeviceSetup
for your experimental setup, that's great! You can use that. The Applications Library works for any setup, as long as the qubit UIDs match the names of the logical signal groups defined in the DeviceSetup
. See Define qubits below.
Create a DeviceSetup¶
You don't have DeviceSetup
, start by creating one for your experimental setup.
You have several options for defining your DeviceSetup
:
- Define your descriptor and setup by hand following the Device Setup and Descriptor tutorial in the LabOne Q Core documentation.
- Use the helper function
generate_descriptor
(comes with thelaboneq
package) - Use the helper function
generate_device_setup
(comes with thelaboneq
package) - Use the helper function
tunable_transmon_setup
(comes with thelaboneq-applications
package)
These three helper functions are targeted for an experimental setup containing qubits. The functions generate_descriptor
and generate_device_setup
can be uses to generate a DeviceSetup
with logical signal groups for n
number of qubits and the instrument serial numbers that you have in your rack. tunable_transmon_setup
is a convenient function to get a dummy, non-configurable DeviceSetup
for n
tunable-transmon qubits meant to be used in emulation mode for quick prototyping.
Below, we show how to use these helper functions to create a DeviceSetup
containing an SHFQC+ instrument, an HDAWG instrument, and a PQSC instrument, which are used to operate 3 qubits, labelled q0, q1, q2
.
generate_descriptor¶
The advantage of generate_descriptor
is that it, by setting get_zsync=True
, it automatically detects the zsync ports of the PQCS that to which the other instruments in this descriptor are connected.
However, you can only specify the instrument serial numbers (DEVxxxx), and the instruments are created with default options.
# Setting get_zsync=True automatically detects the zsync ports of the PQCS that
# are used by the other instruments in this descriptor.
# Here, we are not connected to instruments, so we set this flag to False.
from laboneq.contrib.example_helpers.generate_descriptor import generate_descriptor
from laboneq.simple import DeviceSetup
descriptor = generate_descriptor(
pqsc=["DEV10001"],
hdawg_8=["DEV8001"],
shfqc_6=["DEV12001"],
number_data_qubits=3,
number_flux_lines=3,
include_cr_lines=False,
multiplex=True,
number_multiplex=3,
get_zsync=False,
ip_address="localhost",
)
setup = DeviceSetup.from_descriptor(descriptor, "localhost")
The setup
contains a logical signal group for each qubit labelled q0, q1, q2
, and each of these qubit signal-line group contains the following signal lines: drive_line
, drive_line_ef
, measure_line
, acquire_line
, flux_line
, as shown below.
qubit_signals = {
quid: list(lsg.logical_signals) for quid, lsg in setup.logical_signal_groups.items()
}
qubit_signals
{'q0': ['flux_line', 'drive_line', 'drive_line_ef', 'measure_line', 'acquire_line'], 'q1': ['flux_line', 'drive_line', 'drive_line_ef', 'measure_line', 'acquire_line'], 'q2': ['flux_line', 'drive_line', 'drive_line_ef', 'measure_line', 'acquire_line']}
generate_device_setup¶
The advantage of generate_device_setup
is that you have full control over how to configure your instruments in the DeviceSetup
, by specifying any additional properties or options of the instruments.
For an overview of the available device options and how to set them, see the instrument options overview table.
Here, we do not configure any options.
from laboneq.contrib.example_helpers.generate_device_setup import (
generate_device_setup,
)
# specify the number of qubits you want to use
number_of_qubits = 3
# generate the device setup using a helper function
setup = generate_device_setup(
number_qubits=number_of_qubits,
pqsc=[{"serial": "DEV10001"}],
hdawg=[
{
"serial": "DEV8001",
"zsync": 0,
"number_of_channels": 8,
"options": None,
}
],
shfqc=[
{
"serial": "DEV12001",
"zsync": 1,
"number_of_channels": 6,
"readout_multiplex": 3,
"options": None,
}
],
include_flux_lines=True,
multiplex_drive_lines=True, # adds drive_ef
server_host="localhost",
setup_name="my_setup",
)
The setup
contains a logical signal group for each qubit labelled q0, q1, q2
, and each of these qubit signal-line group contains the following signal lines: drive_line
, drive_line_ef
, measure_line
, acquire_line
, flux_line
, as shown below.
qubit_signals = {
quid: list(lsg.logical_signals) for quid, lsg in setup.logical_signal_groups.items()
}
qubit_signals
{'q0': ['measure_line', 'acquire_line', 'drive_line', 'drive_line_ef', 'flux_line'], 'q1': ['measure_line', 'acquire_line', 'drive_line', 'drive_line_ef', 'flux_line'], 'q2': ['measure_line', 'acquire_line', 'drive_line', 'drive_line_ef', 'flux_line']}
tunable_transmon_setup¶
When you want to quickly prototype new experiments in emulation mode and don't care about the exact details of the DeviceSetup
, you can use the helper function tunable_transmon_setup
.
This function creates a dummy DeviceSetup
containing an SHFQC+ instrument, an HDAWG instrument, and a PQSC instrument, which are used to operate n_qubits
tunable transmon qubits, labelled q0, q1, q2, ...
Let's use this function for n_qubits=3
.
from laboneq_applications.qpu_types.tunable_transmon.demo_qpus import (
tunable_transmon_setup,
)
setup = tunable_transmon_setup(n_qubits=3)
The setup
contains a logical signal group for each qubit labelled with the qubit UID, and each of these qubit signal-line group contains the following signal lines: drive
, drive_ef
, measure
, acquire
, flux
, as shown below.
qubit_signals = {
quid: list(lsg.logical_signals) for quid, lsg in setup.logical_signal_groups.items()
}
qubit_signals
{'q0': ['drive', 'drive_ef', 'measure', 'acquire', 'flux'], 'q1': ['drive', 'drive_ef', 'measure', 'acquire', 'flux'], 'q2': ['drive', 'drive_ef', 'measure', 'acquire', 'flux']}
Inspect the qubit-instrument connectivity¶
Use either of the three DeviceSetups
defined above to inspect the connectivity between the instruments and the lines of the qubits:
def get_physical_signal_name(quid, signal_name):
logical_signal = setup.logical_signal_groups[quid].logical_signals[signal_name]
return logical_signal.physical_channel.uid
qubit_signals = {
quid: list(lsg.logical_signals) for quid, lsg in setup.logical_signal_groups.items()
}
connections = {
quid: {sig_name: get_physical_signal_name(quid, sig_name) for sig_name in signals}
for quid, signals in qubit_signals.items()
}
from pprint import pprint
pprint(connections) # noqa: T203
{'q0': {'acquire': 'device_shfqc/qachannels_0_input', 'drive': 'device_shfqc/sgchannels_0_output', 'drive_ef': 'device_shfqc/sgchannels_0_output', 'flux': 'device_hdawg/sigouts_0', 'measure': 'device_shfqc/qachannels_0_output'}, 'q1': {'acquire': 'device_shfqc/qachannels_0_input', 'drive': 'device_shfqc/sgchannels_1_output', 'drive_ef': 'device_shfqc/sgchannels_1_output', 'flux': 'device_hdawg/sigouts_1', 'measure': 'device_shfqc/qachannels_0_output'}, 'q2': {'acquire': 'device_shfqc/qachannels_0_input', 'drive': 'device_shfqc/sgchannels_2_output', 'drive_ef': 'device_shfqc/sgchannels_2_output', 'flux': 'device_hdawg/sigouts_2', 'measure': 'device_shfqc/qachannels_0_output'}}
We see that the three qubits are read out in parallel on the same quantum analyzer (QA) channel of the SHFQC instrument, and that their drive lines are controlled from individual signal generation (SG) channels of the SHFQC instrument. Finally, the flux lines of the qubits are controlled by individual HDAWG outputs.
Define qubits¶
We will show how to create qubit instances from the logical signal groups of either of the three DeviceSetups
defined above. Here, we use the TunableTransmonQubit
class with corresponding TunableTransmonQubitParameters
, but the procedure is the same for any other child class of LabOne Q QuantumElements
class.
from laboneq_applications.qpu_types.tunable_transmon import (
TunableTransmonQubit,
TunableTransmonQubitParameters,
)
qubits = []
for i in range(3):
q = TunableTransmonQubit.from_logical_signal_group(
f"q{i}",
setup.logical_signal_groups[f"q{i}"],
parameters=TunableTransmonQubitParameters(),
)
qubits.append(q)
By using TunableTransmonQubit.from_logical_signal_group
, the qubits are instantiated with the logical signals given by the signals contained in the logical signal group in the DeviceSetup
that has the same name as the qubit UID. So for example, if the logical signal groups in your DeviceSetup
contain the logical signals "drive", "measure", "acquire", then the qubits will also have these signals.
Check that the qubits we've created have the signals you expect from the DeviceSetup
:
for q in qubits:
print(q.uid)
for sig, lsg in q.signals.items():
print(f"\t'{sig}:\t'{lsg}'")
q0 'drive: '/logical_signal_groups/q0/drive' 'drive_ef: '/logical_signal_groups/q0/drive_ef' 'measure: '/logical_signal_groups/q0/measure' 'acquire: '/logical_signal_groups/q0/acquire' 'flux: '/logical_signal_groups/q0/flux' q1 'drive: '/logical_signal_groups/q1/drive' 'drive_ef: '/logical_signal_groups/q1/drive_ef' 'measure: '/logical_signal_groups/q1/measure' 'acquire: '/logical_signal_groups/q1/acquire' 'flux: '/logical_signal_groups/q1/flux' q2 'drive: '/logical_signal_groups/q2/drive' 'drive_ef: '/logical_signal_groups/q2/drive_ef' 'measure: '/logical_signal_groups/q2/measure' 'acquire: '/logical_signal_groups/q2/acquire' 'flux: '/logical_signal_groups/q2/flux'
The qubits are instantiated with identical default values of the TunableTransmonQubitParameters
class. Let's see what the qubit parameters are:
qubits[0].parameters
TunableTransmonQubitParameters( │ resonance_frequency_ge=None, │ resonance_frequency_ef=None, │ drive_lo_frequency=None, │ readout_resonator_frequency=None, │ readout_lo_frequency=None, │ readout_integration_delay=2e-08, │ drive_range=10, │ readout_range_out=5, │ readout_range_in=10, │ flux_offset_voltage=0, │ user_defined={}, │ ge_T1=0, │ ge_T2=0, │ ge_T2_star=0, │ ef_T1=0, │ ef_T2=0, │ ef_T2_star=0, │ ge_drive_amplitude_pi=0.2, │ ge_drive_amplitude_pi2=0.1, │ ge_drive_length=5e-08, │ ge_drive_pulse={ │ │ 'function': 'drag', │ │ 'beta': 0, │ │ 'sigma': 0.25 │ }, │ ef_drive_amplitude_pi=0.2, │ ef_drive_amplitude_pi2=0.1, │ ef_drive_length=5e-08, │ ef_drive_pulse={ │ │ 'function': 'drag', │ │ 'beta': 0, │ │ 'sigma': 0.25 │ }, │ readout_amplitude=1.0, │ readout_length=2e-06, │ readout_pulse={ │ │ 'function': 'const' │ }, │ readout_integration_length=2e-06, │ readout_integration_kernels_type='default', │ readout_integration_kernels=None, │ readout_integration_discrimination_thresholds=None, │ reset_delay_length=1e-06, │ spectroscopy_length=5e-06, │ spectroscopy_amplitude=1, │ spectroscopy_pulse={ │ │ 'function': 'const', │ │ 'can_compress': True │ }, │ dc_slot=0, │ dc_voltage_parking=0.0 )
Check out the tutorial on qubits and quantum operations to learn more about these parameters and how they are used in the Applications Library together with quantum operations.
Adjust the values of the qubit parameters to the ones for your quantum device. You can change the value of any of the parameters as shown below for the drive_lo_frequency
parameter:
qubits[0].parameters.drive_lo_frequency = 6e9
If you already have the correct qubit parameters stored in an instance of TunableTransmonQubitParameters
(for example, loaded from a file), you can directly pass them to the parameters
argument of TunableTransmonQubit.from_logical_signal_group
, and the qubits will be created with those parameters.
Define quantum operations¶
Next, we need to define the class of quantum operations implementing gates and operations on the qubits defined above. Here, we will use an instance of TunableTransmonOperations
for the tunable transmons defined above.
from laboneq_applications.qpu_types.tunable_transmon import TunableTransmonOperations
quantum_operations = TunableTransmonOperations()
quantum_operations.keys()
['acquire', 'active_reset', 'barrier', 'calibration_traces', 'delay', 'measure', 'passive_reset', 'prepare_state', 'qubit_spectroscopy_drive', 'ramsey', 'rx', 'ry', 'rz', 'set_frequency', 'set_readout_amplitude', 'x180', 'x180_ef_reset', 'x90', 'y180', 'y90', 'z180', 'z90']
To learn more about quantum operations and how they are used to create quantum experiments from the qubit parameters, see the tutorial on qubits and quantum operations.
Define the QPU¶
Finally, we define the quantum processor (QPU) containing the qubits and the corresponding quantum operations.
The qpu
contains the source of ground truth for an experiment and the best state of knowledge of the quantum system that is being operated. This means that the parameters of the qubits and any other parameters of the QPU define the configuration used by all the experiments in the Applications Library.
from laboneq.dsl.quantum import QPU
qpu = QPU(qubits=qubits, quantum_operations=quantum_operations)
Loading From a File¶
The qubits and QPU can also be loaded back from json
files saved by an experiment in the Applications Library. You just need the path to the file:
from laboneq import serializers
serializers.load(path_to_file)
Optional: define a QuantumPlatform¶
Optionally, you can collect the setup
and the qpu
in an instance of QuantumPlatform
.
from laboneq.dsl.quantum import QuantumPlatform
qt_platform = QuantumPlatform(setup=setup, qpu=qpu)
Demo QuantumPlatform¶
The tunable_transmon_setup and a QPU
for n
tunable transmon qubits defined above can also be more conveniently obtained by instantiating a demo quantum platform provided by the Application Library. This demo platform is useful for quick prototyping in emulation mode.
from laboneq_applications.qpu_types.tunable_transmon.demo_qpus import demo_platform
demo_qt_platform = demo_platform(n_qubits=3)
log_sig_groups = demo_qt_platform.setup.logical_signal_groups
qubit_signals = {
quid: list(lsg.logical_signals) for quid, lsg in log_sig_groups.items()
}
qubit_signals
{'q0': ['drive', 'drive_ef', 'measure', 'acquire', 'flux'], 'q1': ['drive', 'drive_ef', 'measure', 'acquire', 'flux'], 'q2': ['drive', 'drive_ef', 'measure', 'acquire', 'flux']}
demo_qt_platform.qpu.qubits[0].parameters
TunableTransmonQubitParameters( │ resonance_frequency_ge=6500000000.0, │ resonance_frequency_ef=6300000000.0, │ drive_lo_frequency=6400000000, │ readout_resonator_frequency=7100000000.0, │ readout_lo_frequency=7000000000.0, │ readout_integration_delay=2e-08, │ drive_range=10, │ readout_range_out=5, │ readout_range_in=10, │ flux_offset_voltage=0, │ user_defined={}, │ ge_T1=0, │ ge_T2=0, │ ge_T2_star=0, │ ef_T1=0, │ ef_T2=0, │ ef_T2_star=0, │ ge_drive_amplitude_pi=0.8, │ ge_drive_amplitude_pi2=0.4, │ ge_drive_length=5.1e-08, │ ge_drive_pulse={ │ │ 'function': 'drag', │ │ 'beta': 0.01, │ │ 'sigma': 0.21 │ }, │ ef_drive_amplitude_pi=0.7, │ ef_drive_amplitude_pi2=0.3, │ ef_drive_length=5.2e-08, │ ef_drive_pulse={ │ │ 'function': 'drag', │ │ 'beta': 0.01, │ │ 'sigma': 0.21 │ }, │ readout_amplitude=1.0, │ readout_length=2e-06, │ readout_pulse={ │ │ 'function': 'const' │ }, │ readout_integration_length=2e-06, │ readout_integration_kernels_type='default', │ readout_integration_kernels=None, │ readout_integration_discrimination_thresholds=None, │ reset_delay_length=1e-06, │ spectroscopy_length=5e-06, │ spectroscopy_amplitude=1, │ spectroscopy_pulse={ │ │ 'function': 'const', │ │ 'can_compress': True │ }, │ dc_slot=0, │ dc_voltage_parking=0.0 )
demo_qt_platform.qpu.quantum_operations.keys()
['acquire', 'active_reset', 'barrier', 'calibration_traces', 'delay', 'measure', 'passive_reset', 'prepare_state', 'qubit_spectroscopy_drive', 'ramsey', 'rx', 'ry', 'rz', 'set_frequency', 'set_readout_amplitude', 'x180', 'x180_ef_reset', 'x90', 'y180', 'y90', 'z180', 'z90']
Connect to Session¶
Now let's connect to a LabOne Q Session
. Here, we connect in emulation mode. When running on real hardware, connect using do_emulation=False
.
from laboneq.simple import Session
session = Session(setup)
session.connect(do_emulation=True)
[2024.12.19 17:11:36.650] INFO Logging initialized from [Default inline config in laboneq.laboneq_logging] logdir is /builds/qccs/laboneq-applications/docs/sources/tutorials/sources/laboneq_output/log
[2024.12.19 17:11:36.652] INFO VERSION: laboneq 2.43.0
[2024.12.19 17:11:36.653] INFO Connecting to data server at localhost:8004
[2024.12.19 17:11:36.655] INFO Connected to Zurich Instruments LabOne Data Server version 24.10 at localhost:8004
[2024.12.19 17:11:36.658] INFO Configuring the device setup
[2024.12.19 17:11:36.776] INFO The device setup is configured
<laboneq.dsl.session.ConnectionState at 0x7410c5100da0>
Great! You have created everything you need to get started with the measurements. Now, on to experiments!