Tutorial 4) Generic Tuning Interface

Motivation

So far, the tutorials have focused on parameter sweeps along predefined dimensions. While these sweeps do not need to be linear, all parameter values must still be known in advance before compiling the QUA program.

However, many practical optimization problems require adaptive and non-linear exploration of parameter space, where future values depend on previous measurement results.

Quantum Machines hardware provides a solution via input streams, which allow parameters to be updated dynamically during program execution. (See the official documentation for more details.)

The GenericTuningInterface in Arbok builds on this feature to enable adaptive tuning workflows with minimal changes to existing measurement definitions.

In this tutorial, we demonstrate a real-world example: optimizing the initialization and readout of two different spin parity states. The underlying physics is not essential here—the focus is on how the interface enables flexible and scalable optimization.

The GenericTuningInterface

Creating the driver and a measurement

As in previous tutorials, we begin by creating a Device and initializing the corresponding driver. The device configuration defines the hardware setup, while the driver provides the interface to the Quantum Machines backend.

import numpy as np
from rich import print as rprint
from arbok_driver import ArbokDriver, Device, Measurement
from arbok_driver.examples.configurations.hardware import opx1000_config
from arbok_driver.examples.configurations.sequence import (
    device_config, parity_init_conf, parity_read_conf
)

mock_device = Device(
    name = 'mock_device',
    opx_config = opx1000_config,
    divider_config = {},
    master_config = device_config
)

qm_driver = ArbokDriver('qm_driver', mock_device)
2026-04-06 05:21:50,115 - qm - INFO     - Starting session: fbee7913-cacc-49d9-94b1-bbdb933f55fc

Next, we construct a measurement consisting of four subsequences:

  • initialization (even parity)

  • readout (even parity)

  • initialization (odd parity)

  • readout (odd parity)

At this stage, the even and odd branches are identical apart from their naming. The goal of the optimization will be to maximize the distinguishability between these two cases.

mock_measurement = Measurement(qm_driver, 'mock_measurement')

mock_measurement.add_subsequences_from_dict({
    'parity_init_even': {'config': parity_init_conf},
    'parity_read_even': {'config': parity_read_conf},
    'parity_init_odd': {'config': parity_init_conf},
    'parity_read_odd': {'config': parity_read_conf}
})
mock_measurement.set_sweeps(
    {mock_measurement.iteration: np.arange(100)}
)
Declared 1-dimensional parameter sweep of size 100 [100]
mock_measurement.register_gettables()
Registered 16 gettables for measurement

Initializing the interface

To construct the tuning interface, only the parameter configuration needs to be defined at initialization.

Parameter configuration
A dictionary describing which parameters should be tuned. Each entry maps a high-level tuning parameter to one or more underlying QUA variables, along with scaling factors and bounds.

#[g.full_name for g in mock_measurement.available_gettables]

The parameter dictionary defines how high-level tuning parameters map to underlying QUA variables.

Each entry consists of:

  • qua_vars: a mapping from QUA parameters to scaling factors

  • bounds: the allowed range for the parameter during optimization

This allows multiple physical parameters to be controlled simultaneously through a single tuning variable. For example, the read_detuning parameter modifies both readout branches in a coordinated way, including sign inversion where required.

To inspect available parameters, use:

#mock_measurement.print_readable_snapshot()
tuning_interface = mock_measurement.initialize_tuning_interface(
    parameter_dicts = {
        't_ramp_init': {
            'qua_vars': {mock_measurement.parity_init_odd.arbok_params.t_ramp_over_crossing: 1},
            'bounds': (25, 1e4)
            },
        'read_detuning': {
            'qua_vars': {
                mock_measurement.parity_read_even.arbok_params.v_read['P1']: 1,
                mock_measurement.parity_read_even.arbok_params.v_read['P2']: -1,
                mock_measurement.parity_read_odd.arbok_params.v_read['P1']: 1,
                mock_measurement.parity_read_odd.arbok_params.v_read['P2']: -1,
                },
            'bounds': (-0.01, 0.01)
            },
        },
    verbose = True
)
Adding input stream for t_ramp_over_crossing (t_ramp_init)
Adding input stream for v_read_P1 (read_detuning)
        Adding v_read_P2 to v_read_P1  input stream (factor: -1) (read_detuning)
        Adding v_read_P1 to v_read_P1  input stream (factor: 1) (read_detuning)
        Adding v_read_P2 to v_read_P1  input stream (factor: -1) (read_detuning)

The resulting QUA program differs from a standard measurement in that selected parameters are replaced by variables connected to input streams. This enables values to be updated dynamically at runtime without recompilation.

rprint(mock_measurement.get_qua_program_as_str())
# Single QUA script generated at 2026-04-06 05:21:53.054700
# QUA library version: 1.2.5


from qm import CompilerOptionArguments
from qm.qua import *

with program() as prog:
    v1 = declare(int, )
    v2 = declare(int, value=0)
    v3 = declare(int, )
    input_stream_mock_measurement_int_input_stream = declare_input_stream(int, 'mock_measurement_int_input_stream',
size=1)
    v4 = declare(fixed, )
    input_stream_mock_measurement_fixed_input_stream = declare_input_stream(fixed, 
'mock_measurement_fixed_input_stream', size=1)
    v5 = declare(fixed, )
    v6 = declare(fixed, )
    v7 = declare(fixed, )
    v8 = declare(fixed, )
    v9 = declare(fixed, )
    v10 = declare(fixed, )
    v11 = declare(bool, )
    v12 = declare(bool, )
    v13 = declare(fixed, )
    v14 = declare(fixed, )
    v15 = declare(fixed, )
    v16 = declare(fixed, )
    v17 = declare(fixed, )
    v18 = declare(fixed, )
    v19 = declare(bool, )
    v20 = declare(bool, )
    v21 = declare(int, )
    with infinite_loop_():
        pause()
        assign(v2, 0)
        advance_input_stream(input_stream_mock_measurement_int_input_stream)
        assign(v3, input_stream_mock_measurement_int_input_stream[0])
        advance_input_stream(input_stream_mock_measurement_fixed_input_stream)
        assign(v4, input_stream_mock_measurement_fixed_input_stream[0])
        assign(v21, 0)
        with while_((v21<100)):
            assign(v1, (0+(1*v21)))
            align()
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp(0.06), "P1", duration=250)
            play("unit_ramp"*amp(-0.085), "J1", duration=250)
            play("unit_ramp"*amp(-0.06), "P2", duration=250)
            play("unit_ramp"*amp(-0.07), "P7", duration=250)
            play("unit_ramp"*amp(-0.05), "J7", duration=250)
            play("unit_ramp"*amp(0.07), "P8", duration=250)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(50000, "P1", "J1", "P2", "P7", "J7", "P8")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp(-0.0165), "P1", duration=250)
            play("unit_ramp"*amp(0.035), "J1", duration=250)
            play("unit_ramp"*amp(0.0165), "P2", duration=250)
            play("unit_ramp"*amp(0.099), "P7", duration=250)
            play("unit_ramp"*amp(0.117), "J7", duration=250)
            play("unit_ramp"*amp(-0.099), "P8", duration=250)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(9571, "P1", "J1", "P2", "P7", "J7", "P8")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp(-0.03), "P1", duration=141)
            play("unit_ramp"*amp(0.03), "P2", duration=141)
            play("unit_ramp"*amp(-0.02), "P7", duration=141)
            play("unit_ramp"*amp(0.02), "P8", duration=141)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(250, "P1", "J1", "P2", "P7", "J7", "P8")
            ramp_to_zero("P1")
            ramp_to_zero("J1")
            ramp_to_zero("P2")
            ramp_to_zero("P7")
            ramp_to_zero("J7")
            ramp_to_zero("P8")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            align()
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            wait(25000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp(0.023), "P1", duration=6250)
            play("unit_ramp"*amp(-0.02), "J1", duration=6250)
            play("unit_ramp"*amp(-0.023), "P2", duration=6250)
            play("unit_ramp"*amp(-0.0325), "P7", duration=6250)
            play("unit_ramp"*amp(0.005), "J7", duration=6250)
            play("unit_ramp"*amp(0.0325), "P8", duration=6250)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(25000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            measure("measure", "SET1", integration.full("x_const", v5, ""))
            measure("measure", "SET2", integration.full("x_const", v6, ""))
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            wait(5000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp((v4-0.023)), "P1", duration=250)
            play("unit_ramp"*amp(((v4*-1)--0.023)), "P2", duration=250)
            play("unit_ramp"*amp(-0.0025000000000000022), "P7", duration=250)
            play("unit_ramp"*amp(0.0025000000000000022), "P8", duration=250)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(25000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            measure("measure", "SET1", integration.full("x_const", v7, ""))
            measure("measure", "SET2", integration.full("x_const", v8, ""))
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align()
            assign(v9, (v5-v7))
            assign(v10, (v6-v8))
            assign(v11, (v9>0.001))
            assign(v12, (v10>-0.001))
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            wait(5000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            ramp_to_zero("P1")
            ramp_to_zero("J1")
            ramp_to_zero("P2")
            ramp_to_zero("P7")
            ramp_to_zero("J7")
            ramp_to_zero("P8")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(1000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align()
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp(0.06), "P1", duration=250)
            play("unit_ramp"*amp(-0.085), "J1", duration=250)
            play("unit_ramp"*amp(-0.06), "P2", duration=250)
            play("unit_ramp"*amp(-0.07), "P7", duration=250)
            play("unit_ramp"*amp(-0.05), "J7", duration=250)
            play("unit_ramp"*amp(0.07), "P8", duration=250)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(50000, "P1", "J1", "P2", "P7", "J7", "P8")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp(-0.0165), "P1", duration=250)
            play("unit_ramp"*amp(0.035), "J1", duration=250)
            play("unit_ramp"*amp(0.0165), "P2", duration=250)
            play("unit_ramp"*amp(0.099), "P7", duration=250)
            play("unit_ramp"*amp(0.117), "J7", duration=250)
            play("unit_ramp"*amp(-0.099), "P8", duration=250)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(9571, "P1", "J1", "P2", "P7", "J7", "P8")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp(-0.03), "P1", duration=v3)
            play("unit_ramp"*amp(0.03), "P2", duration=v3)
            play("unit_ramp"*amp(-0.02), "P7", duration=v3)
            play("unit_ramp"*amp(0.02), "P8", duration=v3)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(250, "P1", "J1", "P2", "P7", "J7", "P8")
            ramp_to_zero("P1")
            ramp_to_zero("J1")
            ramp_to_zero("P2")
            ramp_to_zero("P7")
            ramp_to_zero("J7")
            ramp_to_zero("P8")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            align()
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            wait(25000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp(0.023), "P1", duration=6250)
            play("unit_ramp"*amp(-0.02), "J1", duration=6250)
            play("unit_ramp"*amp(-0.023), "P2", duration=6250)
            play("unit_ramp"*amp(-0.0325), "P7", duration=6250)
            play("unit_ramp"*amp(0.005), "J7", duration=6250)
            play("unit_ramp"*amp(0.0325), "P8", duration=6250)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(25000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            measure("measure", "SET1", integration.full("x_const", v13, ""))
            measure("measure", "SET2", integration.full("x_const", v14, ""))
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            wait(5000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            play("unit_ramp"*amp((v4-0.023)), "P1", duration=250)
            play("unit_ramp"*amp(((v4*-1)--0.023)), "P2", duration=250)
            play("unit_ramp"*amp(-0.0025000000000000022), "P7", duration=250)
            play("unit_ramp"*amp(0.0025000000000000022), "P8", duration=250)
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(25000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            measure("measure", "SET1", integration.full("x_const", v15, ""))
            measure("measure", "SET2", integration.full("x_const", v16, ""))
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align()
            assign(v17, (v13-v15))
            assign(v18, (v14-v16))
            assign(v19, (v17>0.001))
            assign(v20, (v18>-0.001))
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            wait(5000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            ramp_to_zero("P1")
            ramp_to_zero("J1")
            ramp_to_zero("P2")
            ramp_to_zero("P7")
            ramp_to_zero("J7")
            ramp_to_zero("P8")
            align("P1", "J1", "P2", "P7", "J7", "P8")
            wait(1000, "P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align("P1", "J1", "P2", "P7", "J7", "P8", "SET1", "SET2")
            align()
            r2 = declare_stream()
            save(v5, r2)
            r3 = declare_stream()
            save(v6, r3)
            r4 = declare_stream()
            save(v7, r4)
            r5 = declare_stream()
            save(v8, r5)
            r6 = declare_stream()
            save(v9, r6)
            r7 = declare_stream()
            save(v10, r7)
            r8 = declare_stream()
            save(v11, r8)
            r9 = declare_stream()
            save(v12, r9)
            r10 = declare_stream()
            save(v13, r10)
            r11 = declare_stream()
            save(v14, r11)
            r12 = declare_stream()
            save(v15, r12)
            r13 = declare_stream()
            save(v16, r13)
            r14 = declare_stream()
            save(v17, r14)
            r15 = declare_stream()
            save(v18, r15)
            r16 = declare_stream()
            save(v19, r16)
            r17 = declare_stream()
            save(v20, r17)
            align()
            assign(v2, (v2+1))
            r1 = declare_stream()
            save(v2, r1)
            align()
            assign(v21, (v21+1))
    with stream_processing():
        r1.buffer(1).save("qm_driver_mock_measurement_shots")
        r2.buffer(100).save("qm_driver_mock_measurement_parity_read_even_ref__p1p2")
        r3.buffer(100).save("qm_driver_mock_measurement_parity_read_even_ref__p7p8")
        r4.buffer(100).save("qm_driver_mock_measurement_parity_read_even_read__p1p2")
        r5.buffer(100).save("qm_driver_mock_measurement_parity_read_even_read__p7p8")
        r6.buffer(100).save("qm_driver_mock_measurement_parity_read_even_diff__p1p2")
        r7.buffer(100).save("qm_driver_mock_measurement_parity_read_even_diff__p7p8")
        r8.buffer(100).save("qm_driver_mock_measurement_parity_read_even_state__p1p2")
        r9.buffer(100).save("qm_driver_mock_measurement_parity_read_even_state__p7p8")
        r10.buffer(100).save("qm_driver_mock_measurement_parity_read_odd_ref__p1p2")
        r11.buffer(100).save("qm_driver_mock_measurement_parity_read_odd_ref__p7p8")
        r12.buffer(100).save("qm_driver_mock_measurement_parity_read_odd_read__p1p2")
        r13.buffer(100).save("qm_driver_mock_measurement_parity_read_odd_read__p7p8")
        r14.buffer(100).save("qm_driver_mock_measurement_parity_read_odd_diff__p1p2")
        r15.buffer(100).save("qm_driver_mock_measurement_parity_read_odd_diff__p7p8")
        r16.buffer(100).save("qm_driver_mock_measurement_parity_read_odd_state__p1p2")
        r17.buffer(100).save("qm_driver_mock_measurement_parity_read_odd_state__p7p8")

config = None

loaded_config = None


The interface is now ready for execution. In this tutorial, we operate in mock mode, but the same workflow applies to real hardware.

Parameter sets can be evaluated using run_parameter_set, which:

  • streams the provided values into the program

  • executes the measurement

  • returns the recorded data and the used parameters

qm_driver.is_mock = True
mock_measurement.mock_steps = 0
# mock_measurement.tuning_interface.compile_connect_and_run( host_ip = 'xxx.xxx.xxx.xxx')

The first steps

The tuning interface is now ready to be used. Values to be measured can now be inserted the following way via run_parameter_set:

step_dict = {
        mock_measurement.parity_init_odd.arbok_params.t_ramp_over_crossing: 100,
        mock_measurement.parity_read_even.arbok_params.v_read['P1']: 0.1
    }
gettable_results, saved_params = tuning_interface.run_parameter_set(step_dict)

Cross-entropy sampling

Exploring high-dimensional parameter spaces manually is often impractical. To address this, the interface includes a cross-entropy sampling algorithm for automated optimization.

A key feature of the updated API is that the cost function is provided as a simple callable, allowing users to define optimization objectives in a lightweight and flexible way.

The method operates iteratively:

  1. Generate a population of random parameter sets within the defined bounds.

  2. Evaluate their performance using the user-defined cost function.

  3. Select the top-performing fraction (select_frac).

  4. Shrink the parameter bounds based on these elite samples.

  5. Repeat for subsequent populations.

This approach efficiently concentrates the search around promising regions of parameter space while maintaining robustness against local minima.

def cost_strategy(gettables: dict) -> float:
    cost = np.mean(
        gettables[mock_measurement.parity_read_even.p1p2.diff__p1p2])
    cost -= np.mean(
        gettables[mock_measurement.parity_read_odd.p1p2.diff__p1p2])
    return cost
dataset = tuning_interface.run_cross_entropy_sampler(
    populations = [20, 10, 10],
    cost_strategy = cost_strategy,
    select_frac = 0.25,
    plot_histograms = True,
    sampling_params_to_plot = [('t_ramp_init', 'read_detuning')]
)
dataset

Summary

In this tutorial, we introduced the GenericTuningInterface, a flexible framework for adaptive parameter optimization in quantum experiments.

By leveraging input streams, the interface enables dynamic parameter updates during execution, removing the need for predefined sweeps. This allows efficient exploration of high-dimensional and non-linear parameter spaces.

We demonstrated how to:

  • define a custom cost function via CostStrategy,

  • map high-level tuning parameters to underlying QUA variables,

  • execute parameter updates in real time,

  • and perform automated optimization using cross-entropy sampling.

The interface provides a scalable and extensible approach to experiment tuning, combining hardware-efficient execution with flexible software abstractions. It is particularly well suited for complex experiments where performance depends on many interdependent parameters.