Skip to content

GeneralNoiseModel drops every pair of a batched two-qubit command when any one qubit is leaked #792

Description

@ciaranra

Summary

In GeneralNoiseModel, a two-qubit command that carries several pairs is dropped in its entirety when any one qubit in the command is leaked. Pairs that involve no leaked qubit lose both their gate and their noise, silently.

Cause

apply_tq_faults in crates/pecos-engines/src/noise/general.rs (around line 1194) loops over pairs with gate.qubits.as_chunks::<2>(), but inside that loop has_leakage is computed from gate.qubits, the whole command, rather than from the current pair:

for qubits in gate.qubits.as_chunks::<2>().0 {
    ...
    let has_leakage = !self.leaked_qubits.is_empty()
        && gate.qubits.iter().any(|&qubit| self.is_leaked(usize::from(qubit)));

The seepage branch a few lines below has the same shape: it iterates &gate.qubits rather than the current pair, so seepage is attempted once per pair for every leaked qubit in the command.

Reproduction

Leak qubit 0 deterministically, then send one command with two pairs, only the first of which touches the leaked qubit:

use pecos_engines::byte_message::ByteMessage;
use pecos_engines::engine_system::{ControlEngine, EngineStage};
use pecos_engines::noise::GeneralNoiseModel;

let mut noise = GeneralNoiseModel::builder()
    .with_p_prep(1.0).with_prep_leak_ratio(1.0)
    .with_p1(0.0).with_p2(0.0).with_p_meas_0(0.0).with_p_meas_1(0.0)
    .with_seed(7)
    .build();

let mut b = ByteMessage::quantum_operations_builder();
b.pz(&[0]);                       // qubit 0 leaks
noise.start(b.build()).unwrap();

let mut b = ByteMessage::quantum_operations_builder();
b.cx(&[(0, 1), (2, 3)]);
let EngineStage::NeedsProcessing(out) = noise.start(b.build()).unwrap() else { panic!() };
println!("{:?}", out.quantum_ops().unwrap());   // prints []

Observed: the output message contains no gates. CX(2, 3) is gone even though neither qubit is leaked.

Control: sending b.cx(&[(2, 3)]) alone to the same model afterwards emits CX [2, 3] as expected, so the loss comes from sharing a command with the leaked pair.

Expected: CX(0, 1) is suppressed because qubit 0 is leaked; CX(2, 3) is emitted and receives its own two-qubit noise draw.

Scope

  • Affects every two-qubit gate type handled by GeneralNoiseModel, only when leakage is enabled and a command carries more than one pair.
  • The fix is to evaluate leakage, and the seepage loop, over the current pair.
  • A regression test should cover a mixed command (one leaked pair, one healthy pair) and assert the healthy gate survives.

Provenance

Found during an AI-assisted review of #788 and confirmed as pre-existing and unrelated to that PR. Reproduced by execution against code identical to dev for this file.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrustPull requests that update rust codeseverity:criticalSilently wrong results, data loss, or security exposure

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions