Skip to content

Add qubit loss to GeneralNoiseModel - #1647

Open
ciaranra wants to merge 4 commits into
qubit-loss-1afrom
qubit-loss-1b
Open

ciaranra wants to merge 4 commits into
qubit-loss-1afrom
qubit-loss-1b

Conversation

@ciaranra

@ciaranra ciaranra commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Stacked on #1643, which is stacked on #1642; review those first. This is the second part of adding qubit loss to GeneralNoiseModel. It adds loss state, loss sources, recovery and partner spreading, and gives the lost readout rows from #1643 their meaning.

What changes

Loss is separate from leakage. A loss event resets the backend qubit with an injected PZ, marks the atom lost and clears its leakage mark. An atom is never both lost and leaked. Injected resets and recovery Paulis make no noise draws and do not change the prepared-qubit pool. Shot reset clears loss state but keeps the configuration.

Configuration (Rust QubitLoss::builder() / GeneralNoiseModelBuilder::with_loss / without_loss, JSON loss section, Python with_loss(dict), which converts the dict strictly to JSON and then uses the same parser):

  • Sources:
    • measurement: per operand, after each user measurement; the current readout is unchanged.
    • prep: per operand, after a preparation runs.
    • idle_rate: a rate, drawn as clamp(rate * duration, 0, 1) at each idle site before ordinary idle noise.
    • events channels at before_/after_single_qubit and before_/after_two_qubit sites: an outer probability, then one conditional label such as LI, IL, LL or a Pauli.
  • recovery channels: a lost atom draws the false-negative rate, a present one the false-positive rate. A detection resets the atom to the maximally mixed state. With default_recovery (on by default), a model with no listed recovery channel gets one before-two-qubit channel at 1e-3 / 1e-3.
  • lost_partner: at a two-qubit gate suppressed by an earlier loss, the surviving atom is lost with loss_probability (default 1), or else gets a Pauli from the optional pauli_model. So with {"enabled": true} alone, the present atom in such a pair is always lost too. A pair lost by that same command's own before-two-qubit events channel does not spread at that gate.
  • reload_on_prep: PZ and MPZ restore a lost atom.

Gates on lost atoms.

  • A lost operand suppresses its one-qubit gate or its two-qubit pair, including that gate's fault draws.
  • Custom counts as one operation: a lost operand left after the before-site recovery suppresses the whole command.
  • Tracked measurements always emit. Lost atoms read through the herald gates: MeasureZLost and MeasureZLeakedLost return 3. MZ, MPZ and MeasureZLeaked read a lost atom as 0, then apply the model's p_meas_0 flip (the non-matrix readout path). A configured lost readout row replaces that fallback.

Hook order. At a before-site: recovery, then the existing non-loss hooks, then loss events. At an after-site: loss entries, then non-loss hooks. Non-loss before-hooks skip groups that are still lost after recovery; after-hooks skip groups that are lost or whose gate was suppressed for loss. All loss state survives simulator round trips.

Scaling and validation.

  • scaling scales the measurement, prep, idle and event outer probabilities.
  • Conditional weights, recovery rates and lost_partner are not scaled.
  • Every probability must be in [0, 1] both before and after scaling. Error messages name the field, the channel index and the bad value. Python errors give the JSON key path.

Unsupported paths reject loss: RuntimeNoise / runtime frames and the Selene general-noise adapter say to remove the loss section; neo routing gives its existing beyond-the-simple-subset error, which says to use .stack(SimStack::Engines). Loss is outside the simple probability subset.

Docs. A "Qubit loss" section in docs/user-guide/noise-model-builders.md gives the semantics. It also has recipes for published loss models with numeric JSON instances:

  • Perrin et al. 2603.24237 and 2412.07841
  • Liu et al. 2603.04156
  • Baranes et al. 2502.20558
  • Bluvstein et al. 2506.20661
  • Nishio et al. 2607.29603

It notes that the chain loss in Coble et al. 2511.16632 cannot be expressed. The changelog has an entry.

Known limitations

Verification

  • Pre-commit on the changed files, just rstest, just python-ci-lint, just python-ci-core, just docs-test and the docs Rust crate test pass locally. Rust lanes ran pinned to the E-cores (taskset -c 16-31).
  • After the branch was brought up to date with dev, all of the lanes above were run again and pass. The last commit only quotes empty labels in weight-map errors; pre-commit, clippy and just rstest were run after it.
  • The invariance test from Add MeasureZLost and MeasureZLeakedLost herald gates and per-flavor readout matrices to GeneralNoiseModel #1643 passes against unchanged fixtures, so models without loss keep identical outcomes, commands and RNG use.
  • Tests:
    • 40 engine tests (crates/pecos-engines/tests/general_noise_loss.rs);
    • 5 statistical tests with Jeffreys intervals (crates/pecos/tests/general_noise_loss_statistics.rs);
    • 8 Python test functions, 21 cases (python/pecos-rslib/tests/test_general_noise_loss.py);
    • 7 unit tests in crates/pecos-engines/src/noise/general/loss.rs, including a check that the seven published-model numeric instances build without adding default recovery;
    • rejection tests in the Selene adapter (t20_qubit_loss_is_rejected) and neo routing (t20_neo_stack_rejects_qubit_loss).
  • The new tests were mutation-checked: removing or altering the behaviour each test guards makes it fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant