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