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 pub fn name(&self) -> &'static str {
428 match self {
429 Self::Base(ch) => ch.name(),
430 Self::TwoQubitDepolarizing(ch) => ch.name(),
431 Self::ThermalRelaxation(ch) => ch.name(),
432 Self::Crosstalk(ch) => ch.name(),
433 }
434 }
435
436 /// Get the qubits this channel affects
437 pub fn qubits(&self) -> Vec<QubitId> {
438 match self {
439 Self::Base(ch) => ch.qubits(),
440 Self::TwoQubitDepolarizing(ch) => ch.qubits(),
441 Self::ThermalRelaxation(ch) => ch.qubits(),
442 Self::Crosstalk(ch) => ch.qubits(),
443 }
444 }
445
446 /// Apply the noise channel to a state vector
447 pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
448 match self {
449 Self::Base(ch) => ch.apply_to_statevector(state),
450 Self::TwoQubitDepolarizing(ch) => ch.apply_to_statevector(state),
451 Self::ThermalRelaxation(ch) => ch.apply_to_statevector(state),
452 Self::Crosstalk(ch) => ch.apply_to_statevector(state),
453 }
454 }
455
456 /// Get the probability of the noise occurring
457 pub fn probability(&self) -> f64 {
458 match self {
459 Self::Base(ch) => ch.probability(),
460 Self::TwoQubitDepolarizing(ch) => ch.probability(),
461 Self::ThermalRelaxation(ch) => ch.probability(),
462 Self::Crosstalk(ch) => ch.probability(),
463 }
464 }
465}
466
467/// Advanced noise model that supports the new noise channel types
468#[derive(Debug, Clone)]
469pub struct AdvancedNoiseModel {
470 /// List of noise channels
471 pub channels: Vec<AdvancedNoiseChannelType>,
472
473 /// Whether the noise is applied after each gate
474 pub per_gate: bool,
475}
476
477impl AdvancedNoiseModel {
478 /// Create a new empty noise model
479 pub fn new(per_gate: bool) -> Self {
480 Self {
481 channels: Vec::new(),
482 per_gate,
483 }
484 }
485
486 /// Add a basic noise channel to the model
487 pub fn add_base_channel(&mut self, channel: NoiseChannelType) -> &mut Self {
488 self.channels.push(AdvancedNoiseChannelType::Base(channel));
489 self
490 }
491
492 /// Add a two-qubit depolarizing noise channel to the model
493 pub fn add_two_qubit_depolarizing(
494 &mut self,
495 channel: TwoQubitDepolarizingChannel,
496 ) -> &mut Self {
497 self.channels
498 .push(AdvancedNoiseChannelType::TwoQubitDepolarizing(channel));
499 self
500 }
501
502 /// Add a thermal relaxation noise channel to the model
503 pub fn add_thermal_relaxation(&mut self, channel: ThermalRelaxationChannel) -> &mut Self {
504 self.channels
505 .push(AdvancedNoiseChannelType::ThermalRelaxation(channel));
506 self
507 }
508
509 /// Add a crosstalk noise channel to the model
510 pub fn add_crosstalk(&mut self, channel: CrosstalkChannel) -> &mut Self {
511 self.channels
512 .push(AdvancedNoiseChannelType::Crosstalk(channel));
513 self
514 }
515
516 /// Apply all noise channels to a state vector
517 pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
518 for channel in &self.channels {
519 channel.apply_to_statevector(state)?;
520 }
521
522 // Normalize the state vector after applying all noise channels
523 NoiseChannelType::normalize_state(state);
524
525 Ok(())
526 }
527
528 /// Get the total number of channels
529 pub fn num_channels(&self) -> usize {
530 self.channels.len()
531 }
532
533 /// Convert to basic noise model (for backward compatibility)
534 pub fn to_basic_model(&self) -> NoiseModel {
535 let mut model = NoiseModel::new(self.per_gate);
536
537 for channel in &self.channels {
538 if let AdvancedNoiseChannelType::Base(ch) = channel {
539 model.channels.push(ch.clone());
540 }
541 }
542
543 model
544 }
545}
546
547impl Default for AdvancedNoiseModel {
548 fn default() -> Self {
549 Self::new(true)
550 }
551}
552
553/// Builder for realistic device noise models
554pub struct RealisticNoiseModelBuilder {
555 model: AdvancedNoiseModel,
556}
557
558impl RealisticNoiseModelBuilder {
559 /// Create a new noise model builder
560 pub fn new(per_gate: bool) -> Self {
561 Self {
562 model: AdvancedNoiseModel::new(per_gate),
563 }
564 }
565
566 /// Add realistic IBM Quantum device noise parameters
567 pub fn with_ibm_device_noise(mut self, qubits: &[QubitId], device_name: &str) -> Self {
568 match device_name {
569 "ibmq_lima" | "ibmq_belem" | "ibmq_quito" => {
570 // 5-qubit IBM Quantum Falcon processors
571 // Parameters are approximate and based on typical values
572
573 // Relaxation and dephasing times
574 let t1_values = [115e-6, 100e-6, 120e-6, 105e-6, 110e-6]; // ~100 microseconds
575 let t2_values = [95e-6, 80e-6, 100e-6, 90e-6, 85e-6]; // ~90 microseconds
576
577 // Single-qubit gates
578 let gate_time_1q = 35e-9; // 35 nanoseconds
579 let gate_error_1q = 0.001; // 0.1% error rate
580
581 // Two-qubit gates (CNOT)
582 let _gate_time_2q = 300e-9; // 300 nanoseconds
583 let gate_error_2q = 0.01; // 1% error rate
584
585 // Readout errors
586 let readout_error = 0.025; // 2.5% error
587
588 // Add individual qubit noise
589 for (i, &qubit) in qubits.iter().enumerate().take(5) {
590 let t1 = t1_values[i % 5];
591 let t2 = t2_values[i % 5];
592
593 // Add thermal relaxation
594 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
595 target: qubit,
596 t1,
597 t2,
598 gate_time: gate_time_1q,
599 excited_state_population: 0.01, // ~1% thermal excitation
600 });
601
602 // Add depolarizing noise for single-qubit gates
603 self.model.add_base_channel(NoiseChannelType::Depolarizing(
604 crate::noise::DepolarizingChannel {
605 target: qubit,
606 probability: gate_error_1q,
607 },
608 ));
609
610 // Add readout error as a bit flip channel
611 self.model.add_base_channel(NoiseChannelType::BitFlip(
612 crate::noise::BitFlipChannel {
613 target: qubit,
614 probability: readout_error,
615 },
616 ));
617 }
618
619 // Add two-qubit gate noise (for nearest-neighbor connectivity)
620 for i in 0..qubits.len().saturating_sub(1) {
621 let q1 = qubits[i];
622 let q2 = qubits[i + 1];
623
624 // Add two-qubit depolarizing noise
625 self.model
626 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
627 qubit1: q1,
628 qubit2: q2,
629 probability: gate_error_2q,
630 });
631
632 // Add crosstalk between adjacent qubits
633 self.model.add_crosstalk(CrosstalkChannel {
634 primary: q1,
635 neighbor: q2,
636 strength: 0.003, // 0.3% crosstalk
637 });
638 }
639 }
640 "ibmq_bogota" | "ibmq_santiago" | "ibmq_casablanca" => {
641 // 5-qubit IBM Quantum Falcon processors (newer)
642 // Parameters are approximate and based on typical values
643
644 // Relaxation and dephasing times
645 let t1_values = [140e-6, 130e-6, 145e-6, 135e-6, 150e-6]; // ~140 microseconds
646 let t2_values = [120e-6, 110e-6, 125e-6, 115e-6, 130e-6]; // ~120 microseconds
647
648 // Single-qubit gates
649 let gate_time_1q = 30e-9; // 30 nanoseconds
650 let gate_error_1q = 0.0005; // 0.05% error rate
651
652 // Two-qubit gates (CNOT)
653 let _gate_time_2q = 250e-9; // 250 nanoseconds
654 let gate_error_2q = 0.008; // 0.8% error rate
655
656 // Readout errors
657 let readout_error = 0.02; // 2% error
658
659 // Add individual qubit noise
660 for (i, &qubit) in qubits.iter().enumerate().take(5) {
661 let t1 = t1_values[i % 5];
662 let t2 = t2_values[i % 5];
663
664 // Add thermal relaxation
665 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
666 target: qubit,
667 t1,
668 t2,
669 gate_time: gate_time_1q,
670 excited_state_population: 0.008, // ~0.8% thermal excitation
671 });
672
673 // Add depolarizing noise for single-qubit gates
674 self.model.add_base_channel(NoiseChannelType::Depolarizing(
675 crate::noise::DepolarizingChannel {
676 target: qubit,
677 probability: gate_error_1q,
678 },
679 ));
680
681 // Add readout error as a bit flip channel
682 self.model.add_base_channel(NoiseChannelType::BitFlip(
683 crate::noise::BitFlipChannel {
684 target: qubit,
685 probability: readout_error,
686 },
687 ));
688 }
689
690 // Add two-qubit gate noise (for nearest-neighbor connectivity)
691 for i in 0..qubits.len().saturating_sub(1) {
692 let q1 = qubits[i];
693 let q2 = qubits[i + 1];
694
695 // Add two-qubit depolarizing noise
696 self.model
697 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
698 qubit1: q1,
699 qubit2: q2,
700 probability: gate_error_2q,
701 });
702
703 // Add crosstalk between adjacent qubits
704 self.model.add_crosstalk(CrosstalkChannel {
705 primary: q1,
706 neighbor: q2,
707 strength: 0.002, // 0.2% crosstalk
708 });
709 }
710 }
711 "ibm_cairo" | "ibm_hanoi" | "ibm_auckland" => {
712 // 27-qubit IBM Quantum Falcon processors
713 // Parameters are approximate and based on typical values
714
715 // Relaxation and dephasing times (average values)
716 let t1 = 130e-6; // 130 microseconds
717 let t2 = 100e-6; // 100 microseconds
718
719 // Single-qubit gates
720 let gate_time_1q = 35e-9; // 35 nanoseconds
721 let gate_error_1q = 0.0004; // 0.04% error rate
722
723 // Two-qubit gates (CNOT)
724 let _gate_time_2q = 275e-9; // 275 nanoseconds
725 let gate_error_2q = 0.007; // 0.7% error rate
726
727 // Readout errors
728 let readout_error = 0.018; // 1.8% error
729
730 // Add individual qubit noise
731 for &qubit in qubits {
732 // Add thermal relaxation
733 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
734 target: qubit,
735 t1,
736 t2,
737 gate_time: gate_time_1q,
738 excited_state_population: 0.007, // ~0.7% thermal excitation
739 });
740
741 // Add depolarizing noise for single-qubit gates
742 self.model.add_base_channel(NoiseChannelType::Depolarizing(
743 crate::noise::DepolarizingChannel {
744 target: qubit,
745 probability: gate_error_1q,
746 },
747 ));
748
749 // Add readout error as a bit flip channel
750 self.model.add_base_channel(NoiseChannelType::BitFlip(
751 crate::noise::BitFlipChannel {
752 target: qubit,
753 probability: readout_error,
754 },
755 ));
756 }
757
758 // Add two-qubit gate noise (for nearest-neighbor connectivity)
759 for i in 0..qubits.len().saturating_sub(1) {
760 let q1 = qubits[i];
761 let q2 = qubits[i + 1];
762
763 // Add two-qubit depolarizing noise
764 self.model
765 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
766 qubit1: q1,
767 qubit2: q2,
768 probability: gate_error_2q,
769 });
770
771 // Add crosstalk between adjacent qubits
772 self.model.add_crosstalk(CrosstalkChannel {
773 primary: q1,
774 neighbor: q2,
775 strength: 0.0015, // 0.15% crosstalk
776 });
777 }
778 }
779 "ibm_washington" | "ibm_eagle" => {
780 // 127-qubit IBM Quantum Eagle processors
781 // Parameters are approximate and based on typical values
782
783 // Relaxation and dephasing times (average values)
784 let t1 = 150e-6; // 150 microseconds
785 let t2 = 120e-6; // 120 microseconds
786
787 // Single-qubit gates
788 let gate_time_1q = 30e-9; // 30 nanoseconds
789 let gate_error_1q = 0.0003; // 0.03% error rate
790
791 // Two-qubit gates (CNOT)
792 let _gate_time_2q = 220e-9; // 220 nanoseconds
793 let gate_error_2q = 0.006; // 0.6% error rate
794
795 // Readout errors
796 let readout_error = 0.015; // 1.5% error
797
798 // Add individual qubit noise
799 for &qubit in qubits {
800 // Add thermal relaxation
801 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
802 target: qubit,
803 t1,
804 t2,
805 gate_time: gate_time_1q,
806 excited_state_population: 0.006, // ~0.6% thermal excitation
807 });
808
809 // Add depolarizing noise for single-qubit gates
810 self.model.add_base_channel(NoiseChannelType::Depolarizing(
811 crate::noise::DepolarizingChannel {
812 target: qubit,
813 probability: gate_error_1q,
814 },
815 ));
816
817 // Add readout error as a bit flip channel
818 self.model.add_base_channel(NoiseChannelType::BitFlip(
819 crate::noise::BitFlipChannel {
820 target: qubit,
821 probability: readout_error,
822 },
823 ));
824 }
825
826 // Add two-qubit gate noise (for nearest-neighbor connectivity)
827 for i in 0..qubits.len().saturating_sub(1) {
828 let q1 = qubits[i];
829 let q2 = qubits[i + 1];
830
831 // Add two-qubit depolarizing noise
832 self.model
833 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
834 qubit1: q1,
835 qubit2: q2,
836 probability: gate_error_2q,
837 });
838
839 // Add crosstalk between adjacent qubits
840 self.model.add_crosstalk(CrosstalkChannel {
841 primary: q1,
842 neighbor: q2,
843 strength: 0.001, // 0.1% crosstalk
844 });
845 }
846 }
847 _ => {
848 // Generic IBM Quantum device (conservative estimates)
849 // Parameters are approximate and based on typical values
850
851 // Relaxation and dephasing times (average values)
852 let t1 = 100e-6; // 100 microseconds
853 let t2 = 80e-6; // 80 microseconds
854
855 // Single-qubit gates
856 let gate_time_1q = 40e-9; // 40 nanoseconds
857 let gate_error_1q = 0.001; // 0.1% error rate
858
859 // Two-qubit gates (CNOT)
860 let _gate_time_2q = 300e-9; // 300 nanoseconds
861 let gate_error_2q = 0.01; // 1% error rate
862
863 // Readout errors
864 let readout_error = 0.025; // 2.5% error
865
866 // Add individual qubit noise
867 for &qubit in qubits {
868 // Add thermal relaxation
869 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
870 target: qubit,
871 t1,
872 t2,
873 gate_time: gate_time_1q,
874 excited_state_population: 0.01, // ~1% thermal excitation
875 });
876
877 // Add depolarizing noise for single-qubit gates
878 self.model.add_base_channel(NoiseChannelType::Depolarizing(
879 crate::noise::DepolarizingChannel {
880 target: qubit,
881 probability: gate_error_1q,
882 },
883 ));
884
885 // Add readout error as a bit flip channel
886 self.model.add_base_channel(NoiseChannelType::BitFlip(
887 crate::noise::BitFlipChannel {
888 target: qubit,
889 probability: readout_error,
890 },
891 ));
892 }
893
894 // Add two-qubit gate noise (for nearest-neighbor connectivity)
895 for i in 0..qubits.len().saturating_sub(1) {
896 let q1 = qubits[i];
897 let q2 = qubits[i + 1];
898
899 // Add two-qubit depolarizing noise
900 self.model
901 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
902 qubit1: q1,
903 qubit2: q2,
904 probability: gate_error_2q,
905 });
906
907 // Add crosstalk between adjacent qubits
908 self.model.add_crosstalk(CrosstalkChannel {
909 primary: q1,
910 neighbor: q2,
911 strength: 0.003, // 0.3% crosstalk
912 });
913 }
914 }
915 }
916
917 self
918 }
919
920 /// Add realistic Rigetti device noise parameters
921 pub fn with_rigetti_device_noise(mut self, qubits: &[QubitId], device_name: &str) -> Self {
922 match device_name {
923 "Aspen-M-3" | "Aspen-M-2" => {
924 // Rigetti Aspen-M series processors
925 // Parameters are approximate and based on typical values
926
927 // Relaxation and dephasing times (average values)
928 let t1 = 20e-6; // 20 microseconds
929 let t2 = 15e-6; // 15 microseconds
930
931 // Single-qubit gates
932 let gate_time_1q = 50e-9; // 50 nanoseconds
933 let gate_error_1q = 0.0015; // 0.15% error rate
934
935 // Two-qubit gates (CZ)
936 let _gate_time_2q = 220e-9; // 220 nanoseconds
937 let gate_error_2q = 0.02; // 2% error rate
938
939 // Readout errors
940 let readout_error = 0.03; // 3% error
941
942 // Add individual qubit noise
943 for &qubit in qubits {
944 // Add thermal relaxation
945 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
946 target: qubit,
947 t1,
948 t2,
949 gate_time: gate_time_1q,
950 excited_state_population: 0.02, // ~2% thermal excitation
951 });
952
953 // Add depolarizing noise for single-qubit gates
954 self.model.add_base_channel(NoiseChannelType::Depolarizing(
955 crate::noise::DepolarizingChannel {
956 target: qubit,
957 probability: gate_error_1q,
958 },
959 ));
960
961 // Add readout error as a bit flip channel
962 self.model.add_base_channel(NoiseChannelType::BitFlip(
963 crate::noise::BitFlipChannel {
964 target: qubit,
965 probability: readout_error,
966 },
967 ));
968 }
969
970 // Add two-qubit gate noise (for nearest-neighbor connectivity)
971 for i in 0..qubits.len().saturating_sub(1) {
972 let q1 = qubits[i];
973 let q2 = qubits[i + 1];
974
975 // Add two-qubit depolarizing noise
976 self.model
977 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
978 qubit1: q1,
979 qubit2: q2,
980 probability: gate_error_2q,
981 });
982
983 // Add crosstalk between adjacent qubits
984 self.model.add_crosstalk(CrosstalkChannel {
985 primary: q1,
986 neighbor: q2,
987 strength: 0.004, // 0.4% crosstalk
988 });
989 }
990 }
991 _ => {
992 // Generic Rigetti device (conservative estimates)
993 // Parameters are approximate and based on typical values
994
995 // Relaxation and dephasing times (average values)
996 let t1 = 15e-6; // 15 microseconds
997 let t2 = 12e-6; // 12 microseconds
998
999 // Single-qubit gates
1000 let gate_time_1q = 60e-9; // 60 nanoseconds
1001 let gate_error_1q = 0.002; // 0.2% error rate
1002
1003 // Two-qubit gates (CZ)
1004 let _gate_time_2q = 250e-9; // 250 nanoseconds
1005 let gate_error_2q = 0.025; // 2.5% error rate
1006
1007 // Readout errors
1008 let readout_error = 0.035; // 3.5% error
1009
1010 // Add individual qubit noise
1011 for &qubit in qubits {
1012 // Add thermal relaxation
1013 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
1014 target: qubit,
1015 t1,
1016 t2,
1017 gate_time: gate_time_1q,
1018 excited_state_population: 0.025, // ~2.5% thermal excitation
1019 });
1020
1021 // Add depolarizing noise for single-qubit gates
1022 self.model.add_base_channel(NoiseChannelType::Depolarizing(
1023 crate::noise::DepolarizingChannel {
1024 target: qubit,
1025 probability: gate_error_1q,
1026 },
1027 ));
1028
1029 // Add readout error as a bit flip channel
1030 self.model.add_base_channel(NoiseChannelType::BitFlip(
1031 crate::noise::BitFlipChannel {
1032 target: qubit,
1033 probability: readout_error,
1034 },
1035 ));
1036 }
1037
1038 // Add two-qubit gate noise (for nearest-neighbor connectivity)
1039 for i in 0..qubits.len().saturating_sub(1) {
1040 let q1 = qubits[i];
1041 let q2 = qubits[i + 1];
1042
1043 // Add two-qubit depolarizing noise
1044 self.model
1045 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
1046 qubit1: q1,
1047 qubit2: q2,
1048 probability: gate_error_2q,
1049 });
1050
1051 // Add crosstalk between adjacent qubits
1052 self.model.add_crosstalk(CrosstalkChannel {
1053 primary: q1,
1054 neighbor: q2,
1055 strength: 0.005, // 0.5% crosstalk
1056 });
1057 }
1058 }
1059 }
1060
1061 self
1062 }
1063
1064 /// Add custom thermal relaxation parameters
1065 pub fn with_custom_thermal_relaxation(
1066 mut self,
1067 qubits: &[QubitId],
1068 t1: Duration,
1069 t2: Duration,
1070 gate_time: Duration,
1071 ) -> Self {
1072 let t1_seconds = t1.as_secs_f64();
1073 let t2_seconds = t2.as_secs_f64();
1074 let gate_time_seconds = gate_time.as_secs_f64();
1075
1076 for &qubit in qubits {
1077 self.model.add_thermal_relaxation(ThermalRelaxationChannel {
1078 target: qubit,
1079 t1: t1_seconds,
1080 t2: t2_seconds,
1081 gate_time: gate_time_seconds,
1082 excited_state_population: 0.01, // Default 1% thermal excitation
1083 });
1084 }
1085
1086 self
1087 }
1088
1089 /// Add custom two-qubit depolarizing noise
1090 pub fn with_custom_two_qubit_noise(
1091 mut self,
1092 qubit_pairs: &[(QubitId, QubitId)],
1093 probability: f64,
1094 ) -> Self {
1095 for &(q1, q2) in qubit_pairs {
1096 self.model
1097 .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
1098 qubit1: q1,
1099 qubit2: q2,
1100 probability,
1101 });
1102 }
1103
1104 self
1105 }
1106
1107 /// Add custom crosstalk noise between pairs of qubits
1108 pub fn with_custom_crosstalk(
1109 mut self,
1110 qubit_pairs: &[(QubitId, QubitId)],
1111 strength: f64,
1112 ) -> Self {
1113 for &(q1, q2) in qubit_pairs {
1114 self.model.add_crosstalk(CrosstalkChannel {
1115 primary: q1,
1116 neighbor: q2,
1117 strength,
1118 });
1119 }
1120
1121 self
1122 }
1123
1124 /// Build the noise model
1125 pub fn build(self) -> AdvancedNoiseModel {
1126 self.model
1127 }
1128}