@@ -92,7 +92,7 @@ use pecos_core::QubitId;
9292use pecos_core:: errors:: PecosError ;
9393use rand_chacha:: ChaCha8Rng ;
9494use std:: any:: Any ;
95- use std:: collections:: HashSet ;
95+ use std:: collections:: { HashSet , BTreeSet } ;
9696
9797/// General noise model implementation that includes parameterized error channels for various quantum operations
9898///
@@ -326,6 +326,17 @@ pub struct GeneralNoiseModel {
326326 /// Random number generator for stochastic noise processes
327327 rng : NoiseRng < ChaCha8Rng > ,
328328
329+ /// Set of qubits that have been initialized 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+ initialized_qubits : BTreeSet < usize > ,
339+
329340 /// Track which qubits are being measured in the current batch and their gate types
330341 /// This is needed to properly handle leakage during measurements
331342 /// Each entry is (`qubit_id`, `is_measure_leaked`)
@@ -503,9 +514,11 @@ impl GeneralNoiseModel {
503514 ) ;
504515 }
505516 GateType :: Prep => {
517+ for & q in & gate. qubits {
518+ self . initialized_qubits . insert ( usize:: from ( q) ) ;
519+ }
506520 self . apply_prep_faults ( & gate, & mut builder) ;
507-
508- // TODO: Implement prep crosstalk when needed
521+ self . apply_crosstalk_faults ( & gate, self . p_prep_crosstalk , & mut builder) ;
509522 }
510523 GateType :: Measure | GateType :: MeasureLeaked => {
511524 // Track which qubits are being measured for leakage handling
@@ -518,6 +531,7 @@ impl GeneralNoiseModel {
518531 // Measurement noise is handled in apply_noise_on_continue_processing
519532 // We still need to add the original gate here
520533 builder. add_gate_command ( & gate) ;
534+ self . apply_crosstalk_faults ( & gate, self . p_meas_crosstalk , & mut builder) ;
521535 }
522536 GateType :: I => {
523537 let err_msg = format ! (
@@ -783,6 +797,39 @@ impl GeneralNoiseModel {
783797 }
784798 }
785799
800+ /// Apply crosstalk noise
801+ ///
802+ /// Naive crosstalk noise model:
803+ /// 1. All qubits in the trap but the ones in the `gate` are subject to crosstalk
804+ // error. The `gate` should be either qubit measurement or initialization.
805+ // 2. *Each* qubit not in `gate` has the given `probability` to suffer an error.
806+ /// 3. Affected qubits are collapsed into the computational basis (Z measurement).
807+ ///
808+ /// In ion trap systems, this could represent scattered light during optical pumping
809+ /// affecting neighboring ions.
810+ pub fn apply_crosstalk_faults (
811+ & mut self ,
812+ gate : & Gate ,
813+ probability : f64 ,
814+ builder : & mut ByteMessageBuilder
815+ ) {
816+ let mut affected_qubits = Vec :: new ( ) ;
817+ let gate_qubits: Vec < usize > = gate. qubits . iter ( ) . map ( |q| usize:: from ( * q) ) . collect ( ) ;
818+
819+ for q in self . initialized_qubits . clone ( ) {
820+ if !gate_qubits. contains ( & q) {
821+ if self . rng . occurs ( probability) {
822+ affected_qubits. push ( q) ;
823+ }
824+ }
825+ }
826+ // TODO potentially major issue: I expect these will be adding measurement
827+ // outcomes to the OutputEngine, which should be ignored. How do we do this?
828+ // Perhaps there should be another add_collapse() method that applies a
829+ // measurement and discards the outcome.
830+ builder. add_measurements ( & affected_qubits) ;
831+ }
832+
786833 /// Apply single-qubit gate noise faults
787834 ///
788835 /// Models errors that occur during single-qubit gate operations:
0 commit comments