Skip to content

Commit aa80671

Browse files
Naive crosstalk (#175)
* Basic prototype. I think there's a major issue, I have not tested yet. * Now discarding outcomes from crosstalk projections * Changed nomenclature from initialization to preparation * I forgot to change initialized_qubits to prepared_qubits in default.rs * Adding with_average methods for crosstalk
1 parent 780dca3 commit aa80671

File tree

3 files changed

+222
-15
lines changed

3 files changed

+222
-15
lines changed

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

Lines changed: 202 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ use pecos_core::QubitId;
9292
use pecos_core::errors::PecosError;
9393
use rand_chacha::ChaCha8Rng;
9494
use std::any::Any;
95-
use std::collections::HashSet;
95+
use std::collections::{BTreeSet, HashSet};
9696

9797
/// General noise model implementation that includes parameterized error channels for various quantum operations
9898
///
@@ -172,9 +172,9 @@ pub struct GeneralNoiseModel {
172172
/// qubit states after initialization. Ranges from 0 to 1.
173173
p_prep_leak_ratio: f64,
174174

175-
/// Probability of crosstalk during initialization operations
175+
/// Probability of crosstalk during preparation operations
176176
///
177-
/// Models the probability that an initialization operation on one qubit affects nearby qubits.
177+
/// Models the probability that a preparation operation on one qubit affects nearby qubits.
178178
/// In ion trap systems, this could represent scattered light during optical pumping affecting
179179
/// neighboring ions.
180180
p_prep_crosstalk: f64,
@@ -326,10 +326,23 @@ pub struct GeneralNoiseModel {
326326
/// Random number generator for stochastic noise processes
327327
rng: NoiseRng<ChaCha8Rng>,
328328

329+
/// Set of qubits that have been prepared at any point in the program.
330+
///
331+
/// This is so that we know which qubits exists and we can apply crosstalk
332+
/// to them. Qubits that are measured / discarded are not removed from here, since
333+
/// PECOS does not assume measurements are destructive. This should not cause a
334+
/// problem, since inactive qubits suffering error have no effect on the state,
335+
/// and active qubits should always suffer errors under this naive crosstalk model.
336+
///
337+
/// Using a `BTreeSet` because we will iterate over the qubits and we want determinism.
338+
prepared_qubits: BTreeSet<usize>,
339+
329340
/// Track which qubits are being measured in the current batch and their gate types
330-
/// This is needed to properly handle leakage during measurements
331-
/// Each entry is (`qubit_id`, `is_measure_leaked`)
332-
measured_qubits: Vec<(usize, bool)>,
341+
/// This is needed to properly handle leakage during measurements as well
342+
/// as crosstalk.
343+
/// TODO: manage this via result tags.
344+
/// Each entry is (`qubit_id`, `is_measure_leaked`, `is_crosstalk`)
345+
measured_qubits: Vec<(usize, bool, bool)>,
333346
}
334347

335348
impl ControlEngine for GeneralNoiseModel {
@@ -503,21 +516,24 @@ impl GeneralNoiseModel {
503516
);
504517
}
505518
GateType::Prep => {
519+
for &q in &gate.qubits {
520+
self.prepared_qubits.insert(usize::from(q));
521+
}
506522
self.apply_prep_faults(&gate, &mut builder);
507-
508-
// TODO: Implement prep crosstalk when needed
523+
self.apply_crosstalk_faults(&gate, self.p_prep_crosstalk, &mut builder);
509524
}
510525
GateType::Measure | GateType::MeasureLeaked => {
511526
// Track which qubits are being measured for leakage handling
512527
let is_measure_leaked = gate.gate_type == GateType::MeasureLeaked;
513528
self.measured_qubits.extend(
514529
gate.qubits
515530
.iter()
516-
.map(|q| (usize::from(*q), is_measure_leaked)),
531+
.map(|q| (usize::from(*q), is_measure_leaked, false)),
517532
);
518533
// Measurement noise is handled in apply_noise_on_continue_processing
519534
// We still need to add the original gate here
520535
builder.add_gate_command(&gate);
536+
self.apply_crosstalk_faults(&gate, self.p_meas_crosstalk, &mut builder);
521537
}
522538
GateType::I => {
523539
let err_msg = format!(
@@ -589,15 +605,26 @@ impl GeneralNoiseModel {
589605

590606
// Check if we have leaked qubits that were measured
591607
let has_leakage = !self.leaked_qubits.is_empty()
592-
&& self.measured_qubits.iter().any(|(q, _)| self.is_leaked(*q));
608+
&& self
609+
.measured_qubits
610+
.iter()
611+
.any(|(q, _, _)| self.is_leaked(*q));
593612

594613
for (idx, outcome) in measurement_outcomes.into_iter().enumerate() {
595614
let mut val = outcome;
596615

597-
// Check if this measurement corresponds to a leaked qubit
598-
if has_leakage && idx < self.measured_qubits.len() {
599-
let (qubit, is_measure_leaked) = self.measured_qubits[idx];
600-
if self.is_leaked(qubit) {
616+
// Check if this measurement corresponds to a leaked qubit or comes from
617+
// crosstalk
618+
if idx < self.measured_qubits.len() {
619+
let (qubit, is_measure_leaked, is_crosstalk) = self.measured_qubits[idx];
620+
621+
// Check if this measurement comes from crosstalk noise. If so, ignore it.
622+
if is_crosstalk {
623+
trace!("Qubit {qubit} was measured by crosstalk; outcome is ignored.");
624+
continue; // Skip this iteration
625+
}
626+
627+
if has_leakage && self.is_leaked(qubit) {
601628
if is_measure_leaked {
602629
trace!("Qubit {qubit} is leaked, MeasureLeaked returns 2");
603630
// For MeasureLeaked, return 2 for leaked qubits
@@ -783,6 +810,41 @@ impl GeneralNoiseModel {
783810
}
784811
}
785812

813+
/// Apply crosstalk noise
814+
///
815+
/// Naive crosstalk noise model:
816+
/// 1. All qubits in the trap but the ones in the `gate` are subject to crosstalk
817+
// error. The `gate` should be either qubit measurement or preparation.
818+
// 2. *Each* qubit not in `gate` has the given `probability` to suffer an error.
819+
/// 3. Affected qubits are collapsed into the computational basis (Z measurement).
820+
///
821+
/// In ion trap systems, this could represent scattered light during optical pumping
822+
/// affecting neighboring ions.
823+
pub fn apply_crosstalk_faults(
824+
&mut self,
825+
gate: &Gate,
826+
probability: f64,
827+
builder: &mut ByteMessageBuilder,
828+
) {
829+
let mut affected_qubits = Vec::new();
830+
let gate_qubits: Vec<usize> = gate.qubits.iter().map(|q| usize::from(*q)).collect();
831+
832+
for q in self.prepared_qubits.clone() {
833+
if !gate_qubits.contains(&q) && self.rng.occurs(probability) {
834+
affected_qubits.push(q);
835+
trace!("Qubit {q} affected by crosstalk error");
836+
}
837+
}
838+
839+
builder.add_measurements(&affected_qubits);
840+
// We need to mark these measurements as being introduced by crosstalk rather
841+
// than the user's program so that we can discard the results in
842+
// apply_noise_on_continue_processing.
843+
self.measured_qubits.extend(
844+
affected_qubits.iter().map(|&q| (q, false, true)), // (qubit, is_measure_leaked, is_crosstalk)
845+
);
846+
}
847+
786848
/// Apply single-qubit gate noise faults
787849
///
788850
/// Models errors that occur during single-qubit gate operations:
@@ -2225,6 +2287,132 @@ mod tests {
22252287
);
22262288
}
22272289

2290+
#[test]
2291+
fn test_prep_crosstalk() {
2292+
use crate::byte_message::ByteMessageBuilder;
2293+
2294+
let mut model = GeneralNoiseModel::builder()
2295+
.with_p_prep_crosstalk(1.0)
2296+
.with_seed(42)
2297+
.build();
2298+
let noise = model
2299+
.as_any_mut()
2300+
.downcast_mut::<GeneralNoiseModel>()
2301+
.unwrap();
2302+
2303+
let mut builder = ByteMessageBuilder::new();
2304+
let _ = builder.for_quantum_operations();
2305+
// Prepare a bunch of |0> states
2306+
builder.add_prep(&[0, 1, 2, 3, 4]);
2307+
// Apply mid-circuit measurement and reset
2308+
builder.add_measurements(&[2]);
2309+
builder.add_prep(&[2]);
2310+
let _cmd = noise.apply_noise_on_start(&builder.build()).unwrap();
2311+
2312+
assert_eq!(
2313+
noise.measured_qubits.len(),
2314+
5,
2315+
"There should be 5 measured qubits: one from MCMR and the others from
2316+
crosstalk got: {:?}",
2317+
noise.measured_qubits
2318+
);
2319+
2320+
let (q, _, is_crosstalk) = noise.measured_qubits[0];
2321+
assert_eq!(q, 2, "The first measurement should be the MCMR on qubit 2");
2322+
assert!(!is_crosstalk, "The first measurement should come from MCMR");
2323+
2324+
for (_, _, is_crosstalk) in &noise.measured_qubits[1..] {
2325+
assert!(
2326+
is_crosstalk,
2327+
"The other measurements should come from crosstalk"
2328+
);
2329+
}
2330+
2331+
// All results are 0
2332+
let mut outcome_builder = ByteMessageBuilder::new();
2333+
let _ = outcome_builder.for_outcomes();
2334+
outcome_builder.add_outcomes(&[0, 0, 0, 0, 0]);
2335+
2336+
let mcmr = noise
2337+
.apply_noise_on_continue_processing(outcome_builder.build())
2338+
.unwrap();
2339+
let results = mcmr.outcomes().unwrap();
2340+
2341+
assert_eq!(
2342+
noise.measured_qubits.len(),
2343+
0,
2344+
"The list of measured_qubits should have been cleared."
2345+
);
2346+
assert_eq!(
2347+
results.len(),
2348+
1,
2349+
"There should only be one outcome: that of the mid-circ measurement"
2350+
);
2351+
}
2352+
2353+
#[test]
2354+
fn test_meas_crosstalk() {
2355+
use crate::byte_message::ByteMessageBuilder;
2356+
2357+
let mut model = GeneralNoiseModel::builder()
2358+
.with_p_meas_crosstalk(1.0)
2359+
.with_seed(42)
2360+
.build();
2361+
let noise = model
2362+
.as_any_mut()
2363+
.downcast_mut::<GeneralNoiseModel>()
2364+
.unwrap();
2365+
2366+
let mut builder = ByteMessageBuilder::new();
2367+
let _ = builder.for_quantum_operations();
2368+
// Prepare a bunch of |0> states
2369+
builder.add_prep(&[0, 1, 2, 3, 4]);
2370+
// Apply mid-circuit measurement and reset
2371+
builder.add_measurements(&[2]);
2372+
builder.add_prep(&[2]);
2373+
let _cmd = noise.apply_noise_on_start(&builder.build()).unwrap();
2374+
2375+
assert_eq!(
2376+
noise.measured_qubits.len(),
2377+
5,
2378+
"There should be 5 measured qubits: one from MCMR and the others from
2379+
crosstalk got: {:?}",
2380+
noise.measured_qubits
2381+
);
2382+
2383+
let (q, _, is_crosstalk) = noise.measured_qubits[0];
2384+
assert_eq!(q, 2, "The first measurement should be the MCMR on qubit 2");
2385+
assert!(!is_crosstalk, "The first measurement should come from MCMR");
2386+
2387+
for (_, _, is_crosstalk) in &noise.measured_qubits[1..] {
2388+
assert!(
2389+
is_crosstalk,
2390+
"The other measurements should come from crosstalk"
2391+
);
2392+
}
2393+
2394+
// All results are 0
2395+
let mut outcome_builder = ByteMessageBuilder::new();
2396+
let _ = outcome_builder.for_outcomes();
2397+
outcome_builder.add_outcomes(&[0, 0, 0, 0, 0]);
2398+
2399+
let mcmr = noise
2400+
.apply_noise_on_continue_processing(outcome_builder.build())
2401+
.unwrap();
2402+
let results = mcmr.outcomes().unwrap();
2403+
2404+
assert_eq!(
2405+
noise.measured_qubits.len(),
2406+
0,
2407+
"The list of measured_qubits should have been cleared."
2408+
);
2409+
assert_eq!(
2410+
results.len(),
2411+
1,
2412+
"There should only be one outcome: that of the mid-circ measurement"
2413+
);
2414+
}
2415+
22282416
#[test]
22292417
fn test_parameter_scaling() {
22302418
// Test that scaling factors are applied correctly - use builder pattern

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,15 @@ impl GeneralNoiseModelBuilder {
412412
self
413413
}
414414

415+
// TODO: See if we should put a average scaling...
416+
/// Set the average prep crosstalk
417+
#[must_use]
418+
pub fn with_average_p_prep_crosstalk(mut self, prob: f64) -> Self {
419+
let prob: f64 = prob * 18.0 / 5.0;
420+
self.p_prep_crosstalk = Some(prob);
421+
self
422+
}
423+
415424
/// Set the scaling factor for initialization errors
416425
///
417426
/// Multiplier for preparation error probabilities. Allows adjustment of the relative
@@ -642,6 +651,15 @@ impl GeneralNoiseModelBuilder {
642651
self
643652
}
644653

654+
// TODO: See if we should put a average scaling...
655+
/// Set the average measurement crosstalk
656+
#[must_use]
657+
pub fn with_average_p_meas_crosstalk(mut self, prob: f64) -> Self {
658+
let prob: f64 = prob * 18.0 / 5.0;
659+
self.p_meas_crosstalk = Some(prob);
660+
self
661+
}
662+
645663
/// Set the scaling factor for measurement faults
646664
///
647665
/// Multiplier for measurement error probabilities. Allows adjustment of the relative

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::noise::{
22
GeneralNoiseModel, NoiseRng, SingleQubitWeightedSampler, TwoQubitWeightedSampler,
33
};
4-
use std::collections::{BTreeMap, HashSet};
4+
use std::collections::{BTreeMap, BTreeSet, HashSet};
55

66
impl Default for GeneralNoiseModel {
77
/// Create a new noise model with default error parameters
@@ -100,6 +100,7 @@ impl Default for GeneralNoiseModel {
100100
p2_idle: 0.0,
101101
leaked_qubits: HashSet::new(),
102102
rng: NoiseRng::default(),
103+
prepared_qubits: BTreeSet::new(),
103104
measured_qubits: Vec::new(),
104105
p_meas_crosstalk: 0.0,
105106
p_prep_crosstalk: 0.0,

0 commit comments

Comments
 (0)