quantrs2_sim/noise_advanced.rs
1//! Advanced noise models beyond simple single-qubit depolarising channels.
2//!
3//! Provides two-qubit correlated noise, crosstalk, leakage to higher energy
4//! levels, and time-dependent amplitude / phase damping noise channels for
5//! realistic device simulation.
6
7#![allow(clippy::needless_range_loop)]
8
9use scirs2_core::Complex64;
10use std::f64::consts::PI;
11use std::time::Duration;
12
13use quantrs2_core::error::QuantRS2Result;
14use quantrs2_core::qubit::QubitId;
15
16use crate::noise::{NoiseChannel, NoiseChannelType, NoiseModel};
17
18/// Two-qubit depolarizing noise channel
19#[derive(Debug, Clone)]
20pub struct TwoQubitDepolarizingChannel {
21 /// First qubit
22 pub qubit1: QubitId,
23
24 /// Second qubit
25 pub qubit2: QubitId,
26
27 /// Probability of error
28 pub probability: f64,
29}
30
31impl NoiseChannel for TwoQubitDepolarizingChannel {
32 fn name(&self) -> &'static str {
33 "TwoQubitDepolarizing"
34 }
35
36 fn qubits(&self) -> Vec<QubitId> {
37 vec![self.qubit1, self.qubit2]
38 }
39
40 fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
41 let q1_idx = self.qubit1.id() as usize;
42 #[allow(clippy::needless_range_loop)]
43 let q2_idx = self.qubit2.id() as usize;
44 let dim = state.len();
45
46 // Apply two-qubit depolarizing noise with probability p
47 if fastrand::f64() < self.probability {
48 // Choose randomly between 15 possible Pauli errors (excluding I⊗I)
49 let error_type = fastrand::u32(..) % 15;
50
51 // Create a copy of the state to read from
52 let state_copy = state.to_vec();
53
54 match error_type {
55 0 => {
56 // X⊗I
57 for i in 0..dim {
58 let flipped_i = i ^ (1 << q1_idx);
59 state[i] = state_copy[flipped_i];
60 }
61 }
62 1 => {
63 // I⊗X
64 for i in 0..dim {
65 let flipped_i = i ^ (1 << q2_idx);
66 state[i] = state_copy[flipped_i];
67 }
68 }
69 2 => {
70 // X⊗X
71 for i in 0..dim {
72 let flipped_i = i ^ (1 << q1_idx) ^ (1 << q2_idx);
73 state[i] = state_copy[flipped_i];
74 }
75 }
76 3 => {
77 // Y⊗I
78 for i in 0..dim {
79 let flipped_i = i ^ (1 << q1_idx);
80 let phase = if (i >> q1_idx) & 1 == 1 { 1.0 } else { -1.0 };
81 state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
82 }
83 }
84 4 => {
85 // I⊗Y
86 for i in 0..dim {
87 let flipped_i = i ^ (1 << q2_idx);
88 let phase = if (i >> q2_idx) & 1 == 1 { 1.0 } else { -1.0 };
89 state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
90 }
91 }
92 5 => {
93 // Y⊗Y
94 for i in 0..dim {
95 let flipped_i = i ^ (1 << q1_idx) ^ (1 << q2_idx);
96 let phase1 = if (i >> q1_idx) & 1 == 1 { 1.0 } else { -1.0 };
97 let phase2 = if (i >> q2_idx) & 1 == 1 { 1.0 } else { -1.0 };
98 state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase1 * phase2);
99 }
100 }
101 6 => {
102 // Z⊗I
103 for i in 0..dim {
104 if (i >> q1_idx) & 1 == 1 {
105 state[i] = -state_copy[i];
106 }
107 }
108 }
109 7 => {
110 // I⊗Z
111 for i in 0..dim {
112 if (i >> q2_idx) & 1 == 1 {
113 state[i] = -state_copy[i];
114 }
115 }
116 }
117 8 => {
118 // Z⊗Z
119 for i in 0..dim {
120 let parity = ((i >> q1_idx) & 1) ^ ((i >> q2_idx) & 1);
121 if parity == 1 {
122 state[i] = -state_copy[i];
123 }
124 }
125 }
126 9 => {
127 // X⊗Y
128 for i in 0..dim {
129 let flipped_i = i ^ (1 << q1_idx) ^ (1 << q2_idx);
130 let phase = if (i >> q2_idx) & 1 == 1 { 1.0 } else { -1.0 };
131 state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
132 }
133 }
134 10 => {
135 // X⊗Z
136 for i in 0..dim {
137 let flipped_i = i ^ (1 << q1_idx);
138 if (flipped_i >> q2_idx) & 1 == 1 {
139 state[i] = -state_copy[flipped_i];
140 } else {
141 state[i] = state_copy[flipped_i];
142 }
143 }
144 }
145 11 => {
146 // Y⊗X
147 for i in 0..dim {
148 let flipped_i = i ^ (1 << q1_idx) ^ (1 << q2_idx);
149 let phase = if (i >> q1_idx) & 1 == 1 { 1.0 } else { -1.0 };
150 state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
151 }
152 }
153 12 => {
154 // Y⊗Z
155 for i in 0..dim {
156 let flipped_i = i ^ (1 << q1_idx);
157 let phase = if ((i >> q1_idx) & 1 == 1) ^ ((i >> q2_idx) & 1 == 1) {
158 Complex64::new(0.0, -1.0)
159 } else {
160 Complex64::new(0.0, 1.0)
161 };
162 state[i] = state_copy[flipped_i] * phase;
163 }
164 }
165 13 => {
166 // Z⊗X
167 for i in 0..dim {
168 let flipped_i = i ^ (1 << q2_idx);
169 if (i >> q1_idx) & 1 == 1 {
170 state[i] = -state_copy[flipped_i];
171 } else {
172 state[i] = state_copy[flipped_i];
173 }
174 }
175 }
176 14 => {
177 // Z⊗Y
178 for i in 0..dim {
179 let flipped_i = i ^ (1 << q2_idx);
180 let phase = if ((i >> q1_idx) & 1 == 1) ^ ((i >> q2_idx) & 1 == 1) {
181 Complex64::new(0.0, -1.0)
182 } else {
183 Complex64::new(0.0, 1.0)
184 };
185 state[i] = state_copy[flipped_i] * phase;
186 }
187 }
188 _ => unreachable!(),
189 }
190 }
191
192 Ok(())
193 }
194
195 fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
196 // Two-qubit depolarizing has 16 Kraus operators (15 Pauli errors + identity)
197 // This is a simplified implementation since full representation is large
198 let p = self.probability;
199 let sqrt_1_minus_p = (1.0 - p).sqrt();
200 let sqrt_p_15 = (p / 15.0).sqrt();
201
202 // Return placeholder Kraus operators
203 // In a full implementation, this would be a 16×16 matrix
204 vec![
205 vec![Complex64::new(sqrt_1_minus_p, 0.0)],
206 vec![Complex64::new(sqrt_p_15, 0.0)],
207 ]
208 }
209
210 fn probability(&self) -> f64 {
211 self.probability
212 }
213}
214
215/// Thermal relaxation noise channel (combination of T1 and T2 effects)
216#[derive(Debug, Clone)]
217pub struct ThermalRelaxationChannel {
218 /// Target qubit
219 pub target: QubitId,
220
221 /// T1 relaxation time (seconds)
222 pub t1: f64,
223
224 /// T2 pure dephasing time (seconds)
225 pub t2: f64,
226
227 /// Gate time (seconds)
228 pub gate_time: f64,
229
230 /// Excited state population at thermal equilibrium (0.0 to 1.0)
231 pub excited_state_population: f64,
232}
233
234impl NoiseChannel for ThermalRelaxationChannel {
235 fn name(&self) -> &'static str {
236 "ThermalRelaxation"
237 }
238
239 fn qubits(&self) -> Vec<QubitId> {
240 vec![self.target]
241 }
242
243 fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
244 let target_idx = self.target.id() as usize;
245 let dim = state.len();
246
247 // Calculate relaxation and dephasing probabilities
248 let p_reset = 1.0 - (-self.gate_time / self.t1).exp();
249 let p_phase = 0.5 * (1.0 - (-self.gate_time / self.t2).exp());
250
251 // Create a copy of the state for reading
252 let state_copy = state.to_vec();
253
254 // Apply thermal relaxation
255 // First apply amplitude damping (relaxation)
256 for i in 0..dim {
257 if (i >> target_idx) & 1 == 1 {
258 // This basis state has the target qubit in |1⟩
259 let base_idx = i & !(1 << target_idx); // Flip the target bit to 0
260
261 // Apply relaxation with probability p_reset
262 if fastrand::f64() < p_reset {
263 // With probability (1-p_eq), collapse to |0⟩ state
264 // With probability p_eq, collapse to |1⟩ state (thermal equilibrium)
265 if fastrand::f64() < self.excited_state_population {
266 // Stay in |1⟩ due to thermal excitation
267 state[i] = state_copy[i];
268 } else {
269 // Collapse to |0⟩
270 state[base_idx] += state_copy[i];
271 state[i] = Complex64::new(0.0, 0.0);
272 }
273 } else {
274 // No relaxation occurs, but apply sqrt(1-p) factor
275 state[i] = state_copy[i] * Complex64::new((1.0 - p_reset).sqrt(), 0.0);
276 }
277 }
278 }
279
280 // Then apply phase damping (dephasing on top of amplitude damping)
281 for i in 0..dim {
282 if (i >> target_idx) & 1 == 1 {
283 // Apply additional pure dephasing
284 if fastrand::f64() < p_phase {
285 // Random phase
286 state[i] *= Complex64::new(-1.0, 0.0); // Apply phase flip
287 }
288 }
289 }
290
291 // Normalize the state
292 NoiseChannelType::normalize_state(state);
293
294 Ok(())
295 }
296
297 fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
298 // For thermal relaxation, we would typically have 3 Kraus operators
299 // This is a simplified implementation
300 let p_reset = 1.0 - (-self.gate_time / self.t1).exp();
301 let p_phase = 0.5 * (1.0 - (-self.gate_time / self.t2).exp());
302
303 // Return placeholder Kraus operators
304 vec![vec![Complex64::new(1.0 - p_reset - p_phase, 0.0)]]
305 }
306
307 fn probability(&self) -> f64 {
308 // Return the combined probability of an error occurring
309 let p_reset = 1.0 - (-self.gate_time / self.t1).exp();
310 let p_phase = 0.5 * (1.0 - (-self.gate_time / self.t2).exp());
311 p_reset + p_phase - p_reset * p_phase // Combined probability
312 }
313}
314
315/// Crosstalk noise channel for adjacent qubits
316#[derive(Debug, Clone)]
317pub struct CrosstalkChannel {
318 /// Primary qubit
319 pub primary: QubitId,
320
321 /// Neighbor qubit
322 pub neighbor: QubitId,
323
324 /// Crosstalk strength (0.0 to 1.0)
325 pub strength: f64,
326}
327
328impl NoiseChannel for CrosstalkChannel {
329 fn name(&self) -> &'static str {
330 "Crosstalk"
331 }
332
333 fn qubits(&self) -> Vec<QubitId> {
334 vec![self.primary, self.neighbor]
335 }
336
337 fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
338 let primary_idx = self.primary.id() as usize;
339 let neighbor_idx = self.neighbor.id() as usize;
340 let dim = state.len();
341
342 // Apply crosstalk with probability based on strength
343 if fastrand::f64() < self.strength {
344 // Create a copy of the state for reading
345 let state_copy = state.to_vec();
346
347 // Randomly select an effect (simplified model):
348 // 1. ZZ interaction
349 // 2. Neighbor rotation
350 let effect = fastrand::u32(..) % 2;
351
352 match effect {
353 0 => {
354 // ZZ interaction
355 for i in 0..dim {
356 let parity = ((i >> primary_idx) & 1) ^ ((i >> neighbor_idx) & 1);
357 if parity == 1 {
358 // Apply phase shift if qubits have different parity
359 let phase = fastrand::f64() * PI;
360 state[i] *= Complex64::new(phase.cos(), phase.sin());
361 }
362 }
363 }
364 1 => {
365 // Small rotation on neighbor when primary qubit is |1⟩
366 for i in 0..dim {
367 if (i >> primary_idx) & 1 == 1 {
368 // Primary qubit is |1⟩, apply partial X rotation to neighbor
369 let neighbor_bit = (i >> neighbor_idx) & 1;
370 let flipped_i = i ^ (1 << neighbor_idx);
371
372 // Small, random amplitude swap
373 let theta: f64 = fastrand::f64() * 0.2; // Small angle
374 let cos_theta = theta.cos();
375 let sin_theta = theta.sin();
376
377 let amp_original = state_copy[i];
378 let amp_flipped = state_copy[flipped_i];
379
380 if neighbor_bit == 0 {
381 state[i] = amp_original * Complex64::new(cos_theta, 0.0)
382 + amp_flipped * Complex64::new(sin_theta, 0.0);
383 state[flipped_i] = amp_original * Complex64::new(-sin_theta, 0.0)
384 + amp_flipped * Complex64::new(cos_theta, 0.0);
385 } else {
386 state[i] = amp_original * Complex64::new(cos_theta, 0.0)
387 - amp_flipped * Complex64::new(sin_theta, 0.0);
388 state[flipped_i] = amp_original * Complex64::new(sin_theta, 0.0)
389 + amp_flipped * Complex64::new(cos_theta, 0.0);
390 }
391 }
392 }
393 }
394 _ => unreachable!(),
395 }
396 }
397
398 // Normalize the state
399 NoiseChannelType::normalize_state(state);
400
401 Ok(())
402 }
403
404 fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
405 // Crosstalk noise is complex and typically needs multiple Kraus operators
406 // This is a placeholder for a full implementation
407 vec![vec![Complex64::new(1.0, 0.0)]]
408 }
409
410 fn probability(&self) -> f64 {
411 self.strength
412 }
413}
414
415/// Extension to `NoiseChannelType` to include advanced noise channels
416#[derive(Debug, Clone)]
417pub enum AdvancedNoiseChannelType {
418 /// Base noise channel types
419 Base(NoiseChannelType),
420
421 /// Two-qubit depolarizing channel
422 TwoQubitDepolarizing(TwoQubitDepolarizingChannel),
423
424 /// Thermal relaxation channel
425 ThermalRelaxation(ThermalRelaxationChannel),
426
427 /// Crosstalk channel
428 Crosstalk(CrosstalkChannel),
429}
430
431impl AdvancedNoiseChannelType {
432 /// Get the name of the noise channel
433 #[must_use]
434 pub fn name(&self) -> &'static str {
435 match self {
436 Self::Base(ch) => ch.name(),
437 Self::TwoQubitDepolarizing(ch) => ch.name(),
438 Self::ThermalRelaxation(ch) => ch.name(),
439 Self::Crosstalk(ch) => ch.name(),
440 }
441 }
442
443 /// Get the qubits this channel affects
444 #[must_use]
445 pub fn qubits(&self) -> Vec<QubitId> {
446 match self {
447 Self::Base(ch) => ch.qubits(),
448 Self::TwoQubitDepolarizing(ch) => ch.qubits(),
449 Self::ThermalRelaxation(ch) => ch.qubits(),
450 Self::Crosstalk(ch) => ch.qubits(),
451 }
452 }
453
454 /// Apply the noise channel to a state vector
455 pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
456 match self {
457 Self::Base(ch) => ch.apply_to_statevector(state),
458 Self::TwoQubitDepolarizing(ch) => ch.apply_to_statevector(state),
459 Self::ThermalRelaxation(ch) => ch.apply_to_statevector(state),
460 Self::Crosstalk(ch) => ch.apply_to_statevector(state),
461 }
462 }
463
464 /// Get the probability of the noise occurring
465 #[must_use]
466 pub fn probability(&self) -> f64 {
467 match self {
468 Self::Base(ch) => ch.probability(),
469 Self::TwoQubitDepolarizing(ch) => ch.probability(),
470 Self::ThermalRelaxation(ch) => ch.probability(),
471 Self::Crosstalk(ch) => ch.probability(),
472 }
473 }
474}
475
476/// Advanced noise model that supports the new noise channel types
477#[derive(Debug, Clone)]
478pub struct AdvancedNoiseModel {
479 /// List of noise channels
480 pub channels: Vec<AdvancedNoiseChannelType>,
481
482 /// Whether the noise is applied after each gate
483 pub per_gate: bool,
484}
485
486impl AdvancedNoiseModel {
487 /// Create a new empty noise model
488 #[must_use]
489 pub const fn new(per_gate: bool) -> Self {
490 Self {
491 channels: Vec::new(),
492 per_gate,
493 }
494 }
495
496 /// Add a basic noise channel to the model
497 pub fn add_base_channel(&mut self, channel: NoiseChannelType) -> &mut Self {
498 self.channels.push(AdvancedNoiseChannelType::Base(channel));
499 self
500 }
501
502 /// Add a two-qubit depolarizing noise channel to the model
503 pub fn add_two_qubit_depolarizing(
504 &mut self,
505 channel: TwoQubitDepolarizingChannel,
506 ) -> &mut Self {
507 self.channels
508 .push(AdvancedNoiseChannelType::TwoQubitDepolarizing(channel));
509 self
510 }
511
512 /// Add a thermal relaxation noise channel to the model
513 pub fn add_thermal_relaxation(&mut self, channel: ThermalRelaxationChannel) -> &mut Self {
514 self.channels
515 .push(AdvancedNoiseChannelType::ThermalRelaxation(channel));
516 self
517 }
518
519 /// Add a crosstalk noise channel to the model
520 pub fn add_crosstalk(&mut self, channel: CrosstalkChannel) -> &mut Self {
521 self.channels
522 .push(AdvancedNoiseChannelType::Crosstalk(channel));
523 self
524 }
525
526 /// Apply all noise channels to a state vector
527 pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
528 for channel in &self.channels {
529 channel.apply_to_statevector(state)?;
530 }
531
532 // Normalize the state vector after applying all noise channels
533 NoiseChannelType::normalize_state(state);
534
535 Ok(())
536 }
537
538 /// Get the total number of channels
539 #[must_use]
540 pub fn num_channels(&self) -> usize {
541 self.channels.len()
542 }
543
544 /// Convert to basic noise model (for backward compatibility)
545 #[must_use]
546 pub fn to_basic_model(&self) -> NoiseModel {
547 let mut model = NoiseModel::new(self.per_gate);
548
549 for channel in &self.channels {
550 if let AdvancedNoiseChannelType::Base(ch) = channel {
551 model.channels.push(ch.clone());
552 }
553 }
554
555 model
556 }
557}
558
559impl Default for AdvancedNoiseModel {
560 fn default() -> Self {
561 Self::new(true)
562 }
563}
564
565/// Builder for realistic device noise models
566pub struct RealisticNoiseModelBuilder {
567 model: AdvancedNoiseModel,
568}
569
570impl RealisticNoiseModelBuilder {
571 /// Create a new noise model builder
572 #[must_use]
573 pub const fn new(per_gate: bool) -> Self {
574 Self {
575 model: AdvancedNoiseModel::new(per_gate),
576 }
577 }
578
579 /// Add realistic IBM Quantum device noise parameters
580 #[must_use]
581 pub fn with_ibm_device_noise(mut self, qubits: &[QubitId], device_name: &str) -> Self {
582 match device_name {
583 "ibmq_lima" | "ibmq_belem" | "ibmq_quito" => {
584 // 5-qubit IBM Quantum Falcon processors
585 // Parameters are approximate and based on typical values
586
587 // Relaxation and dephasing times
588 let t1_values = [115e-6, 100e-6, 120e-6, 105e-6, 110e-6]; // ~100 microseconds
589 let t2_values = [95e-6, 80e-6, 100e-6, 90e-6, 85e-6]; // ~90 microseconds
590
591 // Single-qubit gates
592 let gate_time_1q = 35e-9; // 35 nanoseconds
593 let gate_error_1q = 0.001; // 0.1% error rate
594
595 // Two-qubit gates (CNOT)
596 // let _gate_time_2q = 300e-9; // 300 nanoseconds
597 let gate_error_2q = 0.01; // 1% error rate
598
599 // Readout errors
600 let readout_error = 0.025; // 2.5% error
601
602 // Add individual qubit noise
603 for (i, &qubit) in qubits.iter().enumerate().take(5) {
604 let t1 = t1_values[i % 5];
605 let t2 = t2_values[i % 5];
606
607 // Add thermal relaxation
608 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
609 target: qubit,
610 t1,
611 t2,
612 gate_time: gate_time_1q,
613 excited_state_population: 0.01, // ~1% thermal excitation
614 });
615
616 // Add depolarizing noise for single-qubit gates
617 self.model.add_base_channel(NoiseChannelType::Depolarizing(
618 crate::noise::DepolarizingChannel {
619 target: qubit,
620 probability: gate_error_1q,
621 },
622 ));
623
624 // Add readout error as a bit flip channel
625 self.model.add_base_channel(NoiseChannelType::BitFlip(
626 crate::noise::BitFlipChannel {
627 target: qubit,
628 probability: readout_error,
629 },
630 ));
631 }
632
633 // Add two-qubit gate noise (for nearest-neighbor connectivity)
634 for i in 0..qubits.len().saturating_sub(1) {
635 let q1 = qubits[i];
636 let q2 = qubits[i + 1];
637
638 // Add two-qubit depolarizing noise
639 self.model
640 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
641 qubit1: q1,
642 qubit2: q2,
643 probability: gate_error_2q,
644 });
645
646 // Add crosstalk between adjacent qubits
647 self.model.add_crosstalk(CrosstalkChannel {
648 primary: q1,
649 neighbor: q2,
650 strength: 0.003, // 0.3% crosstalk
651 });
652 }
653 }
654 "ibmq_bogota" | "ibmq_santiago" | "ibmq_casablanca" => {
655 // 5-qubit IBM Quantum Falcon processors (newer)
656 // Parameters are approximate and based on typical values
657
658 // Relaxation and dephasing times
659 let t1_values = [140e-6, 130e-6, 145e-6, 135e-6, 150e-6]; // ~140 microseconds
660 let t2_values = [120e-6, 110e-6, 125e-6, 115e-6, 130e-6]; // ~120 microseconds
661
662 // Single-qubit gates
663 let gate_time_1q = 30e-9; // 30 nanoseconds
664 let gate_error_1q = 0.0005; // 0.05% error rate
665
666 // Two-qubit gates (CNOT)
667 // let _gate_time_2q = 250e-9; // 250 nanoseconds
668 let gate_error_2q = 0.008; // 0.8% error rate
669
670 // Readout errors
671 let readout_error = 0.02; // 2% error
672
673 // Add individual qubit noise
674 for (i, &qubit) in qubits.iter().enumerate().take(5) {
675 let t1 = t1_values[i % 5];
676 let t2 = t2_values[i % 5];
677
678 // Add thermal relaxation
679 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
680 target: qubit,
681 t1,
682 t2,
683 gate_time: gate_time_1q,
684 excited_state_population: 0.008, // ~0.8% thermal excitation
685 });
686
687 // Add depolarizing noise for single-qubit gates
688 self.model.add_base_channel(NoiseChannelType::Depolarizing(
689 crate::noise::DepolarizingChannel {
690 target: qubit,
691 probability: gate_error_1q,
692 },
693 ));
694
695 // Add readout error as a bit flip channel
696 self.model.add_base_channel(NoiseChannelType::BitFlip(
697 crate::noise::BitFlipChannel {
698 target: qubit,
699 probability: readout_error,
700 },
701 ));
702 }
703
704 // Add two-qubit gate noise (for nearest-neighbor connectivity)
705 for i in 0..qubits.len().saturating_sub(1) {
706 let q1 = qubits[i];
707 let q2 = qubits[i + 1];
708
709 // Add two-qubit depolarizing noise
710 self.model
711 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
712 qubit1: q1,
713 qubit2: q2,
714 probability: gate_error_2q,
715 });
716
717 // Add crosstalk between adjacent qubits
718 self.model.add_crosstalk(CrosstalkChannel {
719 primary: q1,
720 neighbor: q2,
721 strength: 0.002, // 0.2% crosstalk
722 });
723 }
724 }
725 "ibm_cairo" | "ibm_hanoi" | "ibm_auckland" => {
726 // 27-qubit IBM Quantum Falcon processors
727 // Parameters are approximate and based on typical values
728
729 // Relaxation and dephasing times (average values)
730 let t1 = 130e-6; // 130 microseconds
731 let t2 = 100e-6; // 100 microseconds
732
733 // Single-qubit gates
734 let gate_time_1q = 35e-9; // 35 nanoseconds
735 let gate_error_1q = 0.0004; // 0.04% error rate
736
737 // Two-qubit gates (CNOT)
738 // let _gate_time_2q = 275e-9; // 275 nanoseconds
739 let gate_error_2q = 0.007; // 0.7% error rate
740
741 // Readout errors
742 let readout_error = 0.018; // 1.8% error
743
744 // Add individual qubit noise
745 for &qubit in qubits {
746 // Add thermal relaxation
747 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
748 target: qubit,
749 t1,
750 t2,
751 gate_time: gate_time_1q,
752 excited_state_population: 0.007, // ~0.7% thermal excitation
753 });
754
755 // Add depolarizing noise for single-qubit gates
756 self.model.add_base_channel(NoiseChannelType::Depolarizing(
757 crate::noise::DepolarizingChannel {
758 target: qubit,
759 probability: gate_error_1q,
760 },
761 ));
762
763 // Add readout error as a bit flip channel
764 self.model.add_base_channel(NoiseChannelType::BitFlip(
765 crate::noise::BitFlipChannel {
766 target: qubit,
767 probability: readout_error,
768 },
769 ));
770 }
771
772 // Add two-qubit gate noise (for nearest-neighbor connectivity)
773 for i in 0..qubits.len().saturating_sub(1) {
774 let q1 = qubits[i];
775 let q2 = qubits[i + 1];
776
777 // Add two-qubit depolarizing noise
778 self.model
779 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
780 qubit1: q1,
781 qubit2: q2,
782 probability: gate_error_2q,
783 });
784
785 // Add crosstalk between adjacent qubits
786 self.model.add_crosstalk(CrosstalkChannel {
787 primary: q1,
788 neighbor: q2,
789 strength: 0.0015, // 0.15% crosstalk
790 });
791 }
792 }
793 "ibm_washington" | "ibm_eagle" => {
794 // 127-qubit IBM Quantum Eagle processors
795 // Parameters are approximate and based on typical values
796
797 // Relaxation and dephasing times (average values)
798 let t1 = 150e-6; // 150 microseconds
799 let t2 = 120e-6; // 120 microseconds
800
801 // Single-qubit gates
802 let gate_time_1q = 30e-9; // 30 nanoseconds
803 let gate_error_1q = 0.0003; // 0.03% error rate
804
805 // Two-qubit gates (CNOT)
806 // let _gate_time_2q = 220e-9; // 220 nanoseconds
807 let gate_error_2q = 0.006; // 0.6% error rate
808
809 // Readout errors
810 let readout_error = 0.015; // 1.5% error
811
812 // Add individual qubit noise
813 for &qubit in qubits {
814 // Add thermal relaxation
815 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
816 target: qubit,
817 t1,
818 t2,
819 gate_time: gate_time_1q,
820 excited_state_population: 0.006, // ~0.6% thermal excitation
821 });
822
823 // Add depolarizing noise for single-qubit gates
824 self.model.add_base_channel(NoiseChannelType::Depolarizing(
825 crate::noise::DepolarizingChannel {
826 target: qubit,
827 probability: gate_error_1q,
828 },
829 ));
830
831 // Add readout error as a bit flip channel
832 self.model.add_base_channel(NoiseChannelType::BitFlip(
833 crate::noise::BitFlipChannel {
834 target: qubit,
835 probability: readout_error,
836 },
837 ));
838 }
839
840 // Add two-qubit gate noise (for nearest-neighbor connectivity)
841 for i in 0..qubits.len().saturating_sub(1) {
842 let q1 = qubits[i];
843 let q2 = qubits[i + 1];
844
845 // Add two-qubit depolarizing noise
846 self.model
847 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
848 qubit1: q1,
849 qubit2: q2,
850 probability: gate_error_2q,
851 });
852
853 // Add crosstalk between adjacent qubits
854 self.model.add_crosstalk(CrosstalkChannel {
855 primary: q1,
856 neighbor: q2,
857 strength: 0.001, // 0.1% crosstalk
858 });
859 }
860 }
861 _ => {
862 // Generic IBM Quantum device (conservative estimates)
863 // Parameters are approximate and based on typical values
864
865 // Relaxation and dephasing times (average values)
866 let t1 = 100e-6; // 100 microseconds
867 let t2 = 80e-6; // 80 microseconds
868
869 // Single-qubit gates
870 let gate_time_1q = 40e-9; // 40 nanoseconds
871 let gate_error_1q = 0.001; // 0.1% error rate
872
873 // Two-qubit gates (CNOT)
874 // let _gate_time_2q = 300e-9; // 300 nanoseconds
875 let gate_error_2q = 0.01; // 1% error rate
876
877 // Readout errors
878 let readout_error = 0.025; // 2.5% error
879
880 // Add individual qubit noise
881 for &qubit in qubits {
882 // Add thermal relaxation
883 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
884 target: qubit,
885 t1,
886 t2,
887 gate_time: gate_time_1q,
888 excited_state_population: 0.01, // ~1% thermal excitation
889 });
890
891 // Add depolarizing noise for single-qubit gates
892 self.model.add_base_channel(NoiseChannelType::Depolarizing(
893 crate::noise::DepolarizingChannel {
894 target: qubit,
895 probability: gate_error_1q,
896 },
897 ));
898
899 // Add readout error as a bit flip channel
900 self.model.add_base_channel(NoiseChannelType::BitFlip(
901 crate::noise::BitFlipChannel {
902 target: qubit,
903 probability: readout_error,
904 },
905 ));
906 }
907
908 // Add two-qubit gate noise (for nearest-neighbor connectivity)
909 for i in 0..qubits.len().saturating_sub(1) {
910 let q1 = qubits[i];
911 let q2 = qubits[i + 1];
912
913 // Add two-qubit depolarizing noise
914 self.model
915 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
916 qubit1: q1,
917 qubit2: q2,
918 probability: gate_error_2q,
919 });
920
921 // Add crosstalk between adjacent qubits
922 self.model.add_crosstalk(CrosstalkChannel {
923 primary: q1,
924 neighbor: q2,
925 strength: 0.003, // 0.3% crosstalk
926 });
927 }
928 }
929 }
930
931 self
932 }
933
934 /// Add realistic Rigetti device noise parameters
935 #[must_use]
936 pub fn with_rigetti_device_noise(mut self, qubits: &[QubitId], device_name: &str) -> Self {
937 match device_name {
938 "Aspen-M-3" | "Aspen-M-2" => {
939 // Rigetti Aspen-M series processors
940 // Parameters are approximate and based on typical values
941
942 // Relaxation and dephasing times (average values)
943 let t1 = 20e-6; // 20 microseconds
944 let t2 = 15e-6; // 15 microseconds
945
946 // Single-qubit gates
947 let gate_time_1q = 50e-9; // 50 nanoseconds
948 let gate_error_1q = 0.0015; // 0.15% error rate
949
950 // Two-qubit gates (CZ)
951 // let _gate_time_2q = 220e-9; // 220 nanoseconds
952 let gate_error_2q = 0.02; // 2% error rate
953
954 // Readout errors
955 let readout_error = 0.03; // 3% error
956
957 // Add individual qubit noise
958 for &qubit in qubits {
959 // Add thermal relaxation
960 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
961 target: qubit,
962 t1,
963 t2,
964 gate_time: gate_time_1q,
965 excited_state_population: 0.02, // ~2% thermal excitation
966 });
967
968 // Add depolarizing noise for single-qubit gates
969 self.model.add_base_channel(NoiseChannelType::Depolarizing(
970 crate::noise::DepolarizingChannel {
971 target: qubit,
972 probability: gate_error_1q,
973 },
974 ));
975
976 // Add readout error as a bit flip channel
977 self.model.add_base_channel(NoiseChannelType::BitFlip(
978 crate::noise::BitFlipChannel {
979 target: qubit,
980 probability: readout_error,
981 },
982 ));
983 }
984
985 // Add two-qubit gate noise (for nearest-neighbor connectivity)
986 for i in 0..qubits.len().saturating_sub(1) {
987 let q1 = qubits[i];
988 let q2 = qubits[i + 1];
989
990 // Add two-qubit depolarizing noise
991 self.model
992 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
993 qubit1: q1,
994 qubit2: q2,
995 probability: gate_error_2q,
996 });
997
998 // Add crosstalk between adjacent qubits
999 self.model.add_crosstalk(CrosstalkChannel {
1000 primary: q1,
1001 neighbor: q2,
1002 strength: 0.004, // 0.4% crosstalk
1003 });
1004 }
1005 }
1006 _ => {
1007 // Generic Rigetti device (conservative estimates)
1008 // Parameters are approximate and based on typical values
1009
1010 // Relaxation and dephasing times (average values)
1011 let t1 = 15e-6; // 15 microseconds
1012 let t2 = 12e-6; // 12 microseconds
1013
1014 // Single-qubit gates
1015 let gate_time_1q = 60e-9; // 60 nanoseconds
1016 let gate_error_1q = 0.002; // 0.2% error rate
1017
1018 // Two-qubit gates (CZ)
1019 // let _gate_time_2q = 250e-9; // 250 nanoseconds
1020 let gate_error_2q = 0.025; // 2.5% error rate
1021
1022 // Readout errors
1023 let readout_error = 0.035; // 3.5% error
1024
1025 // Add individual qubit noise
1026 for &qubit in qubits {
1027 // Add thermal relaxation
1028 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
1029 target: qubit,
1030 t1,
1031 t2,
1032 gate_time: gate_time_1q,
1033 excited_state_population: 0.025, // ~2.5% thermal excitation
1034 });
1035
1036 // Add depolarizing noise for single-qubit gates
1037 self.model.add_base_channel(NoiseChannelType::Depolarizing(
1038 crate::noise::DepolarizingChannel {
1039 target: qubit,
1040 probability: gate_error_1q,
1041 },
1042 ));
1043
1044 // Add readout error as a bit flip channel
1045 self.model.add_base_channel(NoiseChannelType::BitFlip(
1046 crate::noise::BitFlipChannel {
1047 target: qubit,
1048 probability: readout_error,
1049 },
1050 ));
1051 }
1052
1053 // Add two-qubit gate noise (for nearest-neighbor connectivity)
1054 for i in 0..qubits.len().saturating_sub(1) {
1055 let q1 = qubits[i];
1056 let q2 = qubits[i + 1];
1057
1058 // Add two-qubit depolarizing noise
1059 self.model
1060 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
1061 qubit1: q1,
1062 qubit2: q2,
1063 probability: gate_error_2q,
1064 });
1065
1066 // Add crosstalk between adjacent qubits
1067 self.model.add_crosstalk(CrosstalkChannel {
1068 primary: q1,
1069 neighbor: q2,
1070 strength: 0.005, // 0.5% crosstalk
1071 });
1072 }
1073 }
1074 }
1075
1076 self
1077 }
1078
1079 /// Add custom thermal relaxation parameters
1080 #[must_use]
1081 pub fn with_custom_thermal_relaxation(
1082 mut self,
1083 qubits: &[QubitId],
1084 t1: Duration,
1085 t2: Duration,
1086 gate_time: Duration,
1087 ) -> Self {
1088 let t1_seconds = t1.as_secs_f64();
1089 let t2_seconds = t2.as_secs_f64();
1090 let gate_time_seconds = gate_time.as_secs_f64();
1091
1092 for &qubit in qubits {
1093 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
1094 target: qubit,
1095 t1: t1_seconds,
1096 t2: t2_seconds,
1097 gate_time: gate_time_seconds,
1098 excited_state_population: 0.01, // Default 1% thermal excitation
1099 });
1100 }
1101
1102 self
1103 }
1104
1105 /// Add custom two-qubit depolarizing noise
1106 #[must_use]
1107 pub fn with_custom_two_qubit_noise(
1108 mut self,
1109 qubit_pairs: &[(QubitId, QubitId)],
1110 probability: f64,
1111 ) -> Self {
1112 for &(q1, q2) in qubit_pairs {
1113 self.model
1114 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
1115 qubit1: q1,
1116 qubit2: q2,
1117 probability,
1118 });
1119 }
1120
1121 self
1122 }
1123
1124 /// Add custom crosstalk noise between pairs of qubits
1125 #[must_use]
1126 pub fn with_custom_crosstalk(
1127 mut self,
1128 qubit_pairs: &[(QubitId, QubitId)],
1129 strength: f64,
1130 ) -> Self {
1131 for &(q1, q2) in qubit_pairs {
1132 self.model.add_crosstalk(CrosstalkChannel {
1133 primary: q1,
1134 neighbor: q2,
1135 strength,
1136 });
1137 }
1138
1139 self
1140 }
1141
1142 /// Build the noise model
1143 #[must_use]
1144 pub fn build(self) -> AdvancedNoiseModel {
1145 self.model
1146 }
1147}