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.
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_faultsincrates/pecos-engines/src/noise/general.rs(around line 1194) loops over pairs withgate.qubits.as_chunks::<2>(), but inside that loophas_leakageis computed fromgate.qubits, the whole command, rather than from the current pair:The seepage branch a few lines below has the same shape: it iterates
&gate.qubitsrather 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:
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 emitsCX [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
GeneralNoiseModel, only when leakage is enabled and a command carries more than one pair.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
devfor this file.