Skip to content

Repository files navigation

EEDT-BIOS

Qiskit Ecosystem CI License Python

Point it at a quantum computer and it tells you whether a two-qubit pair can be used as a clock — and gives you the numbers behind the answer.

Clock, here, means using the decay of an entangled pair as a timing reference: the pair is left idle for a chosen delay, and what has happened to it in that time is read out and corrected for. GPS is the number that says whether the correction helped — the fidelity of the corrected circuit minus the fidelity of an uncorrected one at the same delay. It is the papers' term for the quantity; positive means fidelity was recovered, and the verdict is GO when it stands more than three standard errors from zero.

$ eedt-bios --platform ibm

  Select backend:
    [1] ibm_kingston  [ibm]  queue=86
    [2] ibm_fez  [ibm]  queue=88
  choice> 1

  Select qubit pair:
    [1] Q149-Q150  T2=333us  CZ=0.0050  score=0.6
  choice> 1

  [calibration] Q149-Q150 on ibm_kingston
    sweep tau: [17, 53, 90, 127, 163, 200] us  + 8 Ramsey delays
      tau=  16.7us  F_ff=0.9355  F_ref=0.5430  GPS=+0.3926
      tau=  53.3us  F_ff=0.8516  F_ref=0.5625  GPS=+0.2891
      ...
    T1 from the reference curve: 410us
    nu_ZZ = -3.135kHz
    tau* = 88.6us
  GPS = +0.4316   z = +13.81
  boot mode: GO

What it is for

Choosing a pair and a delay. A 156-qubit processor has upwards of a hundred neighbouring pairs, and the useful idle time on each is a property of that pair on that day. Handed a backend, this ranks the candidates, calibrates one, and returns the delay to use — the input any feedforward experiment needs before it can start.

Finding out whether feedforward helps here at all. It is not a given. On 2026-07-27 the same code found a clear gain on IBM Heron (z up to 13.8 across five pairs) and nothing measurable on IQM Garnet (twenty-nine measurements pooling to zero). Two jobs tell you which situation you are in.

Measuring the ZZ coupling of the pair you are about to use. Neighbouring qubits shift each other's frequency; how much, and in which direction, is what sets the delay. Measured signed, in the same job as everything else, and withheld rather than guessed at when coherence is too short to resolve it.

Seeing the operating window. Where the correction helps on that pair, and where it stops. Hardware drifts — the same pair moved measurably inside half an hour during validation — so a window measured last month is not the one you have now.

Reproducing or extending the published work. Every run records the backend job id, the sweep, and the raw coupling measurement, so a result can be traced or re-examined without repeating it.

What you get

Two jobs on the backend, and three files you can keep.

file what is in it
eedt_bios_<stamp>.csv one row: the verdict, the operating point, the ZZ coupling, T1, and the backend job id
..._sweep.csv the delay sweep — where the correction helps on that pair, that day
..._ramsey.csv the raw coupling measurement, kept so a rejected fit can be re-examined

The headline number is GPS, and the verdict is GO when it is more than three standard errors from zero.

Install

Not published on PyPI. Install from source:

git clone https://github.com/okudat9/eedt-bios
cd eedt-bios

pip install -e ".[all]"      # both vendors
pip install -e ".[ibm]"      # IBM Runtime only
pip install -e ".[iqm]"      # IQM only

Add dev for the test dependencies: pip install -e ".[all,dev]".

pyproject.toml carries lower bounds so the tool stays installable alongside current Qiskit. The exact combination the results below were taken with is in requirements-lock.txt.

Credentials

Read from the environment; nothing is stored in the source.

# Windows PowerShell
$env:QSCRIPT_IBM_TOKEN = "..."
$env:IQM_TOKEN = "..."
# Linux / macOS
export QSCRIPT_IBM_TOKEN=...
export IQM_TOKEN=...

--ibm-token / --iqm-token override them.

Run it

Interactive — the tool lists what it found, with queue depth where the backend reports it, and you choose:

$ eedt-bios --platform ibm

  Select backend:
    [1] ibm_kingston  [ibm]  queue=86
    [2] ibm_fez  [ibm]  queue=88
    [3] ibm_marrakesh  [ibm]  queue=2340
  choice> 1

  Select qubit pair:
    [1] Q149-Q150  T2=333us  CZ=0.0050  score=0.6
    [2] Q125-Q126  T2=322us  CZ=0.0050  score=0.6
  choice> 1

Scripted:

eedt-bios --platform ibm --backend ibm_kingston --shots 512 --yes
eedt-bios --platform iqm --iqm-device garnet --clock-impl static --yes
eedt-bios --platform ibm --nu-zz 2.655 --yes      # known coupling, one job
eedt-bios --platform iqm --tau-star 10 --yes      # known delay, one job
flag meaning
--platform {auto,ibm,iqm} vendor; auto follows whichever token is set
--backend NAME a specific IBM backend; otherwise the account is enumerated
--iqm-device NAME a specific IQM machine (garnet, emerald, sirius)
--clock-impl {auto,dynamic,static} force the correction
--shots N shots for the clock run (default 256)
--nu-zz KHZ known coupling; skips the Ramsey scan
--tau-star US known delay; skips calibration entirely
--measure-nu / --no-measure-nu force the coupling measurement on or off
--zero-baseline also run tau = 0 and report the decay cost
--output-dir DIR where the CSVs go (default results/)
--no-csv skip writing files
--yes, -y non-interactive: first backend, best-ranked pair
--version print the version and exit

Words used here

Bell state the two qubits prepared as (|00> + |11>)/sqrt(2) — correlated, and decaying
T1 / T2 how long a qubit keeps its energy / its phase. Microseconds to a millisecond on current hardware
ZZ coupling, nu_ZZ neighbouring qubits shift each other's frequency. That shift, in kHz. Usually negative on fixed-frequency transmons
tau* the delay the clock is run at. Chosen from nu_ZZ, or from the sweep
phi* 0.873 rad. The phase the pair should have accumulated at tau*. An operating set-point that held across the hardware it was measured on — not a claimed constant of nature
MCM mid-circuit measurement: reading one qubit part-way through and acting on the result
GPS F_ff − F_ref: the fidelity gained by correcting, at that delay
z GPS divided by its standard error. How many error bars from zero

How it works

The problem. Neighbouring qubits are always slightly coupled — a "ZZ" interaction, so called because it acts on both qubits' Z axes. Leave an entangled pair idle and that coupling winds phase onto it, while decoherence takes the phase apart. Standard practice is to fight the coupling. The Bell Decay Clock reads it instead.

Why decay, not phase. A conventional ZZ clock recovers the accumulated phase, which only works while the off-diagonal elements of the density matrix survive. On a machine with dephasing-type coupling, or one whose mid-circuit measurement is slower than its coherence budget, they do not. Populations — the diagonal elements — outlive them, so a clock built on the differential decay of Bell-state populations keeps working after the phase is gone.

What gets measured.

GPS = F_ff(tau) - F_ref(tau)

F_ff is the fidelity of the corrected circuit, F_ref of the uncorrected one, both at the same delay on the same pair, both measured on every run. GPS > 0 means the correction recovered fidelity that decay had taken away.

z = GPS / SE          SE = sqrt(0.5 / shots)

The reference is not optional. Comparing against a tau = 0 circuit would mostly measure how much the state decayed rather than what the correction did; that circuit is available separately as --zero-baseline.

Two corrections, and the hardware picks. Not every backend compiles control flow, and one that does may still do better without it — a slow mid-circuit measurement eats the coherence it was meant to exploit. The tool reads backend.target.operation_names and builds what the machine can run. --clock-impl overrides it.

Each pairs a corrected circuit with a reference that differs only by the correction. Both run at the same delay, in the same job.

The four circuits

dynamic static
correction measure q_bell mid-circuit, act on q_sys unconditional Rz(-phi*) on q_bell
readout q_sys only both qubits
fidelity P(q_sys = 0) P(00) + P(11)
requires if_else in the target r, cz, delay, measure
cost the mid-circuit measurement takes time out of the coherence q_sys has left nothing spent waiting

The static rotation is fixed at phi* rather than fitted per delay, so it matches the accumulated phase exactly only at the delay phi* implies.

The two measure different observables. Compare GPS only within the same clock_impl; every row records which was used.

What a run consists of. Two jobs. The first sweeps the delay and, where coherence allows, measures the coupling alongside — one job, because queue time dominates everything else on a shared machine. The second runs the clock at the chosen delay.

How the operating point is chosen

The Bell-state relative phase advances at half the bare ZZ rate, so phi = pi * nu_ZZ * tau, and

tau* = phi* / (pi * nu_ZZ)          phi* = 0.873 rad

Every constant in the source says where it came from. The ones established in the papers carry a pointer to the DOI; the rest — the gates, the sampling, the thresholds — carry their reasoning in full, because there is nowhere else to look it up.

phi* is an operating set-point, reported as phi_ff_rad so it is never mistaken for a measurement. Where it came from, and how far it travels between machines, is a question for the papers rather than for this tool; here it is simply the number the delay is derived from, and --tau-star overrides it.

nu_ZZ is measured alongside the sweep but never used to choose tau* unless it passes its checks — amplitude, residual against the shot-noise floor, and significance against its own Cramér-Rao uncertainty. A coupling that cannot be resolved costs a reported number and nothing else; the delay then comes from the sweep. A delay past half the coherence budget is refused outright, because relaxation alone lifts the reference there and leaves the correction nothing to recover.

Only coupling-map neighbours are offered, and the chosen pair is pinned as an explicit initial layout — without that the transpiler picks its own qubits and every pair returns the same numbers.

How many jobs

Queue time dominates on a shared machine, so everything that can share a job does.

circuits jobs
calibration, with the coupling measured 44 1
calibration, coupling skipped 12 1
clock run 2 (3 with --zero-baseline) 1
--nu-zz or --tau-star 2 1 total

The coupling measurement is skipped by default below a T2 of 100 µs: frequency precision comes from the length of the record, the record cannot outlast coherence, and on a 30 µs machine the uncertainty lands near 7 kHz — larger than the couplings being looked for. --measure-nu forces it anyway.

Output

Written to results/YYYYMMDD/, each with a commented legend so a file read months later explains itself.

eedt_bios_<stamp>.csv — one row: the result and everything that produced it.

What happened

column meaning
boot_mode GO (z ≥ 3), DEGRADED (z ≥ 2), NO-GO
bios_version, timestamp which version wrote this, and when
backend, platform, q_sys, q_bell the machine and the pair. q_sys carries the amplitude and is corrected; q_bell is measured or rotated
job_ids backend job id(s) — the row can be traced on the provider

The result

column meaning
F_ff, F_ref fidelity with and without the correction, same delay, same pair
GPS F_ff − F_ref. Positive means the correction recovered fidelity
z, SE, shots significance, its standard error, and the shot count behind it
fidelity_def which observable F_ff and F_ref are — P(q0=0) or P(00)+P(11)

How it was measured

column meaning
clock_impl dynamic (conditioned on a mid-circuit measurement) or static (unconditional). Different observables — do not compare GPS across them
correction mcm_cond_x, qfeed_plus, or static_rz
tau_star_us the delay the clock ran at
tau_star_source swept (peak of the sweep), from_measured_nu, swept_fallback, supplied, or from_nu
phi_ff_rad the operating set-point, 0.873 rad. Not a measurement
N_run mid-circuit rounds the coherence budget allows

The coupling

column meaning
nu_ZZ_khz the measured coupling, signed. Blank when it could not be resolved
nu_ZZ_source measured, supplied, or why it was refused: not_significant, poor_fit, implausible_contrast, out_of_range, fit_failed
nu_ZZ_code_khz nu_ZZ / 2 — the rate the Bell relative phase advances at
tau_star_from_nu_us phi*/(pi·nu_ZZ). Compare against tau_star_us: agreement checks the relation, disagreement is worth a look
ramsey_f0_khz, ramsey_f1_khz the two fitted fringe frequencies, partner in |0> and |1>. Their difference is nu_ZZ
ramsey_amp_min, ramsey_amp_max, ramsey_resid_max fit diagnostics, written whether or not the coupling was accepted, so a refusal can be understood

The pair

column meaning
T2_sys_us, T2_bell_us as the backend reported them at discovery. A placeholder on IQM, which does not publish them
cz_error entangling-gate error for the edge, where the backend reports it
ref_decay_us measured from the reference across the sweep. T1 for dynamic, Bell decay time for static
F_ref_zero, decoh_drop the tau = 0 baseline and F_ref_zero − F_ref. Blank unless --zero-baseline. The sign follows the readout: negative for dynamic, positive for static

..._sweep.csv — one row per swept delay: tau_us, F_ff, F_ref, GPS, and chosen marking the delay the clock then ran at. This is the operating window of that pair, that day.

..._ramsey.csv — the raw coupling measurement: tau_us and P(q_sys = 0) with the partner in |0> and |1>, each in two quadratures (X_partner_0, Y_partner_0, X_partner_1, Y_partner_1). Kept raw so a rejected fit can be re-examined without going back to the hardware.

Verified

Measured on 2026-07-27. Job ids are on IBM Quantum Platform and IQM Resonance.

IBM Heron r2 (ibm_kingston) — the clock works.

pair tau* GPS z verdict job
Q149–Q150 16.7 µs +0.4316 +13.81 GO d9jj3i3jf64c739gt8s0
Q57–Q67 41.0 µs +0.3105 +9.94 GO d9jia63hdfks73cib3hg
Q125–Q126 42.6 µs +0.2930 +9.38 GO d9ji8kbhdfks73cib1h0
Q126–Q127 16.1 µs +0.2461 +7.88 GO d9jiosqbr2fc73e61il0
Q50–Q51 57.0 µs +0.1797 +5.75 GO d9jii7abr2fc73e614m0

The coupling measurement resolved on Q126–Q127 at −3.13 kHz — negative, as expected for fixed-frequency transmons — from fringes at −9.16 and −12.29 kHz. That raw data is a regression test in the suite.

IQM Garnet — no gain measurable that day. Twenty-nine static measurements across four pairs pool to GPS = −0.0065 ± 0.0048, a 95% interval of [−0.0159, +0.0028], with scatter consistent with shot noise alone. The tool reported NO-GO throughout rather than manufacturing a result. What a different session, a different pair, or a differently built circuit would give is not something this run can say.

Garnet does compile control flow, contrary to what its public fake backend suggests, so clock_impl came out dynamic until forced otherwise. Compiling and working are different things: the conditioned correction did no better there than the unconditional one.

Not verified

Stated plainly so you know which parts have never met hardware.

  • qfeed_plus — the cumulative-phase correction. Its condition (N_run == 0) never arose on the machines tested, so the circuit has not run. Reading it afterwards found it paired with the wrong reference: it leaves the pair unentangled, and the Bell reference it was compared against would have shown half a unit of gain regardless. Corrected and covered by a test, but still unmeasured.
  • --zero-baseline — the tau = 0 diagnostic.
  • IQM Emerald and Sirius — Emerald connected and listed a different coupling map, but no run completed; Sirius was never reached.
  • --yes, --no-csv, --output-dir — exercised by tests, not on hardware.
  • A backend that reports no T2qubit_properties returning None is handled, but has not been seen.

Test coverage is 70%. The uncovered lines are almost entirely the vendor transport layer and the interactive CLI, which need a real backend to reach.

Reporting a problem

Every run writes three files and records the backend job id in each. Sending results/<date>/eedt_bios_<stamp>*.csv is usually enough to diagnose a failure without repeating the measurement — that is what the raw sweep and Ramsey files are for, and the fit diagnostics are written whether or not the fit was accepted.

Troubleshooting

Every one of these came up while validating the tool on 2026-07-27.

Authentication parameters given both as initialisation args and as environment variables — the IQM client refuses a token supplied twice, and having IQM_TOKEN set while also passing --iqm-token counts as twice. The tool now routes the token through the environment either way, so this should not reach you; if it does, unset one source.

The IQM Server URL must not contain the quantum computer name — older deployments took the machine in the URL path (.../garnet). The client now wants a base URL and the machine named separately. Clear any IQM_SERVER_URL left over from an older setup:

set IQM_SERVER_URL=

520 is not a valid HTTPStatus — a fault at the server, not in the circuits or the token; the client raises on a status it does not recognise. Usually worth retrying. The tool does not retry by itself, because a job that failed after submission may still have been charged.

Not enough credits — the job was declined and nothing ran. Reduce the work rather than the shots: --no-measure-nu drops the calibration from 44 circuits to 12, and --tau-star US skips calibration altogether, leaving one job of two circuits.

Every pair returns the same numbers — on IBM this means the transpiler is choosing its own qubits. Fixed here by pinning an explicit initial layout; if you see it in your own code, that is where to look.

nu_ZZ not resolved — the reason is recorded rather than a doubtful number. not_significant means the difference was smaller than its own uncertainty, which on a short-T2 machine is the normal outcome and is why the measurement is skipped there by default. poor_fit and implausible_contrast point at the fringe itself; the raw data is in ..._ramsey.csv and the fitted frequencies, amplitudes and residuals are in the result row, so the refusal can be examined without measuring again.

A delay far longer than expected — refused if it exceeds half the coherence budget. Relaxation alone lifts the reference at such delays, so a correction measured there has nothing left to recover. The tool falls back to the swept peak and says so.

Queue waits — shown in the backend list where the vendor reports them. On 2026-07-27 ibm_kingston had 86 jobs waiting and ibm_marrakesh had 2340.

pytest picks up the wrong copy — if a stale eedt_bios.py sits in a parent directory it can shadow the one under test. The header line of a full pytest run prints the path actually imported.

Tests

pytest -q

Runs without hardware. Circuit construction, both fidelity estimators, pair discovery, calibration, the runner, the gates and CSV output are covered with a fake adapter; a further set compiles circuits against vendor fake backends, and several are regressions built from recorded hardware data. Backend-specific tests skip cleanly when a vendor SDK is absent.

Related

nu_zz_map_iqm maps signed ZZ coupling across a device with mandatory negative controls — it answers where the coupling is. This answers how to run a clock on it.

Citing

Okuda, T. Bell Decay Clock: Extracting Useful Information from Decoherence via Mid-Circuit Measurement Feedforward. Zenodo, 2026. doi:10.5281/zenodo.19367565

Supporting theory:

Okuda, T. Phase-Invariant Condition for Optimal Feedforward in ZZ-Coupled Quantum Systems. Zenodo, 2026. doi:10.5281/zenodo.19593677

Machine-readable metadata: CITATION.cff.

License

Apache-2.0. See LICENSE.

About

Boot a Bell Decay Clock on NISQ superconducting processors

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages