Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions crates/pecos-engines/src/noise/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ use pecos_core::QubitId;
use pecos_core::errors::PecosError;
use rand_chacha::ChaCha8Rng;
use std::any::Any;
use std::collections::HashSet;
use std::collections::{HashSet, BTreeSet};

/// General noise model implementation that includes parameterized error channels for various quantum operations
///
Expand Down Expand Up @@ -326,6 +326,17 @@ pub struct GeneralNoiseModel {
/// Random number generator for stochastic noise processes
rng: NoiseRng<ChaCha8Rng>,

/// Set of qubits that have been initialized at any point in the program.
///
/// This is so that we know which qubits exists and we can apply crosstalk
/// to them. Qubits that are measured / discarded are not removed from here, since
/// PECOS does not assume measurements are destructive. This should not cause a
/// problem, since inactive qubits suffering error have no effect on the state,
/// and active qubits should always suffer errors under this naive crosstalk model.
///
/// Using a BTreeSet because we will iterate over the qubits and we want determinism.
initialized_qubits: BTreeSet<usize>,

/// Track which qubits are being measured in the current batch and their gate types
/// This is needed to properly handle leakage during measurements
/// Each entry is (`qubit_id`, `is_measure_leaked`)
Expand Down Expand Up @@ -503,9 +514,11 @@ impl GeneralNoiseModel {
);
}
GateType::Prep => {
for &q in &gate.qubits {
self.initialized_qubits.insert(usize::from(q));
}
self.apply_prep_faults(&gate, &mut builder);

// TODO: Implement prep crosstalk when needed
self.apply_crosstalk_faults(&gate, self.p_prep_crosstalk, &mut builder);
}
GateType::Measure | GateType::MeasureLeaked => {
// Track which qubits are being measured for leakage handling
Expand All @@ -518,6 +531,7 @@ impl GeneralNoiseModel {
// Measurement noise is handled in apply_noise_on_continue_processing
// We still need to add the original gate here
builder.add_gate_command(&gate);
self.apply_crosstalk_faults(&gate, self.p_meas_crosstalk, &mut builder);
}
GateType::I => {
let err_msg = format!(
Expand Down Expand Up @@ -783,6 +797,39 @@ impl GeneralNoiseModel {
}
}

/// Apply crosstalk noise
///
/// Naive crosstalk noise model:
/// 1. All qubits in the trap but the ones in the `gate` are subject to crosstalk
// error. The `gate` should be either qubit measurement or initialization.
// 2. *Each* qubit not in `gate` has the given `probability` to suffer an error.
/// 3. Affected qubits are collapsed into the computational basis (Z measurement).
///
/// In ion trap systems, this could represent scattered light during optical pumping
/// affecting neighboring ions.
pub fn apply_crosstalk_faults(
&mut self,
gate: &Gate,
probability: f64,
builder: &mut ByteMessageBuilder
) {
let mut affected_qubits = Vec::new();
let gate_qubits: Vec<usize> = gate.qubits.iter().map(|q| usize::from(*q)).collect();

for q in self.initialized_qubits.clone() {
if !gate_qubits.contains(&q) {
if self.rng.occurs(probability) {
affected_qubits.push(q);
}
}
}
// TODO potentially major issue: I expect these will be adding measurement
// outcomes to the OutputEngine, which should be ignored. How do we do this?
// Perhaps there should be another add_collapse() method that applies a
// measurement and discards the outcome.
builder.add_measurements(&affected_qubits);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment from Ciaran on the commit:

dcc5fb7#r164014518

Hmmmm, let's see. Measurements end up kinda of being two rounds... First the noise model sends a measurement request to the simulator and then noise model gets a result from the simulator. Then then noise model can intercept/forward/etc. results to the "classical engine". This is done here:

PECOS/crates/pecos-engines/src/noise/general.rs

Line 362 in 55994b8

 fn continue_processing( 

I kinda of had to deal with a similar situation of distinguishing measurement results for leakage... and I ended up just storing the measurement type here:

PECOS/crates/pecos-engines/src/noise/general.rs

Line 332 in 55994b8

 measured_qubits: Vec<(usize, bool)>, 

But I feel like what needs to be done is to add some tagging mechanism in ByteMessage

I might... try adding result tagging to ByteMessage...

Copy link
Collaborator Author

@PabloAndresCQ PabloAndresCQ Aug 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think I understand. Without tagging in ByteMessage, what I could do is add an extra bool to measured_qubits, so that's Vec<(usize, bool, bool)> where the second bool corresponds to is_crosstalk to indicate its origin.

For the naive crosstalk, I'd just throw away the measurement outcome at apply_noise_on_continue_processing here whenever is_crosstalk is True. In future versions, it could be used to make a decision on how to apply crosstalk.

That makes sense, I can do it tomorrow morning. I guess there's no need for tags for the first version, but if you get them in, I'll use them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, sounds good. Later I can add a tag system and replace that.

}

/// Apply single-qubit gate noise faults
///
/// Models errors that occur during single-qubit gate operations:
Expand Down
3 changes: 2 additions & 1 deletion crates/pecos-engines/src/noise/general/default.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::noise::{
GeneralNoiseModel, NoiseRng, SingleQubitWeightedSampler, TwoQubitWeightedSampler,
};
use std::collections::{BTreeMap, HashSet};
use std::collections::{BTreeMap, HashSet, BTreeSet};

impl Default for GeneralNoiseModel {
/// Create a new noise model with default error parameters
Expand Down Expand Up @@ -100,6 +100,7 @@ impl Default for GeneralNoiseModel {
p2_idle: 0.0,
leaked_qubits: HashSet::new(),
rng: NoiseRng::default(),
initialized_qubits: BTreeSet::new(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment from Ciaran in the commit:

dcc5fb7#commitcomment-164014201

Would prefer initialized -> prep or prepared or something

Note: crosstalk for measurement and prep/reset/init can look differently. But can change that later.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, happy to change that.

measured_qubits: Vec::new(),
p_meas_crosstalk: 0.0,
p_prep_crosstalk: 0.0,
Expand Down
Loading