Tutorial 3) Defining Complex Measurements

This tutorial covers two approaches to building complex measurement routines in arbok-driver:

  1. Dictionary-based composition — defining the full sequence hierarchy from a nested dictionary in a single call

  2. Experiment blueprints — using pre-defined Experiment classes to standardise frequently used routines across devices and team members

All code cells are intended to be run in succession. This tutorial assumes you have already set up a Device and ArbokDriver instance as shown in Tutorial 1.

0. Prerequisites

Make sure you have completed Tutorial 1 (Initial Setup) and that the following objects are available in your session:

from arbok_driver import ArbokDriver, Device, Measurement
from arbok_driver.examples.configurations.hardware import opx1000_config

device = Device('device_8q', opx_config=opx1000_config, divider_config={})
my_driver = ArbokDriver('my_driver', device)

1. The Problem: Growing Measurement Complexity

Simple measurements like a charge stability map consist of one or two sub-sequences. As experiments become more sophisticated, a single measurement routine quickly accumulates many sub-sequences with interdependent parameters. A typical coherence measurement such as a CPMG sequence (Carr-Purcell-Meiboom-Gill) requires:

  • A state initialization stage (parity initialization)

  • A transit to the control point in gate voltage space

  • A spin control stage containing multiple nested operations:

    • An initial pi/2 rotation to place the qubit on the equator

    • The CPMG refocusing pulses

    • A final state projection

  • A transit back from the control point

  • A readout stage

Adding these components one by one, managing their interdependencies, and ensuring consistent configuration across measurements quickly becomes error-prone, especially when the same routine needs to be deployed on a new device or by a new team member.

arbok-driver provides two solutions to this problem, covered in the following sections.


2. Dictionary-Based Composition

Rather than instantiating and adding sub-sequences individually, arbok-driver allows the entire hierarchy to be defined as a nested dictionary. Each entry specifies:

  • sequence: the SubSequence class to instantiate

  • kwargs: optional keyword arguments passed to the constructor

  • sub_sequences: a nested dictionary of further child sub-sequences

  • config: a device configuration dictionary (used for readout and initialization stages)

The full hierarchy is then constructed in a single call to add_subsequences_from_dict.

from arbok_driver.examples.sequences import (
    ToControlPoint, FromControlPoint,
    Xstrict, Cpmg, StateProjection
)
from arbok_driver.examples.configurations.sequence import (
    parity_init_conf, parity_read_conf
)

cpmg_conf = {
    # Initialization stage — device-specific, provided via config
    'parity_init': {'config': parity_init_conf},
    # Move to the control point in gate voltage space
    'to_control': {'sequence': ToControlPoint},
    # Spin control stage — contains nested sub-sequences
    'spin_control': {
        'sub_sequences': {
            # Initial pi/2 rotation to place qubit on the Bloch sphere equator
            'x_strict': {
                'sequence': Xstrict,
                'kwargs': {
                    'target_qubit': 'Q1',
                    'control_pulse': 'control_pi2'
                }
            },
            # CPMG refocusing pulses
            'cpmg': {
                'sequence': Cpmg,
                'kwargs': {
                    'target_qubit': 'Q1',
                    'repetitions': 10,         # number of refocusing pulses
                    't_equator_wait': int(1e3) # free evolution time in ns
                }
            },
            # Final state projection before readout
            'state_projection': {
                'sequence': StateProjection,
                'kwargs': {'target_qubit': 'Q1'}
            }
        },
        # check_step_requirements enables asynchronous conditional execution
        # (see Tutorial 4: Asynchronous Measurements)
        'kwargs': {'check_step_requirements': True}
    },
    # Move back from the control point
    'from_control': {'sequence': FromControlPoint},
    # Readout stage — device-specific, provided via config
    'parity_read': {'config': parity_read_conf}
}

# Instantiate the measurement and build the hierarchy from the dictionary
cpmg_meas = Measurement(my_driver, 'cpmg_meas')
cpmg_meas.add_subsequences_from_dict(cpmg_conf)

The full parameter space of the measurement is now available through the QCoDeS interface. You can inspect it with:

cpmg_meas.print_readable_snapshot()

All parameters, including t_equator_wait, repetitions, and any voltage offsets defined in the initialization and readout configurations, are registered as QCoDeS parameters and can be swept or updated directly.


3. The Problem with Raw Dictionaries at Scale

The dictionary approach works well for one-off measurements or exploratory work. However, in a team environment or across multiple devices, raw dictionaries introduce a maintenance problem:

  • The same dictionary may be copied and modified independently by different users

  • There is no guarantee that two instances of nominally the same measurement are actually identical

  • When a sub-sequence is updated or renamed, all copies of the dictionary need to be found and updated manually

For measurements that are run routinely, arbok-driver provides the Experiment class to address this.


4. Experiment Blueprints

An Experiment object encodes the invariant structure of a measurement — the sub-sequence hierarchy, class choices, and default parameters — while leaving the device-specific components as required arguments. This means:

  • The experimental logic is defined once and versioned centrally

  • Different devices only need to provide their own initialization and readout configurations

  • New team members can deploy the same routine on a new device by providing two arguments

The CpmgExperiment blueprint encodes exactly the same structure as the dictionary above:

from arbok_driver.examples.experiments import CpmgExperiment

# Create the identical CPMG measurement from a pre-defined blueprint
# Only the device-specific components need to be provided
cpmg_meas2 = my_driver.create_measurement_from_experiment(
    CpmgExperiment(
        target_qubit='Q1',
        parity_init=parity_init_conf,
        parity_read=parity_read_conf
    )
)

The resulting cpmg_meas2 object is identical to cpmg_meas in every respect. Verify this by comparing their parameter snapshots:

cpmg_meas2.print_readable_snapshot()

5. Deploying on a Different Device

The real benefit of the Experiment pattern becomes apparent when the same measurement needs to run on a different device. Only the configuration objects change while the sequence structure and all internal logic remain exactly as defined in the blueprint:

# Deploy the same CPMG routine on a different device
# No changes to the experiment blueprint are needed
# cpmg_meas_device2 = my_driver.create_measurement_from_experiment(
#     CpmgExperiment(
#         target_qubit='Q1',
#         init_config=parity_init_conf_device2,
#         read_config=parity_read_conf_device2
#     )
# )

6. Summary

Approach

Best suited for

add_subsequences_from_dict

Exploratory measurements, one-off routines, prototyping new sequences

Experiment blueprint

Established routines, multi-device campaigns, team environments

Both approaches produce identical measurement objects and the same QUA program. The Experiment pattern adds a layer of standardisation that reduces the risk of inconsistencies and makes it easier for new users to get started without understanding the full internal structure of the sequence.

Next steps:

  • Tutorial 4: Asynchronous Measurements — using step requirements for heralding and conditional execution

  • Tutorial 5: Parameter Input Streaming — adaptive measurements with the GenericTuningInterface