How to debug a failed routine with Boulder Opal

Diagnose and resolve failed calibration by re-running experiments with adjusted parameters

A routine failure does not necessarily require a full re-run from scratch. Instead, lower-level experiments can be used to debug an automation error, allowing you to collect any missing data or components, before continuing with automations. This user guide assumes you have a valid Boulder Opal Scale Up client session and a completed (but failed or partially failed) routine execution. It walks through the workflow for isolating the specific experiment that failed, re-running it with adjusted parameters, and feeding the corrected result back into the device model.

In this notebook you will:

  1. Retrieve and inspect results from a failed routine.
  2. Identify the experiment that caused the failure.
  3. Re-run that experiment with wider sweep ranges or more shots.
  4. Update the virtual device with the corrected value and resume calibration.

You will need an authenticated client (see documentation specific to Qblox and Quantum Machines) and a device configuration YAML file for your quantum processor. If you are starting from scratch, complete the get started tutorial first.

1. Inspect the routine result

Every routine and experiment returns a job ID. Use client.get_job_data() to retrieve results, including error messages and diagnostic plots. This provides more detail than client.get_job_summary() and is the recommended starting point when diagnosing a failure.

The results show exactly what error occurred, and where it occurred in the routine process. This includes the error message and the affected component with specific experiment and data analysis context.

routine_job_id = "<your-failed-job-id>"

job_data = await client.get_job_data(routine_job_id)
client.display(job_data)

2. Identify the corresponding experiment

Each routine runs a collection of experiments. When an experiment fails, the Routine exits and returns the error message. You can use that information to re-run that specific experiment with customized parameters that the embedded state machine was unable to determine (such as wider sweep ranges or more shots). The table below maps each routine to its constituent experiments and lists common failure modes

RoutineExperiments it runsPotential failure modes
ResonatorMappingFeedlineDiscovery, ResonatorSpectroscopy, ResonatorSpectroscopyByBias, ResonatorSpectroscopyByPowerResonance not found — sweep range too narrow
TransmonDiscoveryTransmonSpectroscopy, PowerRabi, ReadoutClassifierQubit frequency not found — sweep range or drive power wrong
OneQubitCalibrationPowerRabi, DragLeakageCalibration, FineAmplitudeCalibration, Ramsey, ReadoutClassifierRabi fit failed — amplitude range too narrow
TransmonCoherenceT1, T2, T2EchoExponential fit failed — delay range does not capture the decay

Note: The JobData object returned by client.get_job_data() includes the component name, failure reason, and any diagnostic data.

3. Read the current device data

Before running an experiment manually, check the current state of the device model. A routine may have partially succeeded and updated some parameters before the failing step, so the stored values may already reflect earlier successful calibrations. In this example below, we assume a Routine was run on Qubit 0, so we are checking those related components to verify their latest data. This would be replaced with the affected components for a given failed routine.

device = await client.get_device_data()
await client.display_device_data_sheet()
target_transmon = "q0"
target_resonator = "r0"

transmon_info = device.qpu.get_component(target_transmon)
resonator_info = device.qpu.get_component(target_resonator)

transmon_info

4. Example: re-run resonator spectroscopy

If ResonatorMapping failed to find a resonance, you can run ResonatorSpectroscopy manually with a wider frequency range. Use CWSIterable to define a sweep centered on the last known frequency. Increasing the width parameter broadens the search window, and reducing the step parameter improves resolution.

from boulderopalscaleup.common import CWSIterable
from boulderopalscaleup.experiments import ResonatorSpectroscopy

await client.enable_qubits(target_transmon)

frequencies = CWSIterable(
    center=resonator_info.frequency_low.value, width=40e6, step=0.05e6
)

experiment = ResonatorSpectroscopy(
    resonator=target_resonator, frequencies=frequencies, shot_count=200
)

resonator_spectroscopy_job_id = await client.run(experiment)
result = await client.get_job_data(resonator_spectroscopy_job_id)
client.display(result)

5. Example: re-run transmon spectroscopy

If TransmonDiscovery failed to locate the qubit frequency, you can run TransmonSpectroscopy with a wider sweep and a higher shot count to improve signal-to-noise.

from boulderopalscaleup.experiments import TransmonSpectroscopy

frequencies = CWSIterable(center=transmon_info.freq_01.value, width=100e6, step=0.1e6)

experiment = TransmonSpectroscopy(
    transmon=target_transmon, frequencies=frequencies, shot_count=800
)

transmon_spectroscopy_job_id = await client.run(experiment)
result = await client.get_job_data(transmon_spectroscopy_job_id)
client.display(result)

6. Example: re-run power Rabi

If OneQubitCalibration failed at the Rabi step, you can run PowerRabi with a broader amplitude scale range. Passing scales=None lets the system choose a default range, or you can specify one explicitly with LinspaceIterable.

from boulderopalscaleup.common import LinspaceIterable
from boulderopalscaleup.experiments import PowerRabi

experiment = PowerRabi(
    transmon=target_transmon,
    scales=LinspaceIterable(start=0.0, stop=2.0, count=101),
    shot_count=800,
)

rabi_job_id = await client.run(experiment)
result = await client.get_job_data(rabi_job_id)
client.display(result)

7. Example: re-run T1

If TransmonCoherence failed to fit T1, the delay range may not capture the full exponential decay. Use GeomspaceIterable to span a wider range of delay times, ensuring the measurement window extends well beyond the expected T1 value.

from boulderopalscaleup.common import GeomspaceIterable
from boulderopalscaleup.experiments import T1

experiment = T1(
    transmon=target_transmon,
    delays_ns=GeomspaceIterable(start=16, stop=40_000, count=101),
    shot_count=800,
)

t1_job_id = await client.run(experiment)
result = await client.get_job_data(t1_job_id)
client.display(result)

8. Update the virtual device and resume

Once the individual experiment succeeds, you can manually update the device model with the corrected value and then re-run the full routine. This is useful when a routine fails partway through, as you can fix the blocking step and let the routine continue from an updated baseline. All update_component frequencies are in Hz and time constants in nanoseconds.

device = await client.get_device_data()

device.qpu.update_component(component_id=target_transmon, freq_01=5.4e9)
await client.update_device(device.qpu)

Verify that the updated value is reflected in the device model before re-running the routine.

await client.display_device_data_sheet(node_name=target_transmon)
from boulderopalscaleup.routines import TransmonDiscovery

retry_job_id = await client.run(TransmonDiscovery(transmon=[target_transmon]))

retry_result = await client.get_job_data(retry_job_id)
client.display(retry_result)

9. Troubleshooting tips

  • RuntimeError: No current device has been set occurs when no device has been selected. Call await client.set_current_device("<your-device-name>") before running experiments or routines.

  • KeyError when calling qpu.get_component(component_id) indicates that the component_id does not match a node in your device configuration (for example, "q0", "r0").

10. Next steps

Was this useful?

cta background

New to Boulder Opal?

Get access to everything you need to automate and optimize quantum hardware performance at scale.