1use scirs2_core::random::Rng;
7use scirs2_core::Complex64;
8use scirs2_core::RngExt;
9use std::collections::HashMap;
10
11use quantrs2_core::{
12 error::{QuantRS2Error, QuantRS2Result},
13 gate::GateOp,
14 qubit::QubitId,
15};
16
17use crate::calibration::{
18 DeviceCalibration, QubitCalibration, QubitReadoutData, SingleQubitGateData,
19 TwoQubitGateCalibration,
20};
21use scirs2_core::random::prelude::*;
22
23#[derive(Debug, Clone)]
25pub struct CalibrationNoiseModel {
26 pub device_id: String,
28 pub qubit_noise: HashMap<QubitId, QubitNoiseParams>,
30 pub gate_noise: HashMap<String, GateNoiseParams>,
32 pub two_qubit_noise: HashMap<(QubitId, QubitId), TwoQubitNoiseParams>,
34 pub readout_noise: HashMap<QubitId, ReadoutNoiseParams>,
36 pub crosstalk: CrosstalkNoise,
38 pub temperature: f64,
40}
41
42#[derive(Debug, Clone)]
44pub struct QubitNoiseParams {
45 pub gamma_1: f64,
47 pub gamma_phi: f64,
49 pub thermal_population: f64,
51 pub frequency_drift: f64,
53 pub flicker_noise: f64,
55}
56
57#[derive(Debug, Clone)]
59pub struct GateNoiseParams {
60 pub coherent_error: f64,
62 pub incoherent_error: f64,
64 pub duration: f64,
66 pub depolarizing_rate: f64,
68 pub amplitude_noise: f64,
70 pub phase_noise: f64,
72}
73
74#[derive(Debug, Clone)]
76pub struct TwoQubitNoiseParams {
77 pub gate_noise: GateNoiseParams,
79 pub zz_coupling: f64,
81 pub spectator_crosstalk: HashMap<QubitId, f64>,
83 pub directional_error: f64,
85}
86
87#[derive(Debug, Clone)]
89pub struct ReadoutNoiseParams {
90 pub assignment_matrix: [[f64; 2]; 2],
92 pub readout_excitation: f64,
94 pub readout_relaxation: f64,
96}
97
98#[derive(Debug, Clone)]
100pub struct CrosstalkNoise {
101 pub crosstalk_matrix: Vec<Vec<f64>>,
103 pub threshold: f64,
105 pub single_qubit_crosstalk: f64,
107 pub two_qubit_crosstalk: f64,
109}
110
111impl CalibrationNoiseModel {
112 pub fn from_calibration(calibration: &DeviceCalibration) -> Self {
114 let mut qubit_noise = HashMap::new();
115 let mut readout_noise = HashMap::new();
116
117 for (qubit_id, qubit_cal) in &calibration.qubit_calibrations {
119 qubit_noise.insert(
120 *qubit_id,
121 QubitNoiseParams {
122 gamma_1: 1.0 / qubit_cal.t1,
123 gamma_phi: 1.0 / qubit_cal.t2 - 0.5 / qubit_cal.t1,
124 thermal_population: qubit_cal.thermal_population,
125 frequency_drift: 1e3, flicker_noise: 1e-6, },
128 );
129 }
130
131 for (qubit_id, readout_data) in &calibration.readout_calibration.qubit_readout {
133 let p00 = readout_data.p0_given_0;
134 let p11 = readout_data.p1_given_1;
135
136 readout_noise.insert(
137 *qubit_id,
138 ReadoutNoiseParams {
139 assignment_matrix: [[p00, 1.0 - p11], [1.0 - p00, p11]],
140 readout_excitation: 0.001, readout_relaxation: 0.002, },
143 );
144 }
145
146 let mut gate_noise = HashMap::new();
148 for (gate_name, gate_cal) in &calibration.single_qubit_gates {
149 let mut total_error = 0.0;
151 let mut total_duration = 0.0;
152 let mut count = 0;
153
154 for gate_data in gate_cal.qubit_data.values() {
155 total_error += gate_data.error_rate;
156 total_duration += gate_data.duration;
157 count += 1;
158 }
159
160 if count > 0 {
161 let avg_error = total_error / count as f64;
162 let avg_duration = total_duration / count as f64;
163
164 gate_noise.insert(
165 gate_name.clone(),
166 GateNoiseParams {
167 coherent_error: avg_error * 0.3, incoherent_error: avg_error * 0.7, duration: avg_duration,
170 depolarizing_rate: avg_error / avg_duration,
171 amplitude_noise: 0.001, phase_noise: 0.002, },
174 );
175 }
176 }
177
178 let mut two_qubit_noise = HashMap::new();
180 for ((control, target), gate_cal) in &calibration.two_qubit_gates {
181 let base_noise = GateNoiseParams {
182 coherent_error: gate_cal.error_rate * 0.4,
183 incoherent_error: gate_cal.error_rate * 0.6,
184 duration: gate_cal.duration,
185 depolarizing_rate: gate_cal.error_rate / gate_cal.duration,
186 amplitude_noise: 0.002,
187 phase_noise: 0.003,
188 };
189
190 gate_noise.insert(gate_cal.gate_name.clone(), base_noise.clone());
192
193 let mut spectator_crosstalk = HashMap::new();
195
196 for offset in [-1, 1] {
198 let spectator_id = control.id() as i32 + offset;
199 if spectator_id >= 0 && spectator_id < calibration.topology.num_qubits as i32 {
200 spectator_crosstalk.insert(QubitId(spectator_id as u32), 0.001);
201 }
202
203 let spectator_id = target.id() as i32 + offset;
204 if spectator_id >= 0 && spectator_id < calibration.topology.num_qubits as i32 {
205 spectator_crosstalk.insert(QubitId(spectator_id as u32), 0.001);
206 }
207 }
208
209 two_qubit_noise.insert(
210 (*control, *target),
211 TwoQubitNoiseParams {
212 gate_noise: base_noise,
213 zz_coupling: 0.1, spectator_crosstalk,
215 directional_error: if gate_cal.directional { 0.01 } else { 0.0 },
216 },
217 );
218 }
219
220 let crosstalk = CrosstalkNoise {
222 crosstalk_matrix: calibration.crosstalk_matrix.matrix.clone(),
223 threshold: calibration.crosstalk_matrix.significance_threshold,
224 single_qubit_crosstalk: 0.001,
225 two_qubit_crosstalk: 0.01,
226 };
227
228 let temperature = calibration
230 .qubit_calibrations
231 .values()
232 .filter_map(|q| q.temperature)
233 .sum::<f64>()
234 / calibration.qubit_calibrations.len() as f64;
235
236 Self {
237 device_id: calibration.device_id.clone(),
238 qubit_noise,
239 gate_noise,
240 two_qubit_noise,
241 readout_noise,
242 crosstalk,
243 temperature,
244 }
245 }
246
247 pub fn apply_gate_noise(
249 &self,
250 state: &mut Vec<Complex64>,
251 gate: &dyn GateOp,
252 qubits: &[QubitId],
253 duration_ns: f64,
254 rng: &mut impl Rng,
255 ) -> QuantRS2Result<()> {
256 let gate_name = gate.name();
257
258 match qubits.len() {
259 1 => self.apply_single_qubit_noise(state, gate_name, qubits[0], duration_ns, rng),
260 2 => {
261 self.apply_two_qubit_noise(state, gate_name, qubits[0], qubits[1], duration_ns, rng)
262 }
263 _ => {
264 for &qubit in qubits {
266 self.apply_single_qubit_noise(
267 state,
268 gate_name,
269 qubit,
270 duration_ns / qubits.len() as f64,
271 rng,
272 )?;
273 }
274 Ok(())
275 }
276 }
277 }
278
279 fn apply_single_qubit_noise(
281 &self,
282 state: &mut Vec<Complex64>,
283 gate_name: &str,
284 qubit: QubitId,
285 duration_ns: f64,
286 rng: &mut impl Rng,
287 ) -> QuantRS2Result<()> {
288 let gate_params = self.gate_noise.get(gate_name);
290 let qubit_params = self.qubit_noise.get(&qubit);
291
292 if let Some(params) = gate_params {
293 if params.coherent_error > 0.0 {
295 let error_angle = rng.random_range(-params.coherent_error..params.coherent_error);
296 self.apply_rotation_error(state, qubit, error_angle)?;
297 }
298
299 if params.amplitude_noise > 0.0 {
301 let amplitude_error =
302 1.0 + rng.random_range(-params.amplitude_noise..params.amplitude_noise);
303 self.apply_amplitude_scaling(state, qubit, amplitude_error)?;
304 }
305
306 if params.phase_noise > 0.0 {
308 let phase_error = rng.random_range(-params.phase_noise..params.phase_noise);
309 self.apply_phase_error(state, qubit, phase_error)?;
310 }
311 }
312
313 if let Some(qubit_params) = qubit_params {
315 let actual_duration = duration_ns / 1000.0; let decay_prob = 1.0 - (-actual_duration * qubit_params.gamma_1).exp();
319 if rng.random::<f64>() < decay_prob {
320 self.apply_amplitude_damping(state, qubit, decay_prob)?;
321 }
322
323 let dephase_prob = 1.0 - (-actual_duration * qubit_params.gamma_phi).exp();
325 if rng.random::<f64>() < dephase_prob {
326 self.apply_phase_damping(state, qubit, dephase_prob)?;
327 }
328 }
329
330 Ok(())
331 }
332
333 fn apply_two_qubit_noise(
335 &self,
336 state: &mut Vec<Complex64>,
337 gate_name: &str,
338 control: QubitId,
339 target: QubitId,
340 duration_ns: f64,
341 rng: &mut impl Rng,
342 ) -> QuantRS2Result<()> {
343 if let Some(params) = self.two_qubit_noise.get(&(control, target)) {
344 let gate_params = ¶ms.gate_noise;
346
347 if gate_params.coherent_error > 0.0 {
349 let error =
350 rng.random_range(-gate_params.coherent_error..gate_params.coherent_error);
351 self.apply_two_qubit_rotation_error(state, control, target, error)?;
352 }
353
354 if params.zz_coupling > 0.0 {
356 let zz_angle = params.zz_coupling * duration_ns / 1000.0; self.apply_zz_interaction(state, control, target, zz_angle)?;
358 }
359
360 for (&spectator, &coupling) in ¶ms.spectator_crosstalk {
362 if rng.random::<f64>() < coupling {
363 self.apply_crosstalk_error(state, spectator, coupling * 0.1)?;
364 }
365 }
366
367 self.apply_single_qubit_noise(state, gate_name, control, duration_ns / 2.0, rng)?;
369 self.apply_single_qubit_noise(state, gate_name, target, duration_ns / 2.0, rng)?;
370 }
371
372 Ok(())
373 }
374
375 pub fn apply_readout_noise(&self, measurement: u8, qubit: QubitId, rng: &mut impl Rng) -> u8 {
377 if let Some(params) = self.readout_noise.get(&qubit) {
378 let prob = rng.random::<f64>();
379
380 if measurement == 0 {
381 if prob > params.assignment_matrix[0][0] {
382 return 1;
383 }
384 } else if prob > params.assignment_matrix[1][1] {
385 return 0;
386 }
387 }
388
389 measurement
390 }
391
392 fn apply_rotation_error(
395 &self,
396 state: &mut Vec<Complex64>,
397 qubit: QubitId,
398 angle: f64,
399 ) -> QuantRS2Result<()> {
400 let n_qubits = (state.len() as f64).log2() as usize;
402 let qubit_idx = qubit.id() as usize;
403
404 for i in 0..state.len() {
405 if (i >> qubit_idx) & 1 == 1 {
406 state[i] *= Complex64::from_polar(1.0, angle);
407 }
408 }
409
410 Ok(())
411 }
412
413 fn apply_amplitude_scaling(
414 &self,
415 state: &mut Vec<Complex64>,
416 qubit: QubitId,
417 scale: f64,
418 ) -> QuantRS2Result<()> {
419 for amp in state.iter_mut() {
421 *amp *= scale;
422 }
423
424 let norm = state.iter().map(|a| a.norm_sqr()).sum::<f64>().sqrt();
426 for amp in state.iter_mut() {
427 *amp /= norm;
428 }
429
430 Ok(())
431 }
432
433 fn apply_phase_error(
434 &self,
435 state: &mut Vec<Complex64>,
436 qubit: QubitId,
437 phase: f64,
438 ) -> QuantRS2Result<()> {
439 let qubit_idx = qubit.id() as usize;
440
441 for i in 0..state.len() {
442 if (i >> qubit_idx) & 1 == 1 {
443 state[i] *= Complex64::from_polar(1.0, phase);
444 }
445 }
446
447 Ok(())
448 }
449
450 fn apply_amplitude_damping(
451 &self,
452 state: &mut Vec<Complex64>,
453 qubit: QubitId,
454 gamma: f64,
455 ) -> QuantRS2Result<()> {
456 let qubit_idx = qubit.id() as usize;
458 let damping_factor = (1.0 - gamma).sqrt();
459
460 for i in 0..state.len() {
461 if (i >> qubit_idx) & 1 == 1 {
462 state[i] *= damping_factor;
463 }
464 }
465
466 Ok(())
467 }
468
469 fn apply_phase_damping(
470 &self,
471 state: &mut Vec<Complex64>,
472 qubit: QubitId,
473 gamma: f64,
474 ) -> QuantRS2Result<()> {
475 let qubit_idx = qubit.id() as usize;
477
478 for i in 0..state.len() {
480 if (i >> qubit_idx) & 1 == 1 {
481 state[i] *= (1.0 - gamma).sqrt();
482 }
483 }
484
485 Ok(())
486 }
487
488 fn apply_two_qubit_rotation_error(
489 &self,
490 state: &mut Vec<Complex64>,
491 control: QubitId,
492 target: QubitId,
493 angle: f64,
494 ) -> QuantRS2Result<()> {
495 let control_idx = control.id() as usize;
497 let target_idx = target.id() as usize;
498
499 for i in 0..state.len() {
500 let control_bit = (i >> control_idx) & 1;
501 let target_bit = (i >> target_idx) & 1;
502
503 if control_bit == 1 && target_bit == 1 {
504 state[i] *= Complex64::from_polar(1.0, angle);
505 }
506 }
507
508 Ok(())
509 }
510
511 fn apply_zz_interaction(
512 &self,
513 state: &mut Vec<Complex64>,
514 qubit1: QubitId,
515 qubit2: QubitId,
516 angle: f64,
517 ) -> QuantRS2Result<()> {
518 let idx1 = qubit1.id() as usize;
519 let idx2 = qubit2.id() as usize;
520
521 for i in 0..state.len() {
522 let bit1 = (i >> idx1) & 1;
523 let bit2 = (i >> idx2) & 1;
524
525 let phase = if bit1 == bit2 { angle } else { -angle };
527 state[i] *= Complex64::from_polar(1.0, phase / 2.0);
528 }
529
530 Ok(())
531 }
532
533 fn apply_crosstalk_error(
534 &self,
535 state: &mut Vec<Complex64>,
536 qubit: QubitId,
537 strength: f64,
538 ) -> QuantRS2Result<()> {
539 self.apply_rotation_error(state, qubit, strength)
541 }
542}
543
544pub struct NoiseModelBuilder {
546 calibration: DeviceCalibration,
547 coherent_factor: f64,
548 thermal_factor: f64,
549 crosstalk_factor: f64,
550 readout_factor: f64,
551}
552
553impl NoiseModelBuilder {
554 pub const fn from_calibration(calibration: DeviceCalibration) -> Self {
556 Self {
557 calibration,
558 coherent_factor: 1.0,
559 thermal_factor: 1.0,
560 crosstalk_factor: 1.0,
561 readout_factor: 1.0,
562 }
563 }
564
565 #[must_use]
567 pub const fn coherent_factor(mut self, factor: f64) -> Self {
568 self.coherent_factor = factor;
569 self
570 }
571
572 #[must_use]
574 pub const fn thermal_factor(mut self, factor: f64) -> Self {
575 self.thermal_factor = factor;
576 self
577 }
578
579 #[must_use]
581 pub const fn crosstalk_factor(mut self, factor: f64) -> Self {
582 self.crosstalk_factor = factor;
583 self
584 }
585
586 #[must_use]
588 pub const fn readout_factor(mut self, factor: f64) -> Self {
589 self.readout_factor = factor;
590 self
591 }
592
593 pub fn build(self) -> CalibrationNoiseModel {
595 let mut model = CalibrationNoiseModel::from_calibration(&self.calibration);
596
597 for noise in model.gate_noise.values_mut() {
599 noise.coherent_error *= self.coherent_factor;
600 }
601
602 for noise in model.qubit_noise.values_mut() {
603 noise.thermal_population *= self.thermal_factor;
604 }
605
606 for row in &mut model.crosstalk.crosstalk_matrix {
607 for val in row.iter_mut() {
608 *val *= self.crosstalk_factor;
609 }
610 }
611
612 for readout in model.readout_noise.values_mut() {
613 readout.assignment_matrix[0][1] *= self.readout_factor;
615 readout.assignment_matrix[1][0] *= self.readout_factor;
616 readout.assignment_matrix[0][0] = 1.0 - readout.assignment_matrix[0][1];
618 readout.assignment_matrix[1][1] = 1.0 - readout.assignment_matrix[1][0];
619 }
620
621 model
622 }
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628 use crate::calibration::create_ideal_calibration;
629
630 #[test]
631 fn test_noise_model_from_calibration() {
632 let cal = create_ideal_calibration("test".to_string(), 5);
633 let noise_model = CalibrationNoiseModel::from_calibration(&cal);
634
635 assert_eq!(noise_model.device_id, "test");
636 assert_eq!(noise_model.qubit_noise.len(), 5);
637 assert!(noise_model.gate_noise.contains_key("X"));
638 assert!(noise_model.gate_noise.contains_key("CNOT"));
639 }
640
641 #[test]
642 fn test_noise_model_builder() {
643 let cal = create_ideal_calibration("test".to_string(), 3);
644 let noise_model = NoiseModelBuilder::from_calibration(cal)
645 .coherent_factor(0.5)
646 .thermal_factor(2.0)
647 .crosstalk_factor(0.1)
648 .readout_factor(0.5)
649 .build();
650
651 let x_noise = noise_model
653 .gate_noise
654 .get("X")
655 .expect("X gate noise should exist");
656 assert!(x_noise.coherent_error < 0.001); }
658}