Erado Basic Simulator¶
Erado Basic is a simulator for erasure noise: it does not model other noise types such as depolarising or dephasing noise. Erasure noise converts a qubit into a distinct, checkable “lost” state rather than flipping or randomising it, which changes how error mitigation needs to be approached. Erado Basic lets you validate programs and mitigation strategies against this noise model ahead of running on erasure-prone hardware. It runs on a Qiskit AerSimulator backend using the statevector method: an exact, full-state simulation rather than an approximation. For the underlying theory, see Background in the Erado documentation.
Task submission¶
QCaaS provides a 16-qubit Erado Basic simulator available via QPU ID qpu:uk:4:a63d17f4a0.
Only OpenQASM 2.0 programs are currently supported.
Results are always returned in binary count format (see Task results, or the
Examples below, for what that looks like). Task submission, monitoring, and result
retrieval otherwise follow the same patterns as for QPU tasks; refer to
Submitting and managing tasks.
Note
EradoConfig requires qcaas-client version 3.23.0 or later; see
oqc-qcaas-client on PyPI for the latest
release, and Installation for how to upgrade.
Task configuration¶
Erado Basic tasks use EradoConfig in place of CompilerConfig. Erasure-noise simulation
needs its own controls, such as which noise model to run and how erasures are detected and
handled, that don’t map onto standard QPU compiler settings.
Note
Submitting a CompilerConfig instead of an EradoConfig still works: only repeats
(shot count) carries over, and the task runs as a noiseless simulation with
erasure_rate=0.0 (the current default; may vary by deployment). Tket optimisations, error
mitigation, and other CompilerConfig settings do not apply to Erado Basic and are
ignored.
EradoConfig is a dataclass, so a configuration can be built and checked without a live
connection:
from qcaas_client.client import EradoConfig, ErasureModel
config = EradoConfig(
repeats=1000,
erasure_rate=0.01,
erasure_model=ErasureModel.TRANSPILER_PASS,
)
config.validate() # raises ValueError for out-of-range parameters
The full set of parameters is:
Parameter |
Description |
Notes |
|---|---|---|
|
Number of shots. |
Type:
int (>= 1)Default:
1000 |
|
Per-gate probability that a gate causes an erasure; if it fires, every qubit that gate acts on becomes erased. Total erasure incidence therefore compounds with the number of erasable gates in your circuit, not just circuit depth. |
Type:
float (0-1)Default:
0.5. This is high (50%); most workloads will want a lower value.When
post_selection=True, values above 0.01 are currently rejected to avoid
excessive shot-rejection overhead. |
|
When |
Type:
boolDefault:
False |
|
Simulation method. |
Type:
ErasureModelDefault:
CIRCUIT_SAMPLERCIRCUIT_SAMPLER is more memory-hungry at scale; prefer TRANSPILER_PASS for
larger circuits. |
|
When |
Type:
boolDefault:
False |
|
Return per-shot circuit fidelity alongside results. Not currently supported; |
Type:
boolDefault:
False |
|
Rate at which erasure checks incorrectly signal an erasure. |
Type:
float (0-1)Default:
0.0 |
|
Rate at which erasure checks miss a true erasure. This is what limits how effective
|
Type:
float (0-1)Default:
0.0 |
|
An optional |
Warning
With post_selection=True, erasure_rate values above 0.01 are currently rejected to
avoid excessive shot-rejection overhead. This is easy to hit by accident: raising
erasure_rate on a working post_selection=True task, for example to make erasures more
visible, will cause the task to fail.
Idling error models the noise a qubit accumulates while it sits idle between gates: idle periods
are padded with extra gates, which become additional erasable positions. When set,
idling_error takes an IdlingErrorConfig instance with the following fields; see
Idling error in
the Erado documentation for the underlying mechanism and modelling rationale.
Parameter |
Description |
Notes |
|---|---|---|
|
Maximum number of idle gates inserted into an idle period. |
Type:
intDefault:
14Values below 2 insert no idle gates.
|
|
Qiskit gate inserted during idle periods. |
Type:
str, one of "id", "x", "y", "z"Default:
"id" |
|
Unit duration assigned to each existing circuit gate. |
Type:
float (> 0)Arbitrary relative units, not physical time.
Default:
1.0 |
|
Unit duration assigned to each inserted idle gate. |
Type:
float (> 0)Arbitrary relative units, not physical time.
Default:
0.8 |
|
Minimum ratio between an idle period and the inserted gate sequence length. |
Type:
float (>= 0)Default:
1.0 |
Examples¶
The following examples build up from a noiseless baseline to erasure with and without post-selection, then to configuring idling error, all using the same 5-qubit GHZ circuit. The counts and metrics shown are taken from real runs against the simulator.
Noiseless baseline¶
from qcaas_client.client import (
OQCClient, QPUTask, EradoConfig, ErasureModel, IdlingErrorConfig,
)
client = OQCClient(
url=<oqc_cloud_url>,
authentication_token=<access_token>
)
ghz = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[5];
creg c[5];
h q[0];
cx q[0], q[1];
cx q[1], q[2];
cx q[2], q[3];
cx q[3], q[4];
measure q -> c;
"""
config = EradoConfig(repeats=2000, erasure_rate=0.0)
task = QPUTask(program=ghz, config=config, qpu_id="qpu:uk:4:a63d17f4a0")
result = client.execute_tasks(task)[0]
if result.has_errored():
raise RuntimeError(result.error_details.error_message)
print(result.result)
# {'c': {'00000': 1002, '11111': 998}}
With erasure_rate=0.0, Erado Basic behaves like any other noiseless Qiskit simulator:
results split close to evenly between the GHZ state’s two ideal outcomes, with the usual shot
noise you’d see from any simulator or real device. The remaining examples on this page omit the
has_errored() check shown above for brevity; see Task error for more information if a
task fails.
Erasure without post-selection¶
Enabling erasure with post_selection left at its default (False) includes corrupted
measurements in the results with no indication that anything went wrong:
config = EradoConfig(
repeats=2000,
erasure_rate=0.01,
erasure_model=ErasureModel.CIRCUIT_SAMPLER,
)
task = QPUTask(program=ghz, config=config, qpu_id="qpu:uk:4:a63d17f4a0")
result = client.execute_tasks(task, include_metrics=True)[0]
print(result.result)
# {'c': {'00000': 1005, '10000': 14, '11000': 12, '11100': 11, '11110': 14, '11111': 944}}
metrics = result.metrics
for key in ("n_accepted", "n_rejected", "rejection_rate"):
print(f"{key}: {metrics[key]}")
# n_accepted: 2000
# n_rejected: 111
# rejection_rate: 0.0555
Note
erasure_model is set explicitly here to show the syntax, even though CIRCUIT_SAMPLER
is also the default.
Outcomes other than 00000 and 11111 are shots where an erasure cut the run of cx
gates short: entanglement never reached the remaining qubits, so those qubits measured 0.
Nothing in result.result marks these corrupted outcomes as such. Note that n_rejected
and rejection_rate are non-zero even though no shots were discarded: with
post_selection=False they report erasure incidence, not actual rejections, and
n_accepted still equals the full repeats.
Erasure with post-selection¶
Setting post_selection=True discards those corrupted shots and re-runs until repeats
accepted shots are collected:
config = EradoConfig(repeats=2000, erasure_rate=0.01, post_selection=True)
task = QPUTask(program=ghz, config=config, qpu_id="qpu:uk:4:a63d17f4a0")
result = client.execute_tasks(task, include_metrics=True)[0]
print(result.result)
# {'c': {'00000': 1011, '11111': 989}}
metrics = result.metrics
for key in ("shots", "n_accepted", "n_rejected"):
print(f"{key}: {metrics[key]}")
# shots: 2115
# n_accepted: 2000
# n_rejected: 115
Results are clean again: only 00000 and 11111 remain. shots (2115) now exceeds
repeats (2000): the extra shots were simulated to replace the rejected ones, and retries are
uncapped, bounded only by the task’s execution timeout. For circuits or erasure rates where that
retry cost could be significant, prefer schedule_tasks with polling over the blocking
execute_tasks used in these examples, the same recommendation as for other potentially
long-running QPU tasks; see Task status.
Post-selection cost¶
Because post_selection=True re-runs every rejected shot, its overhead grows with
erasure_rate:
for rate in (0.002, 0.005, 0.01):
config = EradoConfig(repeats=2000, erasure_rate=rate, post_selection=True)
task = QPUTask(program=ghz, config=config, qpu_id="qpu:uk:4:a63d17f4a0")
result = client.execute_tasks(task, include_metrics=True)[0]
print(f"{rate}: {result.metrics['shots']}")
# 0.002: 2016
# 0.005: 2046
# 0.01: 2115
At erasure_rate=0.01 the overhead here is modest (under 6%), but this assumes accurate
erasure detection: see Detection accuracy: false_positive_rate and false_negative_rate below for what happens when it isn’t.
Detection accuracy: false_positive_rate and false_negative_rate¶
false_positive_rate and false_negative_rate model imperfect erasure detection, and
affect post_selection=True in two different ways. This compares the same
erasure_rate=0.01 run above against a high false_negative_rate and a high
false_positive_rate (values chosen to make the effect visible; real detector error rates are
typically much lower):
scenarios = {
"clean": dict(false_positive_rate=0.0, false_negative_rate=0.0),
"high false_negative_rate": dict(false_negative_rate=0.5),
"high false_positive_rate": dict(false_positive_rate=0.1),
}
for name, rates in scenarios.items():
config = EradoConfig(
repeats=2000, erasure_rate=0.01, post_selection=True, **rates
)
task = QPUTask(program=ghz, config=config, qpu_id="qpu:uk:4:a63d17f4a0")
result = client.execute_tasks(task, include_metrics=True)[0]
counts = result.result["c"]
leaked = sum(n for o, n in counts.items() if o not in ("00000", "11111"))
print(name, "- shots:", result.metrics["shots"], "leaked outcomes:", leaked)
# clean - shots: 2115 leaked outcomes: 0
# high false_negative_rate - shots: 2079 leaked outcomes: 21
# high false_positive_rate - shots: 3628 leaked outcomes: 0
With false_negative_rate=0.5, a small number of shots (21 out of 2000 here) leak through
post_selection=True with corrupted outcomes: the erasure genuinely happened but wasn’t
detected, so it was never rejected. This is exactly the limitation described in
false_negative_rate’s row above: post-selection can only discard what it detects. With
false_positive_rate=0.1 instead, the results stay perfectly clean (only 00000 and
11111), but shots jumps to 3628 (an 81% overhead, versus under 6% with no false
positives):
clean shots are being incorrectly flagged and discarded too, at real cost but no accuracy loss.
Only the false_negative_rate series has any counts at the corrupted outcomes; clean and
false_positive_rate sit exactly on top of each other at 00000 and 11111, which is
the visual signature of “costs more, but doesn’t corrupt results.”
Idling error¶
Configuring idling_error adds extra erasable gate positions during idle periods:
config = EradoConfig(
repeats=2000,
erasure_rate=0.01,
idling_error=IdlingErrorConfig(),
)
task = QPUTask(program=ghz, config=config, qpu_id="qpu:uk:4:a63d17f4a0")
result = client.execute_tasks(task, include_metrics=True)[0]
metrics = result.metrics
for key in ("n_erasable_gates", "circuit_depth"):
print(f"{key}: {metrics[key]}")
# n_erasable_gates: 17
# circuit_depth: 6
Compare this to n_erasable_gates=5 (and the same circuit_depth=6) for the identical
circuit without idling_error (the earlier examples above): padding more than triples the
number of erasable gate positions here without adding depth, since the scheduler fills idle time
already present within existing layers rather than adding new ones. More erasable gates at the
same erasure_rate means more chances for an erasure. idling_error can be combined with
post_selection=True in the same way as the plain-erasure example above.
Most of the corrupted outcomes are visibly taller with idling_error enabled, tracking the
roughly 3x jump in n_erasable_gates shown above.