Writing an Experiment Workflow¶
All the quantum experiments in the Applications Library are implemented using Workflows.
The experiment workflows have the following tasks and structure:
The details of this structure and how to use the ready-made experiments in the Applications Library are given in the tutorial on experiment Workflows. Here, we show you how to write a new quantum experiment using Workflows
. We have chosen a Rabi measurement, where we sweep the pulse length instead of the pulse amplitude.
Note: in this tutorial, we use 'experiment' to refer to the generic concept of a quantum computing experiment, and the capitalised version of the word, 'Experiment' or Experiment
, to refer to the LabOne Q Experiment class which describe the pulse sequence of the quantum experiment being implemented.
Let's get started!
Imports¶
We first import everything we need:
laboneq.simple
which, among other things, imports the two name spacesworkflow
anddsl
that will be important in this tutorial- the tasks
compile_experiment
,run_experiment
(see the tutorial on using Tasks in LabOne Q) - the demo
QuantumPlatform
(see the Getting Started tutorial) - the
validation
module, which we will use later in this tutorial
from __future__ import annotations # needed for type setting in python 3.9
import numpy as np
from laboneq.simple import *
from laboneq_applications.core import validation
from laboneq_applications.qpu_types.tunable_transmon.demo_qpus import demo_platform
QPU and Device Setup¶
We generate a pre-configured QuantumPlatform
containing three tunable-transmon qubits with pre-defined parameters, and a Device_Setup
consisting of a SHFQC+, HDAWG, and PQSC. If you already have your own DeviceSetup
and qubits configured, you'll instead initialize the session using your setup as shown in the Getting Started tutorial. This tutorial also provides more details about the pre-configured QuantumPlatform
.
# Create a demonstration QuantumPlatform for a six-tunable-transmon QPU:
qt_platform = demo_platform(n_qubits=3)
# The platform contains a setup, which is an ordinary LabOne Q DeviceSetup:
setup = qt_platform.setup
# And a tunable-transmon QPU:
qpu = qt_platform.qpu
# Inside the QPU, we have qubits, which is a list of six LabOne Q Application
# Library TunableTransmonQubit qubits:
qubits = qpu.qubits
Then, you'll connect to the Session
. Here we connect to an emulated one:
session = Session(setup)
session.connect(do_emulation=True)
[2024.12.19 17:11:53.113] 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:53.115] INFO VERSION: laboneq 2.43.0
[2024.12.19 17:11:53.116] INFO Connecting to data server at localhost:8004
[2024.12.19 17:11:53.118] INFO Connected to Zurich Instruments LabOne Data Server version 24.10 at localhost:8004
[2024.12.19 17:11:53.120] INFO Configuring the device setup
[2024.12.19 17:11:53.156] INFO The device setup is configured
<laboneq.dsl.session.ConnectionState at 0x774c393d5b20>
Create a FolderStore for Saving Data¶
The experiment Workflows
can automatically save the inputs and outputs of all their tasks to the folder path we specify when instantiating the FolderStore. Here, we choose the current working directory.
# import FolderStore from the `workflow` namespace of LabOne Q, which was imported
# from `laboneq.simple`
from pathlib import Path
folder_store = workflow.logbook.FolderStore(Path.cwd())
We disable saving in this tutorial. To enable it, simply run folder_store.activate()
.
folder_store.deactivate()
Write the Experiment Pulse Sequence¶
Usually, when you start implementing a new experiment, the first thing you have in mind is the pulse sequence or the circuit diagram describing the experiment logic.
So let's begin by writing the create_experiment
task of the length-Rabi experiment. You'll notice that every experiment workflow in the Applications Library has this task, which is responsible for creating an instance of the LabOne Q Experiment object describing the pulse sequence of the experiment.
Single-Qubit Experiment¶
We start simple: length-Rabi on a single tunable transmon qubit, written using quantum operations:
@workflow.task
@dsl.qubit_experiment
def create_experiment(
qpu,
qubit,
pulse_lengths,
count=10,
):
"""Pulse duration Rabi experiment."""
qop = qpu.quantum_operations
with dsl.acquire_loop_rt(
count=count,
):
with dsl.sweep(
name=f"pulse_lengths_{qubit.uid}",
parameter=SweepParameter(f"pulse_lengths_{qubit.uid}", pulse_lengths),
) as length:
qop.x180(qubit, length=length, transition="ef")
qop.measure(qubit, handle=dsl.handles.result_handle(qubit))
qop.passive_reset(qubit)
Quite a few constructions have been introduced here, so let's take a closer look:
the
dsl
namespace contains builtin functions for creating theExperiment
pulse sequence:qubit_experiment
,acquire_loop_rt
,sweep
.with dsl.sweep
: This is an ordinary LabOne Q sweep.qop.passive_reset
: This operation resets the qubit to the ground state by delaying for an amount of time configured in the calibration.
The decorator @dsl.qubit_experiment turns create_experiment
into a special function. When called, it returns the Experiment
instance for the pulse sequence created inside it for the qubits passed in the input parameters. Let's call our new task create_experiment
and see this in action!
exp = create_experiment(qpu, qubits[0], np.linspace(0, 100e-9, 5))
type(exp)
laboneq.dsl.experiment.experiment.Experiment
Declarative-Style DSL¶
The logic of decorator @dsl.qubit_experiment
works using context managers. It automatically creates an Experiment
context, to which the rest of the dsl
constructs are added.
However, we can also write the same task using the declarative-style DSL, without contexts. We disable the context-style DSL by passing context=False
to the @dsl.qubit_experiment
decorator. In addition, the first argument of our function is now the experiment itself.
@workflow.task
@dsl.qubit_experiment(context=False)
def create_experiment_declarative(
exp,
qpu,
qubit,
pulse_lengths,
count=10,
):
"""Pulse duration Rabi experiment using declarative-style DSL."""
qop = qpu.quantum_operations
# Create the acquire loop
acq_rt = AcquireLoopRt(count=count)
exp.add(acq_rt)
# Create the sweep
len_swp_par = SweepParameter(f"pulse_lengths_{qubit.uid}", pulse_lengths)
swp = Sweep(f"pulse_lengths_{qubit.uid}", parameters=len_swp_par)
# Add the sections created by the quantum operations to the sweep
swp.add(qop.x180(qubit, length=len_swp_par))
swp.add(qop.measure(qubit, handle=dsl.handles.result_handle(qubit)))
swp.add(qop.passive_reset(qubit))
# Add the sweep to the acquire loop
acq_rt.add(swp)
exp_declarative = create_experiment_declarative(qpu, qubits[0], np.linspace(0, 100e-9, 5))
type(exp_declarative)
laboneq.dsl.experiment.experiment.Experiment
Parallel Multi-Qubit Experiment¶
Let's now change our create_experiment
task to implement the length-Rabi on several qubits in parallel. Here, we use the context-based DSL, but you can write the same using the declarative-style.
@workflow.task
@dsl.qubit_experiment
def create_experiment_multi_qubit(
qpu,
qubits,
pulse_lengths,
count=10,
):
"""Pulse duration Rabi experiment on multiple qubits in parallel."""
# Validation: ensure qubits and amplitudes have the same length
qubits, pulse_lengths = validation.validate_and_convert_qubits_sweeps(
qubits, pulse_lengths
)
lengths_sweep_pars = [
SweepParameter(f"pulse_lengths_{q.uid}", q_lengths)
for q, q_lengths in zip(qubits, pulse_lengths)
]
# We will fix the length of the measure section to the longest section among
# the qubits to allow the qubits to have different readout and/or
# integration lengths.
max_measure_section_length = qpu.measure_section_length(qubits)
qop = qpu.quantum_operations
with dsl.acquire_loop_rt(
count=count,
):
with dsl.sweep(
name="pulse_lengths",
parameter=lengths_sweep_pars,
):
with dsl.section(name="drive", alignment=SectionAlignment.RIGHT):
for q, q_lengths in zip(qubits, lengths_sweep_pars):
sec = qop.x180(q, length=q_lengths, transition="ef")
sec.alignment = SectionAlignment.RIGHT
with dsl.section(name="measure", alignment=SectionAlignment.LEFT):
for q in qubits:
sec = qop.measure(q, dsl.handles.result_handle(q.uid))
# Fix the length of the measure section
sec.length = max_measure_section_length
qop.passive_reset(q)
This looks substantially different to the single-qubit version. Let's have a look at why that is:
the
pulse_lengths
are now passed as a list with the arrays for each qubit. The order in this list must match the order in the list ofqubits
.we create a join sweep over all the qubits by gathering all the sweep parameters into a list
lengths_sweep_pars
.We find the longest readout section across all the qubits (given by the longest integration duration) and fix the length of the
measure
operation to this value for all the qubits. This is to allow the case where the qubits have different integration lengths. Without fixing this length, the measurements for the qubits that are multiplexed would end up out of sync, which is not allowed; see the Measurement Rules.dsl.section
: This creates a new section in the experiment. Sections are important to create timing-consistent and reproducible behavior.We wrap the drive operations for all the qubits into an encompassing section called "drive" the contents of which we align right. We also wrap the
measure
andpassive_reset
operations into a section calledmeasure
, the contenxt of which we align left. We do this for two reasonsto allow the qubits to have different lengths for their drive pulses at every step of the sweep without leading to the measurements getting out of sync for qubits that are multiplexed.
to ensure that when the qubits have different drive lengths, the drive pulses are played back-to-back with the readout pulses. This prevents that the qubits with shorter pulse lengths are ideling during the execution of the longer pulses of the other qubits.
One run of the acquire loop in the code above results in the pulse sequence sketched below for three qubits that have different drive-pulse lengths (blue Gaussian pulses) and different readout-pulse lengths (red flat-top Gaussian pulses). Notice that, even though the qubits have different readout lengths, the individual sections of the measure
operations for each qubit have the same length set by the longest acquisition time. The right-most sections are the passive-reset sections created by the passive_reset
operations.
Let's run this experiment on the three qubit we have created above.
First, let's configure the qubits to have different readout and integration lengths, in order to run the experiment sequence for the case shown in the sketch above.
qubits[0].parameters.readout_length = 1e-6
qubits[1].parameters.readout_length = 1.5e-6
qubits[2].parameters.readout_length = 1.8e-6
qubits[0].parameters.readout_integration_length = 1.2e-6
qubits[1].parameters.readout_integration_length = 1.7e-6
qubits[2].parameters.readout_integration_length = 2e-6
exp_multi_qubit = create_experiment_multi_qubit(
qpu=qpu,
qubits=qubits,
pulse_lengths=[
np.linspace(0, 100e-9, 5),
np.linspace(0, 500e-9, 5),
np.linspace(0, 1000e-9, 5),
],
count=1,
)
Compile this experiment and inspect the pulse sheet viewer.
exp_multi_qubit_compiled = session.compile(exp_multi_qubit)
[2024.12.19 17:11:53.303] INFO Resolved modulation type of oscillator 'q0_readout_acquire_osc' on signal '/logical_signal_groups/q0/acquire' to SOFTWARE
[2024.12.19 17:11:53.304] INFO Resolved modulation type of oscillator 'q0_drive_ge_osc' on signal '/logical_signal_groups/q0/drive' to HARDWARE
[2024.12.19 17:11:53.304] INFO Resolved modulation type of oscillator 'q0_drive_ef_osc' on signal '/logical_signal_groups/q0/drive_ef' to HARDWARE
[2024.12.19 17:11:53.305] INFO Resolved modulation type of oscillator 'q1_readout_acquire_osc' on signal '/logical_signal_groups/q1/acquire' to SOFTWARE
[2024.12.19 17:11:53.305] INFO Resolved modulation type of oscillator 'q1_drive_ge_osc' on signal '/logical_signal_groups/q1/drive' to HARDWARE
[2024.12.19 17:11:53.305] INFO Resolved modulation type of oscillator 'q1_drive_ef_osc' on signal '/logical_signal_groups/q1/drive_ef' to HARDWARE
[2024.12.19 17:11:53.306] INFO Resolved modulation type of oscillator 'q2_readout_acquire_osc' on signal '/logical_signal_groups/q2/acquire' to SOFTWARE
[2024.12.19 17:11:53.306] INFO Resolved modulation type of oscillator 'q2_drive_ge_osc' on signal '/logical_signal_groups/q2/drive' to HARDWARE
[2024.12.19 17:11:53.307] INFO Resolved modulation type of oscillator 'q2_drive_ef_osc' on signal '/logical_signal_groups/q2/drive_ef' to HARDWARE
[2024.12.19 17:11:53.307] INFO Starting LabOne Q Compiler run...
[2024.12.19 17:11:53.319] INFO Schedule completed. [0.008 s]
[2024.12.19 17:11:53.362] INFO Code generation completed for all AWGs. [0.043 s]
[2024.12.19 17:11:53.362] INFO Completed compilation step 1 of 1. [0.051 s]
[2024.12.19 17:11:53.369] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:53.369] INFO Device AWG SeqC LOC CT entries Waveforms Samples
[2024.12.19 17:11:53.370] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:53.370] INFO device_hdawg 0 4 1 0 0
[2024.12.19 17:11:53.370] INFO device_hdawg 1 4 1 0 0
[2024.12.19 17:11:53.371] INFO device_shfqc 0 17 0 3 17216
[2024.12.19 17:11:53.371] INFO device_shfqc_sg 0 29 5 4 1088
[2024.12.19 17:11:53.372] INFO device_shfqc_sg 1 29 5 4 5056
[2024.12.19 17:11:53.372] INFO device_shfqc_sg 2 25 5 4 10048
[2024.12.19 17:11:53.372] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:53.373] INFO TOTAL 108 17 33408
[2024.12.19 17:11:53.373] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:53.379] INFO Finished LabOne Q Compiler run.
show_pulse_sheet("length_rabi", exp_multi_qubit_compiled, interactive=True)
[2024.12.19 17:11:53.386] INFO Recompiling the experiment due to missing extra information in the compiled experiment. Compile with `OUTPUT_EXTRAS=True` and `MAX_EVENTS_TO_PUBLISH=1000` to bypass this step with a small impact on the compilation time.
[2024.12.19 17:11:53.393] INFO Resolved modulation type of oscillator 'q0_readout_acquire_osc' on signal '/logical_signal_groups/q0/acquire' to SOFTWARE
[2024.12.19 17:11:53.393] INFO Resolved modulation type of oscillator 'q0_drive_ge_osc' on signal '/logical_signal_groups/q0/drive' to HARDWARE
[2024.12.19 17:11:53.393] INFO Resolved modulation type of oscillator 'q0_drive_ef_osc' on signal '/logical_signal_groups/q0/drive_ef' to HARDWARE
[2024.12.19 17:11:53.394] INFO Resolved modulation type of oscillator 'q1_readout_acquire_osc' on signal '/logical_signal_groups/q1/acquire' to SOFTWARE
[2024.12.19 17:11:53.394] INFO Resolved modulation type of oscillator 'q1_drive_ge_osc' on signal '/logical_signal_groups/q1/drive' to HARDWARE
[2024.12.19 17:11:53.394] INFO Resolved modulation type of oscillator 'q1_drive_ef_osc' on signal '/logical_signal_groups/q1/drive_ef' to HARDWARE
[2024.12.19 17:11:53.395] INFO Resolved modulation type of oscillator 'q2_readout_acquire_osc' on signal '/logical_signal_groups/q2/acquire' to SOFTWARE
[2024.12.19 17:11:53.395] INFO Resolved modulation type of oscillator 'q2_drive_ge_osc' on signal '/logical_signal_groups/q2/drive' to HARDWARE
[2024.12.19 17:11:53.396] INFO Resolved modulation type of oscillator 'q2_drive_ef_osc' on signal '/logical_signal_groups/q2/drive_ef' to HARDWARE
[2024.12.19 17:11:53.396] INFO Starting LabOne Q Compiler run...
[2024.12.19 17:11:53.408] INFO Schedule completed. [0.009 s]
[2024.12.19 17:11:53.449] INFO Code generation completed for all AWGs. [0.041 s]
[2024.12.19 17:11:53.450] INFO Completed compilation step 1 of 1. [0.050 s]
[2024.12.19 17:11:53.452] INFO Finished LabOne Q Compiler run.
[2024.12.19 17:11:53.457] INFO Recompiling the experiment due to missing extra information in the compiled experiment. Compile with `OUTPUT_EXTRAS=True` and `MAX_EVENTS_TO_PUBLISH=1000` to bypass this step with a small impact on the compilation time.
[2024.12.19 17:11:53.463] INFO Resolved modulation type of oscillator 'q0_readout_acquire_osc' on signal '/logical_signal_groups/q0/acquire' to SOFTWARE
[2024.12.19 17:11:53.463] INFO Resolved modulation type of oscillator 'q0_drive_ge_osc' on signal '/logical_signal_groups/q0/drive' to HARDWARE
[2024.12.19 17:11:53.464] INFO Resolved modulation type of oscillator 'q0_drive_ef_osc' on signal '/logical_signal_groups/q0/drive_ef' to HARDWARE
[2024.12.19 17:11:53.464] INFO Resolved modulation type of oscillator 'q1_readout_acquire_osc' on signal '/logical_signal_groups/q1/acquire' to SOFTWARE
[2024.12.19 17:11:53.464] INFO Resolved modulation type of oscillator 'q1_drive_ge_osc' on signal '/logical_signal_groups/q1/drive' to HARDWARE
[2024.12.19 17:11:53.465] INFO Resolved modulation type of oscillator 'q1_drive_ef_osc' on signal '/logical_signal_groups/q1/drive_ef' to HARDWARE
[2024.12.19 17:11:53.465] INFO Resolved modulation type of oscillator 'q2_readout_acquire_osc' on signal '/logical_signal_groups/q2/acquire' to SOFTWARE
[2024.12.19 17:11:53.465] INFO Resolved modulation type of oscillator 'q2_drive_ge_osc' on signal '/logical_signal_groups/q2/drive' to HARDWARE
[2024.12.19 17:11:53.466] INFO Resolved modulation type of oscillator 'q2_drive_ef_osc' on signal '/logical_signal_groups/q2/drive_ef' to HARDWARE
[2024.12.19 17:11:53.467] INFO Starting LabOne Q Compiler run...
[2024.12.19 17:11:53.478] INFO Schedule completed. [0.009 s]
[2024.12.19 17:11:53.519] INFO Code generation completed for all AWGs. [0.041 s]
[2024.12.19 17:11:53.520] INFO Completed compilation step 1 of 1. [0.050 s]
[2024.12.19 17:11:53.522] INFO Finished LabOne Q Compiler run.
We obtain the same alignment as shown in the sketch above.
Adding Options¶
None of the create_experiment
tasks we've written in the previous sections allowed for the possibility to pass additional options, such as on which transition to pefrom the Rabi experiment, whether to use calibration traces, or other arguments of the acquire_loop_rt
.
To support this functionality, the LabOne Q Tasks
and Workflows
offer the possibility to use Options
classes. The tutorial on Options explains in detail how these classes work and how define and use them.
The Applications Library contains several ready-made Options
classes for Tasks
and Workflows
. You can find an overview of them here.
We import the TuneupExperimentOptions
class and inspect its docstring to see what options fields it's got:
TuneupExperimentOptions?
Object `TuneupExperimentOptions` not found.
This class offers the following options fields:
transition="ge"
: Each kind of qubit supports different transitions. For the tunable transmon qubits implemented in the applications library the two transitions are"ge"
(i.e. ground to first excited state) and"ef"
(i.e. first to second excited state). The tunable transmon operations accept the transition to work with as a parameter.use_cal_traces=True
: whether to use calibration traces in the experiment.cal_states="ge"
: which calibration states to prepare.arguments of the acquire_loop_rt:
count
,acquisition_type
,averaging_mode
,repetition_mode
,repetition_time
,reset_oscillator_phase
.option field to enable and configure active reset:
active_reset
,active_reset_repetitions
,active_reset_states
.
If you do not need all the fields of this options class or you would require additional fields, you can always write a new options class. In this tutorial, we do not want to add active reset, so we do not need those options. Let's write a new task-options class to exclude these fields and also omit some of the acquire_loop_rt
options. We will use the decorate @workflow.task_options
to create an instance of TaskOptions
. To learn more about TaskOptions
and options in general, check out the options tutorial.
@workflow.task_options
class CreateExperimentOptions:
count = 1
averaging_mode = AveragingMode.CYCLIC
transition = "ge"
use_cal_traces = True
cal_states = "ge"
Note that, the simple definition of the options class above does not give you any kind of input validation when used inside a workflow, i.e. you can pass other option fields that do not exist or make a typo when passing one of the existing fields, and the mistake will be silently ignored. Being able to write a simple options class is useful for quick prototyping. But as soon as you start using the class often in an experiment Workflow
, input validation helps make your life easier!
All the options classes defined in the Applications Library have the logic to ensure input validation when used inside Workflows
. You can easily add this logic to your own class by using the options_field
function. Let's re-write CreateExperimentOptions
to include input validation for later used inside a Workflow
:
from typing import Literal
@workflow.task_options
class CreateExperimentOptions:
count: int = workflow.option_field(default=1)
averaging_mode: AveragingMode = workflow.option_field(AveragingMode.CYCLIC)
transition: Literal["ge", "ef"] = workflow.option_field("ge")
use_cal_traces: bool = workflow.option_field(True)
cal_states: str = workflow.option_field("ge")
You can optionally also add a description
to the option_field
to give details about what the options is used for.
Note: Input validation is only available later when the create_experiment
task using the options class above is used inside a Workflow
. The class CreateExperimentOptions
by itself behaves like a regular Python data class and enforces no validation. You can see this by instantiating the class and setting a field that does not exist:
options = CreateExperimentOptions()
options.inexistent_field = "oops" # silently ignored
options
CreateExperimentOptions(count=1, averaging_mode=AveragingMode.CYCLIC, transition='ge', use_cal_traces=True, cal_states='ge')
Now, we re-write our create_experiment_multi_qubits
task to include this options class:
@workflow.task
@dsl.qubit_experiment
def create_experiment_multi_qubit_options(
qpu,
qubits,
pulse_lengths,
options: CreateExperimentOptions | None = None,
):
"""Pulse duration Rabi experiment on multiple qubits in parallel."""
# If no options are passed, we use our class CreateExperimentOptions
opts = CreateExperimentOptions() if options is None else options
# Validation: ensure qubits and pulse_lengths_list have the same length
qubits, pulse_lengths = validation.validate_and_convert_qubits_sweeps(
qubits, pulse_lengths
)
lengths_sweep_pars = [
SweepParameter(f"pulse_lengths_{q.uid}", q_lengths)
for q, q_lengths in zip(qubits, pulse_lengths)
]
# We will fix the length of the measure section to the longest section among
# the qubits to allow the qubits to have different readout and/or
# integration lengths.
max_measure_section_length = qpu.measure_section_length(qubits)
qop = qpu.quantum_operations
with dsl.acquire_loop_rt(
count=opts.count,
averaging_mode=opts.averaging_mode
):
with dsl.sweep(
name="pulse_lengths",
parameter=lengths_sweep_pars,
):
with dsl.section(name="drive", alignment=SectionAlignment.RIGHT):
for q, q_lengths in zip(qubits, lengths_sweep_pars):
qop.prepare_state.omit_section(q, state=opts.transition[0])
sec = qop.x180(q, length=q_lengths, transition=opts.transition)
sec.alignment = SectionAlignment.RIGHT
with dsl.section(name="measure", alignment=SectionAlignment.LEFT):
for q in qubits:
sec = qop.measure(q, dsl.handles.result_handle(q.uid))
# Fix the length of the measure section
sec.length = max_measure_section_length
qop.passive_reset(q)
if opts.use_cal_traces:
qop.calibration_traces.omit_section(
qubits=qubits,
states=opts.cal_states,
)
Notice a few things that have changed:
we have specified the type of the
options
input parameters asTuneupExperimentOptions | None
. You do not need to do this for quick prototyping, but you have to do this when using thecreate_experiment_multi_qubit_options
task inside aWorkflow
. As explained in the tutorial on options, theWorkflow
mechanism automatically resolves the tree of task options for its constituentTasks
, such that they are all available to be configured at theWorkflow
level before running your experimentWorkflow
. However, this automatic resolution only happens if the type of theoptions
input parameter of eachTask
is specified as an instance ofTaskOptions
, in our caseCreateExperimentOptions
. If this type specification is omitted, theWorkflow
does not detect that thisTask
accepts options and they will not be configurable when you want to run your experiment.we have added a new quantum operation,
qop.prepare_state
. In case the transition on which to run the experiment is not "ge", we must first prepare the lowermost state in the transition (for example, "e" iftransition="ef"
). Theprepare_state
in the set of operations for tunable transmons (TunableTransmonOperations) accepts"g"
,"e"
and"f"
as states to prepare.we have added the calibration_traces operation, to be used only if
use_cal_traces=True
.
Let's run the task on the "ef" transition and using the calibration traces for the states "g", "e", "f".
options = CreateExperimentOptions()
options.transition = "ef"
options.use_cal_traces = True
options.cal_states = "gef"
exp_multi_qubit_options = create_experiment_multi_qubit_options(
qpu=qpu,
qubits=qubits,
pulse_lengths=[
np.linspace(0, 100e-9, 3),
np.linspace(0, 500e-9, 3),
np.linspace(0, 1000e-9, 3),
],
options=options
)
exp_multi_qubit_options_compiled = session.compile(exp_multi_qubit_options)
[2024.12.19 17:11:53.620] INFO Resolved modulation type of oscillator 'q0_readout_acquire_osc' on signal '/logical_signal_groups/q0/acquire' to SOFTWARE
[2024.12.19 17:11:53.621] INFO Resolved modulation type of oscillator 'q0_drive_ge_osc' on signal '/logical_signal_groups/q0/drive' to HARDWARE
[2024.12.19 17:11:53.621] INFO Resolved modulation type of oscillator 'q0_drive_ef_osc' on signal '/logical_signal_groups/q0/drive_ef' to HARDWARE
[2024.12.19 17:11:53.622] INFO Resolved modulation type of oscillator 'q1_readout_acquire_osc' on signal '/logical_signal_groups/q1/acquire' to SOFTWARE
[2024.12.19 17:11:53.622] INFO Resolved modulation type of oscillator 'q1_drive_ge_osc' on signal '/logical_signal_groups/q1/drive' to HARDWARE
[2024.12.19 17:11:53.623] INFO Resolved modulation type of oscillator 'q1_drive_ef_osc' on signal '/logical_signal_groups/q1/drive_ef' to HARDWARE
[2024.12.19 17:11:53.623] INFO Resolved modulation type of oscillator 'q2_readout_acquire_osc' on signal '/logical_signal_groups/q2/acquire' to SOFTWARE
[2024.12.19 17:11:53.623] INFO Resolved modulation type of oscillator 'q2_drive_ge_osc' on signal '/logical_signal_groups/q2/drive' to HARDWARE
[2024.12.19 17:11:53.624] INFO Resolved modulation type of oscillator 'q2_drive_ef_osc' on signal '/logical_signal_groups/q2/drive_ef' to HARDWARE
[2024.12.19 17:11:53.625] INFO Starting LabOne Q Compiler run...
[2024.12.19 17:11:53.636] INFO Schedule completed. [0.008 s]
[2024.12.19 17:11:53.683] INFO Code generation completed for all AWGs. [0.046 s]
[2024.12.19 17:11:53.684] INFO Completed compilation step 1 of 1. [0.055 s]
[2024.12.19 17:11:53.690] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:53.690] INFO Device AWG SeqC LOC CT entries Waveforms Samples
[2024.12.19 17:11:53.691] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:53.691] INFO device_hdawg 0 4 1 0 0
[2024.12.19 17:11:53.691] INFO device_hdawg 1 4 1 0 0
[2024.12.19 17:11:53.692] INFO device_shfqc 0 18 0 3 17216
[2024.12.19 17:11:53.692] INFO device_shfqc_sg 0 32 5 4 1088
[2024.12.19 17:11:53.693] INFO device_shfqc_sg 1 32 5 4 3488
[2024.12.19 17:11:53.693] INFO device_shfqc_sg 2 30 5 4 6464
[2024.12.19 17:11:53.693] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:53.694] INFO TOTAL 120 17 28256
[2024.12.19 17:11:53.695] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:53.700] INFO Finished LabOne Q Compiler run.
This time, we inspect the pulse sequence using plot_simulation
because it's easier to see the additional pulses, but you could also use the pulse sheet viewer as above.
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
plot_simulation(exp_multi_qubit_options_compiled, start_time=0e-6, length=17.5e-6,
signal_names_to_show=["drive"])
Notice the preparation $\pi$-pulses on the "ge" transition before the "ef" pulses whose length is being swept. Notice also, the three calibration-state preparations after the sweep: no pulse to prepare the "g" state (at ~$11\mu$s), a "ge" $\pi$ pulse to prepare the "e" state (at ~$13.75\mu$s), and a "ge" $\pi$ pulse followed by an "ef" $\pi$ pulse to prepare the "f" state (at ~$17\mu$s).
The passive-reset time is only $1\mu$s, set in the qubit parameters reset_delay_length
. This makes it easier to look at the experiment in emulation mode. When running on real qubits, the reset_delay_length
should be around $3T_1$.
Write the Experiment Workflow¶
Now that we have written the create_experiment
task, we can create the length-Rabi experiment Workflow
.
You can write a Workflow
without options. But since we have used options for our create_experiment
task, we should allow our experiment Workflow
to accept options as well.
For this we use workflow.WorkflowOptions
, which is a special class that configures the Workflow
engine to perform this automatic resolution of the options of each task we have mentioned above after creating the create_experiment_multi_qubit_options
task. To learn more about this and about the difference between WorkflowOptions
and TaskOptions
, see the tutorial on Options.
@workflow.workflow(name="length_rabi")
def experiment_workflow(
session,
qpu,
qubits,
pulse_lengths,
temporary_parameters = None,
options: workflow.WorkflowOptions | None = None,
):
exp = create_experiment_multi_qubit_options(qpu, qubits, pulse_lengths)
compiled_exp = workflow.tasks.compile_experiment(session, exp)
result = workflow.tasks.run_experiment(session, compiled_exp)
workflow.return_(result)
Get the experiment_workflow
options:
options = experiment_workflow.options()
options
WorkflowOptions( │ _task_options={ │ │ 'create_experiment_multi_qubit_options': CreateExperimentOptions( │ │ │ count=1, │ │ │ averaging_mode=AveragingMode.CYCLIC, │ │ │ transition='ge', │ │ │ use_cal_traces=True, │ │ │ cal_states='ge' │ │ ), │ │ 'run_experiment': RunExperimentOptions( │ │ │ return_legacy_results=False │ │ ) │ } )
Notice the automatic resolution of the options of the tasks. If you now got back to the definition of create_experiment_multi_qubit_options
and remove the type of the options
input parameter (remove CreateExperimentOptions | None
), you will see that the create_experiment_multi_qubit_options
do not appear in the options created in the cell. Try it!
Notice also that we did not manually pass options=options
when calling the tasks inside the experiment_workflow
as you would normally do. In addition to the automatic resolution of task options, the Workflow
also automatically distributes the relevant options from its input to the correct tasks. So options should never be manually passed to the tasks inside a Workflow
.
Let's run the experiment_workflow
!
workflow_results = experiment_workflow(
session=session,
qpu=qpu,
qubits=qubits,
pulse_lengths=[
np.linspace(0, 100e-9, 3),
np.linspace(0, 500e-9, 3),
np.linspace(0, 1000e-9, 3),
],
options=options
).run()
[2024.12.19 17:11:54.249] INFO ──────────────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:54.250] INFO Workflow 'length_rabi': execution started at 2024-12-19 17:11:54.249163Z
[2024.12.19 17:11:54.250] INFO ──────────────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:54.251] INFO Task 'create_experiment_multi_qubit_options': started at 2024-12-19
[2024.12.19 17:11:54.251] INFO 17:11:54.251071Z
[2024.12.19 17:11:54.254] INFO Task 'create_experiment_multi_qubit_options': ended at 2024-12-19
[2024.12.19 17:11:54.254] INFO 17:11:54.253775Z
[2024.12.19 17:11:54.255] INFO Task 'compile_experiment': started at 2024-12-19 17:11:54.255279Z
[2024.12.19 17:11:54.263] INFO Resolved modulation type of oscillator 'q0_readout_acquire_osc' on signal '/logical_signal_groups/q0/acquire' to SOFTWARE
[2024.12.19 17:11:54.263] INFO Resolved modulation type of oscillator 'q0_drive_ge_osc' on signal '/logical_signal_groups/q0/drive' to HARDWARE
[2024.12.19 17:11:54.264] INFO Resolved modulation type of oscillator 'q0_drive_ef_osc' on signal '/logical_signal_groups/q0/drive_ef' to HARDWARE
[2024.12.19 17:11:54.264] INFO Resolved modulation type of oscillator 'q1_readout_acquire_osc' on signal '/logical_signal_groups/q1/acquire' to SOFTWARE
[2024.12.19 17:11:54.265] INFO Resolved modulation type of oscillator 'q1_drive_ge_osc' on signal '/logical_signal_groups/q1/drive' to HARDWARE
[2024.12.19 17:11:54.265] INFO Resolved modulation type of oscillator 'q1_drive_ef_osc' on signal '/logical_signal_groups/q1/drive_ef' to HARDWARE
[2024.12.19 17:11:54.265] INFO Resolved modulation type of oscillator 'q2_readout_acquire_osc' on signal '/logical_signal_groups/q2/acquire' to SOFTWARE
[2024.12.19 17:11:54.266] INFO Resolved modulation type of oscillator 'q2_drive_ge_osc' on signal '/logical_signal_groups/q2/drive' to HARDWARE
[2024.12.19 17:11:54.266] INFO Resolved modulation type of oscillator 'q2_drive_ef_osc' on signal '/logical_signal_groups/q2/drive_ef' to HARDWARE
[2024.12.19 17:11:54.267] INFO Starting LabOne Q Compiler run...
[2024.12.19 17:11:54.276] INFO Schedule completed. [0.006 s]
[2024.12.19 17:11:54.318] INFO Code generation completed for all AWGs. [0.041 s]
[2024.12.19 17:11:54.319] INFO Completed compilation step 1 of 1. [0.049 s]
[2024.12.19 17:11:54.325] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:54.325] INFO Device AWG SeqC LOC CT entries Waveforms Samples
[2024.12.19 17:11:54.326] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:54.326] INFO device_hdawg 0 4 1 0 0
[2024.12.19 17:11:54.326] INFO device_hdawg 1 4 1 0 0
[2024.12.19 17:11:54.327] INFO device_shfqc 0 15 0 3 17216
[2024.12.19 17:11:54.328] INFO device_shfqc_sg 0 23 4 3 864
[2024.12.19 17:11:54.328] INFO device_shfqc_sg 1 23 4 3 3264
[2024.12.19 17:11:54.328] INFO device_shfqc_sg 2 21 4 3 6240
[2024.12.19 17:11:54.329] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:54.329] INFO TOTAL 90 14 27584
[2024.12.19 17:11:54.330] INFO ─────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:54.334] INFO Finished LabOne Q Compiler run.
[2024.12.19 17:11:54.338] INFO Task 'compile_experiment': ended at 2024-12-19 17:11:54.337746Z
[2024.12.19 17:11:54.338] INFO Task 'run_experiment': started at 2024-12-19 17:11:54.338369Z
[2024.12.19 17:11:54.349] INFO Starting near-time execution...
[2024.12.19 17:11:54.364] INFO Finished near-time execution.
[2024.12.19 17:11:54.366] INFO Task 'run_experiment': ended at 2024-12-19 17:11:54.366182Z
[2024.12.19 17:11:54.367] INFO ──────────────────────────────────────────────────────────────────────────────
[2024.12.19 17:11:54.367] INFO Workflow 'length_rabi': execution ended at 2024-12-19 17:11:54.367021Z
[2024.12.19 17:11:54.368] INFO ──────────────────────────────────────────────────────────────────────────────
from laboneq.contrib.example_helpers.plotting.plot_helpers import plot_simulation
plot_simulation(workflow_results.tasks["compile_experiment"].output, start_time=0e-6, length=17.5e-6,
signal_names_to_show=["drive"])
To learn more about how to inspect the results of experiment Workflows
and how to work with them in general, check out the tutorial on experiment Workflows.
Great! You've finished writing you new length-Rabi experiment Workflow
. Check out the other tutorials to learn more about the Applications Library.