1use crate::builder::Circuit;
7use quantrs2_core::{
8 error::{QuantRS2Error, QuantRS2Result},
9 gate::GateOp,
10 qubit::QubitId,
11};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::f64::consts::PI;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct NoiseModel {
19 pub single_qubit_errors: HashMap<String, SingleQubitError>,
21 pub two_qubit_errors: HashMap<String, TwoQubitError>,
23 pub decoherence: HashMap<usize, DecoherenceParams>,
25 pub readout_errors: HashMap<usize, ReadoutError>,
27 pub crosstalk: Option<CrosstalkModel>,
29 pub thermal_noise: Option<ThermalNoise>,
31 pub leakage_errors: HashMap<usize, LeakageError>,
33 pub calibration_time: std::time::SystemTime,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct SingleQubitError {
40 pub depolarizing: f64,
42 pub pauli_x: f64,
44 pub pauli_y: f64,
46 pub pauli_z: f64,
48 pub amplitude_damping: f64,
50 pub phase_damping: f64,
52 pub duration: f64,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct TwoQubitError {
59 pub depolarizing: f64,
61 pub pauli_errors: [[f64; 4]; 4], pub amplitude_damping: [f64; 2],
65 pub phase_damping: [f64; 2],
67 pub duration: f64,
69 pub crosstalk_strength: f64,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct DecoherenceParams {
76 pub t1: f64,
78 pub t2: f64,
80 pub t2_star: f64,
82 pub temperature: f64,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ReadoutError {
89 pub prob_0_to_1: f64,
91 pub prob_1_to_0: f64,
93 pub fidelity: f64,
95 pub duration: f64,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, Default)]
101pub struct CrosstalkModel {
102 pub coupling_matrix: Vec<Vec<f64>>,
104 pub zz_coupling: HashMap<(usize, usize), f64>,
106 pub xy_coupling: HashMap<(usize, usize), f64>,
108 pub frequency_shifts: HashMap<usize, Vec<(usize, f64)>>,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ThermalNoise {
115 pub temperature: f64,
117 pub thermal_photons: f64,
119 pub heating_rate: f64,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct LeakageError {
126 pub leakage_to_2: f64,
128 pub leakage_to_higher: f64,
130 pub seepage: f64,
132}
133
134pub struct NoiseAnalyzer {
136 noise_models: HashMap<String, NoiseModel>,
137}
138
139impl NoiseAnalyzer {
140 #[must_use]
142 pub fn new() -> Self {
143 let mut analyzer = Self {
144 noise_models: HashMap::new(),
145 };
146
147 analyzer.load_device_noise_models();
149 analyzer
150 }
151
152 pub fn add_noise_model(&mut self, device: String, model: NoiseModel) {
154 self.noise_models.insert(device, model);
155 }
156
157 #[must_use]
159 pub fn available_models(&self) -> Vec<String> {
160 self.noise_models.keys().cloned().collect()
161 }
162
163 pub fn analyze_circuit_noise<const N: usize>(
165 &self,
166 circuit: &Circuit<N>,
167 device: &str,
168 ) -> QuantRS2Result<NoiseAnalysisResult> {
169 let noise_model = self
170 .noise_models
171 .get(device)
172 .ok_or_else(|| QuantRS2Error::InvalidInput(format!("Unknown device: {device}")))?;
173
174 let mut total_error = 0.0;
175 let mut gate_errors = Vec::new();
176 let mut decoherence_error = 0.0;
177 let mut readout_error = 0.0;
178 let mut crosstalk_error = 0.0;
179
180 for (i, gate) in circuit.gates().iter().enumerate() {
182 let gate_error = self.calculate_gate_error(gate.as_ref(), noise_model)?;
183 gate_errors.push((i, gate.name().to_string(), gate_error));
184 total_error += gate_error;
185 }
186
187 decoherence_error = self.calculate_decoherence_error(circuit, noise_model)?;
189
190 readout_error = self.calculate_readout_error(N, noise_model);
192
193 if let Some(crosstalk) = &noise_model.crosstalk {
195 crosstalk_error = self.calculate_crosstalk_error(circuit, crosstalk)?;
196 }
197
198 let total_fidelity =
199 1.0 - (total_error + decoherence_error + readout_error + crosstalk_error);
200
201 Ok(NoiseAnalysisResult {
202 total_error: total_error + decoherence_error + readout_error + crosstalk_error,
203 total_fidelity,
204 gate_errors,
205 decoherence_error,
206 readout_error,
207 crosstalk_error,
208 dominant_error_source: self.identify_dominant_error_source(
209 total_error,
210 decoherence_error,
211 readout_error,
212 crosstalk_error,
213 ),
214 })
215 }
216
217 fn calculate_gate_error(
219 &self,
220 gate: &dyn GateOp,
221 noise_model: &NoiseModel,
222 ) -> QuantRS2Result<f64> {
223 let gate_name = gate.name();
224 let qubits = gate.qubits();
225
226 match qubits.len() {
227 1 => {
228 if let Some(error) = noise_model.single_qubit_errors.get(gate_name) {
229 Ok(error.depolarizing + error.amplitude_damping + error.phase_damping)
230 } else {
231 let avg_error = noise_model
233 .single_qubit_errors
234 .values()
235 .map(|e| e.depolarizing + e.amplitude_damping + e.phase_damping)
236 .sum::<f64>()
237 / noise_model.single_qubit_errors.len() as f64;
238 Ok(avg_error)
239 }
240 }
241 2 => {
242 if let Some(error) = noise_model.two_qubit_errors.get(gate_name) {
243 Ok(error.depolarizing
244 + error.amplitude_damping.iter().sum::<f64>()
245 + error.phase_damping.iter().sum::<f64>())
246 } else {
247 let avg_error = noise_model
249 .two_qubit_errors
250 .values()
251 .map(|e| {
252 e.depolarizing
253 + e.amplitude_damping.iter().sum::<f64>()
254 + e.phase_damping.iter().sum::<f64>()
255 })
256 .sum::<f64>()
257 / noise_model.two_qubit_errors.len() as f64;
258 Ok(avg_error)
259 }
260 }
261 _ => {
262 Ok(0.01 * qubits.len() as f64) }
265 }
266 }
267
268 fn calculate_decoherence_error<const N: usize>(
270 &self,
271 circuit: &Circuit<N>,
272 noise_model: &NoiseModel,
273 ) -> QuantRS2Result<f64> {
274 let total_time = self.estimate_circuit_time(circuit, noise_model);
275 let mut total_decoherence = 0.0;
276
277 for qubit_id in 0..N {
278 if let Some(decoherence) = noise_model.decoherence.get(&qubit_id) {
279 let t1_error = 1.0 - (-total_time / decoherence.t1).exp();
281
282 let t2_error = 1.0 - (-total_time / decoherence.t2).exp();
284
285 total_decoherence += t1_error + t2_error;
286 }
287 }
288
289 Ok(total_decoherence / N as f64)
290 }
291
292 fn estimate_circuit_time<const N: usize>(
294 &self,
295 circuit: &Circuit<N>,
296 noise_model: &NoiseModel,
297 ) -> f64 {
298 let mut total_time = 0.0;
299
300 for gate in circuit.gates() {
301 let gate_name = gate.name();
302 let qubits = gate.qubits();
303
304 let duration = match qubits.len() {
305 1 => noise_model
306 .single_qubit_errors
307 .get(gate_name)
308 .map_or(10.0, |e| e.duration), 2 => noise_model
310 .two_qubit_errors
311 .get(gate_name)
312 .map_or(200.0, |e| e.duration), _ => 500.0, };
315
316 total_time += duration;
317 }
318
319 total_time / 1000.0 }
321
322 fn calculate_readout_error(&self, num_qubits: usize, noise_model: &NoiseModel) -> f64 {
324 let mut total_readout_error = 0.0;
325
326 for qubit_id in 0..num_qubits {
327 if let Some(readout) = noise_model.readout_errors.get(&qubit_id) {
328 total_readout_error += 1.0 - readout.fidelity;
329 }
330 }
331
332 total_readout_error / num_qubits as f64
333 }
334
335 fn calculate_crosstalk_error<const N: usize>(
337 &self,
338 circuit: &Circuit<N>,
339 crosstalk: &CrosstalkModel,
340 ) -> QuantRS2Result<f64> {
341 let mut total_crosstalk = 0.0;
342
343 for gate in circuit.gates() {
344 if gate.qubits().len() == 2 {
345 let qubits: Vec<_> = gate.qubits().iter().map(|q| q.id() as usize).collect();
346 let q1 = qubits[0];
347 let q2 = qubits[1];
348
349 if let Some(zz_strength) = crosstalk.zz_coupling.get(&(q1, q2)) {
351 total_crosstalk += zz_strength.abs();
352 }
353
354 if let Some(xy_strength) = crosstalk.xy_coupling.get(&(q1, q2)) {
356 total_crosstalk += xy_strength.abs();
357 }
358 }
359 }
360
361 Ok(total_crosstalk / circuit.gates().len() as f64)
362 }
363
364 fn identify_dominant_error_source(
366 &self,
367 gate_error: f64,
368 decoherence_error: f64,
369 readout_error: f64,
370 crosstalk_error: f64,
371 ) -> ErrorSource {
372 let max_error = gate_error
373 .max(decoherence_error)
374 .max(readout_error)
375 .max(crosstalk_error);
376
377 if max_error == gate_error {
378 ErrorSource::GateErrors
379 } else if max_error == decoherence_error {
380 ErrorSource::Decoherence
381 } else if max_error == readout_error {
382 ErrorSource::Readout
383 } else {
384 ErrorSource::Crosstalk
385 }
386 }
387
388 fn load_device_noise_models(&mut self) {
390 self.add_noise_model("ibm_quantum".to_string(), NoiseModel::ibm_quantum());
392
393 self.add_noise_model("google_quantum".to_string(), NoiseModel::google_quantum());
395
396 self.add_noise_model("aws_braket".to_string(), NoiseModel::aws_braket());
398 }
399}
400
401#[derive(Debug, Clone)]
403pub struct NoiseAnalysisResult {
404 pub total_error: f64,
406 pub total_fidelity: f64,
408 pub gate_errors: Vec<(usize, String, f64)>,
410 pub decoherence_error: f64,
412 pub readout_error: f64,
414 pub crosstalk_error: f64,
416 pub dominant_error_source: ErrorSource,
418}
419
420#[derive(Debug, Clone, PartialEq, Eq)]
422pub enum ErrorSource {
423 GateErrors,
424 Decoherence,
425 Readout,
426 Crosstalk,
427}
428
429impl NoiseModel {
430 #[must_use]
432 pub fn ibm_quantum() -> Self {
433 let mut single_qubit_errors = HashMap::new();
434
435 single_qubit_errors.insert(
437 "X".to_string(),
438 SingleQubitError {
439 depolarizing: 0.0001,
440 pauli_x: 0.00005,
441 pauli_y: 0.00005,
442 pauli_z: 0.0001,
443 amplitude_damping: 0.0002,
444 phase_damping: 0.0003,
445 duration: 35.0, },
447 );
448
449 single_qubit_errors.insert(
450 "RZ".to_string(),
451 SingleQubitError {
452 depolarizing: 0.0,
453 pauli_x: 0.0,
454 pauli_y: 0.0,
455 pauli_z: 0.00001,
456 amplitude_damping: 0.0,
457 phase_damping: 0.00002,
458 duration: 0.0, },
460 );
461
462 let mut two_qubit_errors = HashMap::new();
463
464 two_qubit_errors.insert(
466 "CNOT".to_string(),
467 TwoQubitError {
468 depolarizing: 0.01,
469 pauli_errors: [[0.0; 4]; 4], amplitude_damping: [0.002, 0.002],
471 phase_damping: [0.003, 0.003],
472 duration: 300.0, crosstalk_strength: 0.001,
474 },
475 );
476
477 let mut decoherence = HashMap::new();
478 for i in 0..127 {
479 decoherence.insert(
480 i,
481 DecoherenceParams {
482 t1: 100.0, t2: 80.0, t2_star: 70.0,
485 temperature: 15.0, },
487 );
488 }
489
490 let mut readout_errors = HashMap::new();
491 for i in 0..127 {
492 readout_errors.insert(
493 i,
494 ReadoutError {
495 prob_0_to_1: 0.01,
496 prob_1_to_0: 0.02,
497 fidelity: 0.985,
498 duration: 1000.0, },
500 );
501 }
502
503 Self {
504 single_qubit_errors,
505 two_qubit_errors,
506 decoherence,
507 readout_errors,
508 crosstalk: Some(CrosstalkModel::default()),
509 thermal_noise: Some(ThermalNoise {
510 temperature: 15.0,
511 thermal_photons: 0.001,
512 heating_rate: 0.0001,
513 }),
514 leakage_errors: HashMap::new(),
515 calibration_time: std::time::SystemTime::now(),
516 }
517 }
518
519 #[must_use]
521 pub fn google_quantum() -> Self {
522 let mut single_qubit_errors = HashMap::new();
523
524 single_qubit_errors.insert(
525 "RZ".to_string(),
526 SingleQubitError {
527 depolarizing: 0.0,
528 pauli_x: 0.0,
529 pauli_y: 0.0,
530 pauli_z: 0.00001,
531 amplitude_damping: 0.0,
532 phase_damping: 0.00001,
533 duration: 0.0,
534 },
535 );
536
537 single_qubit_errors.insert(
538 "SQRT_X".to_string(),
539 SingleQubitError {
540 depolarizing: 0.0005,
541 pauli_x: 0.0002,
542 pauli_y: 0.0002,
543 pauli_z: 0.0001,
544 amplitude_damping: 0.0001,
545 phase_damping: 0.0002,
546 duration: 25.0,
547 },
548 );
549
550 let mut two_qubit_errors = HashMap::new();
551
552 two_qubit_errors.insert(
553 "CZ".to_string(),
554 TwoQubitError {
555 depolarizing: 0.005,
556 pauli_errors: [[0.0; 4]; 4],
557 amplitude_damping: [0.001, 0.001],
558 phase_damping: [0.002, 0.002],
559 duration: 20.0,
560 crosstalk_strength: 0.0005,
561 },
562 );
563
564 let mut decoherence = HashMap::new();
565 for i in 0..70 {
566 decoherence.insert(
567 i,
568 DecoherenceParams {
569 t1: 50.0,
570 t2: 40.0,
571 t2_star: 35.0,
572 temperature: 10.0,
573 },
574 );
575 }
576
577 let mut readout_errors = HashMap::new();
578 for i in 0..70 {
579 readout_errors.insert(
580 i,
581 ReadoutError {
582 prob_0_to_1: 0.005,
583 prob_1_to_0: 0.008,
584 fidelity: 0.99,
585 duration: 500.0,
586 },
587 );
588 }
589
590 Self {
591 single_qubit_errors,
592 two_qubit_errors,
593 decoherence,
594 readout_errors,
595 crosstalk: Some(CrosstalkModel::default()),
596 thermal_noise: Some(ThermalNoise {
597 temperature: 10.0,
598 thermal_photons: 0.0005,
599 heating_rate: 0.00005,
600 }),
601 leakage_errors: HashMap::new(),
602 calibration_time: std::time::SystemTime::now(),
603 }
604 }
605
606 #[must_use]
608 pub fn aws_braket() -> Self {
609 let mut single_qubit_errors = HashMap::new();
611
612 single_qubit_errors.insert(
613 "RZ".to_string(),
614 SingleQubitError {
615 depolarizing: 0.0001,
616 pauli_x: 0.00005,
617 pauli_y: 0.00005,
618 pauli_z: 0.00002,
619 amplitude_damping: 0.0001,
620 phase_damping: 0.0002,
621 duration: 0.0,
622 },
623 );
624
625 let mut two_qubit_errors = HashMap::new();
626
627 two_qubit_errors.insert(
628 "CNOT".to_string(),
629 TwoQubitError {
630 depolarizing: 0.008,
631 pauli_errors: [[0.0; 4]; 4],
632 amplitude_damping: [0.0015, 0.0015],
633 phase_damping: [0.0025, 0.0025],
634 duration: 200.0,
635 crosstalk_strength: 0.0008,
636 },
637 );
638
639 let mut decoherence = HashMap::new();
640 for i in 0..100 {
641 decoherence.insert(
642 i,
643 DecoherenceParams {
644 t1: 80.0,
645 t2: 60.0,
646 t2_star: 50.0,
647 temperature: 12.0,
648 },
649 );
650 }
651
652 let mut readout_errors = HashMap::new();
653 for i in 0..100 {
654 readout_errors.insert(
655 i,
656 ReadoutError {
657 prob_0_to_1: 0.008,
658 prob_1_to_0: 0.012,
659 fidelity: 0.988,
660 duration: 800.0,
661 },
662 );
663 }
664
665 Self {
666 single_qubit_errors,
667 two_qubit_errors,
668 decoherence,
669 readout_errors,
670 crosstalk: Some(CrosstalkModel::default()),
671 thermal_noise: Some(ThermalNoise {
672 temperature: 12.0,
673 thermal_photons: 0.0008,
674 heating_rate: 0.00008,
675 }),
676 leakage_errors: HashMap::new(),
677 calibration_time: std::time::SystemTime::now(),
678 }
679 }
680}
681
682impl Default for NoiseAnalyzer {
683 fn default() -> Self {
684 Self::new()
685 }
686}
687
688#[cfg(test)]
689mod tests {
690 use super::*;
691 use quantrs2_core::gate::multi::CNOT;
692 use quantrs2_core::gate::single::Hadamard;
693
694 #[test]
695 fn test_noise_analyzer_creation() {
696 let analyzer = NoiseAnalyzer::new();
697 assert!(!analyzer.available_models().is_empty());
698 }
699
700 #[test]
701 fn test_noise_model_creation() {
702 let model = NoiseModel::ibm_quantum();
703 assert!(!model.single_qubit_errors.is_empty());
704 assert!(!model.two_qubit_errors.is_empty());
705 assert!(!model.decoherence.is_empty());
706 }
707
708 #[test]
709 fn test_circuit_noise_analysis() {
710 let analyzer = NoiseAnalyzer::new();
711 let mut circuit = Circuit::<2>::new();
712 circuit
713 .add_gate(Hadamard { target: QubitId(0) })
714 .expect("add H gate to circuit");
715 circuit
716 .add_gate(CNOT {
717 control: QubitId(0),
718 target: QubitId(1),
719 })
720 .expect("add CNOT gate to circuit");
721
722 let result = analyzer
723 .analyze_circuit_noise(&circuit, "ibm_quantum")
724 .expect("analyze_circuit_noise should succeed");
725 assert!(result.total_fidelity > 0.0 && result.total_fidelity < 1.0);
726 assert!(!result.gate_errors.is_empty());
727 }
728
729 #[test]
730 fn test_single_qubit_error_calculation() {
731 let analyzer = NoiseAnalyzer::new();
732 let model = NoiseModel::ibm_quantum();
733 let h_gate = Hadamard { target: QubitId(0) };
734
735 let error = analyzer
736 .calculate_gate_error(&h_gate, &model)
737 .expect("calculate_gate_error should succeed");
738 assert!(error > 0.0);
739 }
740
741 #[test]
742 fn test_decoherence_params() {
743 let params = DecoherenceParams {
744 t1: 100.0,
745 t2: 80.0,
746 t2_star: 70.0,
747 temperature: 15.0,
748 };
749
750 assert!(params.t1 > params.t2);
751 assert!(params.t2 > params.t2_star);
752 }
753
754 #[test]
755 fn test_readout_error() {
756 let error = ReadoutError {
757 prob_0_to_1: 0.01,
758 prob_1_to_0: 0.02,
759 fidelity: 0.985,
760 duration: 1000.0,
761 };
762
763 assert!(error.fidelity < 1.0);
764 assert!(error.prob_0_to_1 + error.prob_1_to_0 < 1.0);
765 }
766}