1use scirs2_core::random::Rng;
7use scirs2_core::Complex64;
8use std::collections::HashMap;
9
10use quantrs2_core::{
11 error::{QuantRS2Error, QuantRS2Result},
12 gate::GateOp,
13 qubit::QubitId,
14};
15
16use crate::calibration::{
17 DeviceCalibration, QubitCalibration, QubitReadoutData, SingleQubitGateData,
18 TwoQubitGateCalibration,
19};
20use scirs2_core::random::prelude::*;
21
22#[derive(Debug, Clone)]
24pub struct CalibrationNoiseModel {
25 pub device_id: String,
27 pub qubit_noise: HashMap<QubitId, QubitNoiseParams>,
29 pub gate_noise: HashMap<String, GateNoiseParams>,
31 pub two_qubit_noise: HashMap<(QubitId, QubitId), TwoQubitNoiseParams>,
33 pub readout_noise: HashMap<QubitId, ReadoutNoiseParams>,
35 pub crosstalk: CrosstalkNoise,
37 pub temperature: f64,
39}
40
41#[derive(Debug, Clone)]
43pub struct QubitNoiseParams {
44 pub gamma_1: f64,
46 pub gamma_phi: f64,
48 pub thermal_population: f64,
50 pub frequency_drift: f64,
52 pub flicker_noise: f64,
54}
55
56#[derive(Debug, Clone)]
58pub struct GateNoiseParams {
59 pub coherent_error: f64,
61 pub incoherent_error: f64,
63 pub duration: f64,
65 pub depolarizing_rate: f64,
67 pub amplitude_noise: f64,
69 pub phase_noise: f64,
71}
72
73#[derive(Debug, Clone)]
75pub struct TwoQubitNoiseParams {
76 pub gate_noise: GateNoiseParams,
78 pub zz_coupling: f64,
80 pub spectator_crosstalk: HashMap<QubitId, f64>,
82 pub directional_error: f64,
84}
85
86#[derive(Debug, Clone)]
88pub struct ReadoutNoiseParams {
89 pub assignment_matrix: [[f64; 2]; 2],
91 pub readout_excitation: f64,
93 pub readout_relaxation: f64,
95}
96
97#[derive(Debug, Clone)]
99pub struct CrosstalkNoise {
100 pub crosstalk_matrix: Vec<Vec<f64>>,
102 pub threshold: f64,
104 pub single_qubit_crosstalk: f64,
106 pub two_qubit_crosstalk: f64,
108}
109
110impl CalibrationNoiseModel {
111 pub fn from_calibration(calibration: &DeviceCalibration) -> Self {
113 let mut qubit_noise = HashMap::new();
114 let mut readout_noise = HashMap::new();
115
116 for (qubit_id, qubit_cal) in &calibration.qubit_calibrations {
118 qubit_noise.insert(
119 *qubit_id,
120 QubitNoiseParams {
121 gamma_1: 1.0 / qubit_cal.t1,
122 gamma_phi: 1.0 / qubit_cal.t2 - 0.5 / qubit_cal.t1,
123 thermal_population: qubit_cal.thermal_population,
124 frequency_drift: 1e3, flicker_noise: 1e-6, },
127 );
128 }
129
130 for (qubit_id, readout_data) in &calibration.readout_calibration.qubit_readout {
132 let p00 = readout_data.p0_given_0;
133 let p11 = readout_data.p1_given_1;
134
135 readout_noise.insert(
136 *qubit_id,
137 ReadoutNoiseParams {
138 assignment_matrix: [[p00, 1.0 - p11], [1.0 - p00, p11]],
139 readout_excitation: 0.001, readout_relaxation: 0.002, },
142 );
143 }
144
145 let mut gate_noise = HashMap::new();
147 for (gate_name, gate_cal) in &calibration.single_qubit_gates {
148 let mut total_error = 0.0;
150 let mut total_duration = 0.0;
151 let mut count = 0;
152
153 for gate_data in gate_cal.qubit_data.values() {
154 total_error += gate_data.error_rate;
155 total_duration += gate_data.duration;
156 count += 1;
157 }
158
159 if count > 0 {
160 let avg_error = total_error / count as f64;
161 let avg_duration = total_duration / count as f64;
162
163 gate_noise.insert(
164 gate_name.clone(),
165 GateNoiseParams {
166 coherent_error: avg_error * 0.3, incoherent_error: avg_error * 0.7, duration: avg_duration,
169 depolarizing_rate: avg_error / avg_duration,
170 amplitude_noise: 0.001, phase_noise: 0.002, },
173 );
174 }
175 }
176
177 let mut two_qubit_noise = HashMap::new();
179 for ((control, target), gate_cal) in &calibration.two_qubit_gates {
180 let base_noise = GateNoiseParams {
181 coherent_error: gate_cal.error_rate * 0.4,
182 incoherent_error: gate_cal.error_rate * 0.6,
183 duration: gate_cal.duration,
184 depolarizing_rate: gate_cal.error_rate / gate_cal.duration,
185 amplitude_noise: 0.002,
186 phase_noise: 0.003,
187 };
188
189 gate_noise.insert(gate_cal.gate_name.clone(), base_noise.clone());
191
192 let mut spectator_crosstalk = HashMap::new();
194
195 for offset in [-1, 1] {
197 let spectator_id = control.id() as i32 + offset;
198 if spectator_id >= 0 && spectator_id < calibration.topology.num_qubits as i32 {
199 spectator_crosstalk.insert(QubitId(spectator_id as u32), 0.001);
200 }
201
202 let spectator_id = target.id() as i32 + offset;
203 if spectator_id >= 0 && spectator_id < calibration.topology.num_qubits as i32 {
204 spectator_crosstalk.insert(QubitId(spectator_id as u32), 0.001);
205 }
206 }
207
208 two_qubit_noise.insert(
209 (*control, *target),
210 TwoQubitNoiseParams {
211 gate_noise: base_noise,
212 zz_coupling: 0.1, spectator_crosstalk,
214 directional_error: if gate_cal.directional { 0.01 } else { 0.0 },
215 },
216 );
217 }
218
219 let crosstalk = CrosstalkNoise {
221 crosstalk_matrix: calibration.crosstalk_matrix.matrix.clone(),
222 threshold: calibration.crosstalk_matrix.significance_threshold,
223 single_qubit_crosstalk: 0.001,
224 two_qubit_crosstalk: 0.01,
225 };
226
227 let temperature = calibration
229 .qubit_calibrations
230 .values()
231 .filter_map(|q| q.temperature)
232 .sum::<f64>()
233 / calibration.qubit_calibrations.len() as f64;
234
235 Self {
236 device_id: calibration.device_id.clone(),
237 qubit_noise,
238 gate_noise,
239 two_qubit_noise,
240 readout_noise,
241 crosstalk,
242 temperature,
243 }
244 }
245
246 pub fn apply_gate_noise(
248 &self,
249 state: &mut Vec<Complex64>,
250 gate: &dyn GateOp,
251 qubits: &[QubitId],
252 duration_ns: f64,
253 rng: &mut impl Rng,
254 ) -> QuantRS2Result<()> {
255 let gate_name = gate.name();
256
257 match qubits.len() {
258 1 => self.apply_single_qubit_noise(state, gate_name, qubits[0], duration_ns, rng),
259 2 => {
260 self.apply_two_qubit_noise(state, gate_name, qubits[0], qubits[1], duration_ns, rng)
261 }
262 _ => {
263 for &qubit in qubits {
265 self.apply_single_qubit_noise(
266 state,
267 gate_name,
268 qubit,
269 duration_ns / qubits.len() as f64,
270 rng,
271 )?;
272 }
273 Ok(())
274 }
275 }
276 }
277
278 fn apply_single_qubit_noise(
280 &self,
281 state: &mut Vec<Complex64>,
282 gate_name: &str,
283 qubit: QubitId,
284 duration_ns: f64,
285 rng: &mut impl Rng,
286 ) -> QuantRS2Result<()> {
287 let gate_params = self.gate_noise.get(gate_name);
289 let qubit_params = self.qubit_noise.get(&qubit);
290
291 if let Some(params) = gate_params {
292 if params.coherent_error > 0.0 {
294 let error_angle = rng.gen_range(-params.coherent_error..params.coherent_error);
295 self.apply_rotation_error(state, qubit, error_angle)?;
296 }
297
298 if params.amplitude_noise > 0.0 {
300 let amplitude_error =
301 1.0 + rng.gen_range(-params.amplitude_noise..params.amplitude_noise);
302 self.apply_amplitude_scaling(state, qubit, amplitude_error)?;
303 }
304
305 if params.phase_noise > 0.0 {
307 let phase_error = rng.gen_range(-params.phase_noise..params.phase_noise);
308 self.apply_phase_error(state, qubit, phase_error)?;
309 }
310 }
311
312 if let Some(qubit_params) = qubit_params {
314 let actual_duration = duration_ns / 1000.0; let decay_prob = 1.0 - (-actual_duration * qubit_params.gamma_1).exp();
318 if rng.gen::<f64>() < decay_prob {
319 self.apply_amplitude_damping(state, qubit, decay_prob)?;
320 }
321
322 let dephase_prob = 1.0 - (-actual_duration * qubit_params.gamma_phi).exp();
324 if rng.gen::<f64>() < dephase_prob {
325 self.apply_phase_damping(state, qubit, dephase_prob)?;
326 }
327 }
328
329 Ok(())
330 }
331
332 fn apply_two_qubit_noise(
334 &self,
335 state: &mut Vec<Complex64>,
336 gate_name: &str,
337 control: QubitId,
338 target: QubitId,
339 duration_ns: f64,
340 rng: &mut impl Rng,
341 ) -> QuantRS2Result<()> {
342 if let Some(params) = self.two_qubit_noise.get(&(control, target)) {
343 let gate_params = ¶ms.gate_noise;
345
346 if gate_params.coherent_error > 0.0 {
348 let error = rng.gen_range(-gate_params.coherent_error..gate_params.coherent_error);
349 self.apply_two_qubit_rotation_error(state, control, target, error)?;
350 }
351
352 if params.zz_coupling > 0.0 {
354 let zz_angle = params.zz_coupling * duration_ns / 1000.0; self.apply_zz_interaction(state, control, target, zz_angle)?;
356 }
357
358 for (&spectator, &coupling) in ¶ms.spectator_crosstalk {
360 if rng.gen::<f64>() < coupling {
361 self.apply_crosstalk_error(state, spectator, coupling * 0.1)?;
362 }
363 }
364
365 self.apply_single_qubit_noise(state, gate_name, control, duration_ns / 2.0, rng)?;
367 self.apply_single_qubit_noise(state, gate_name, target, duration_ns / 2.0, rng)?;
368 }
369
370 Ok(())
371 }
372
373 pub fn apply_readout_noise(&self, measurement: u8, qubit: QubitId, rng: &mut impl Rng) -> u8 {
375 if let Some(params) = self.readout_noise.get(&qubit) {
376 let prob = rng.gen::<f64>();
377
378 if measurement == 0 {
379 if prob > params.assignment_matrix[0][0] {
380 return 1;
381 }
382 } else if prob > params.assignment_matrix[1][1] {
383 return 0;
384 }
385 }
386
387 measurement
388 }
389
390 fn apply_rotation_error(
393 &self,
394 state: &mut Vec<Complex64>,
395 qubit: QubitId,
396 angle: f64,
397 ) -> QuantRS2Result<()> {
398 let n_qubits = (state.len() as f64).log2() as usize;
400 let qubit_idx = qubit.id() as usize;
401
402 for i in 0..state.len() {
403 if (i >> qubit_idx) & 1 == 1 {
404 state[i] *= Complex64::from_polar(1.0, angle);
405 }
406 }
407
408 Ok(())
409 }
410
411 fn apply_amplitude_scaling(
412 &self,
413 state: &mut Vec<Complex64>,
414 qubit: QubitId,
415 scale: f64,
416 ) -> QuantRS2Result<()> {
417 for amp in state.iter_mut() {
419 *amp *= scale;
420 }
421
422 let norm = state.iter().map(|a| a.norm_sqr()).sum::<f64>().sqrt();
424 for amp in state.iter_mut() {
425 *amp /= norm;
426 }
427
428 Ok(())
429 }
430
431 fn apply_phase_error(
432 &self,
433 state: &mut Vec<Complex64>,
434 qubit: QubitId,
435 phase: f64,
436 ) -> QuantRS2Result<()> {
437 let qubit_idx = qubit.id() as usize;
438
439 for i in 0..state.len() {
440 if (i >> qubit_idx) & 1 == 1 {
441 state[i] *= Complex64::from_polar(1.0, phase);
442 }
443 }
444
445 Ok(())
446 }
447
448 fn apply_amplitude_damping(
449 &self,
450 state: &mut Vec<Complex64>,
451 qubit: QubitId,
452 gamma: f64,
453 ) -> QuantRS2Result<()> {
454 let qubit_idx = qubit.id() as usize;
456 let damping_factor = (1.0 - gamma).sqrt();
457
458 for i in 0..state.len() {
459 if (i >> qubit_idx) & 1 == 1 {
460 state[i] *= damping_factor;
461 }
462 }
463
464 Ok(())
465 }
466
467 fn apply_phase_damping(
468 &self,
469 state: &mut Vec<Complex64>,
470 qubit: QubitId,
471 gamma: f64,
472 ) -> QuantRS2Result<()> {
473 let qubit_idx = qubit.id() as usize;
475
476 for i in 0..state.len() {
478 if (i >> qubit_idx) & 1 == 1 {
479 state[i] *= (1.0 - gamma).sqrt();
480 }
481 }
482
483 Ok(())
484 }
485
486 fn apply_two_qubit_rotation_error(
487 &self,
488 state: &mut Vec<Complex64>,
489 control: QubitId,
490 target: QubitId,
491 angle: f64,
492 ) -> QuantRS2Result<()> {
493 let control_idx = control.id() as usize;
495 let target_idx = target.id() as usize;
496
497 for i in 0..state.len() {
498 let control_bit = (i >> control_idx) & 1;
499 let target_bit = (i >> target_idx) & 1;
500
501 if control_bit == 1 && target_bit == 1 {
502 state[i] *= Complex64::from_polar(1.0, angle);
503 }
504 }
505
506 Ok(())
507 }
508
509 fn apply_zz_interaction(
510 &self,
511 state: &mut Vec<Complex64>,
512 qubit1: QubitId,
513 qubit2: QubitId,
514 angle: f64,
515 ) -> QuantRS2Result<()> {
516 let idx1 = qubit1.id() as usize;
517 let idx2 = qubit2.id() as usize;
518
519 for i in 0..state.len() {
520 let bit1 = (i >> idx1) & 1;
521 let bit2 = (i >> idx2) & 1;
522
523 let phase = if bit1 == bit2 { angle } else { -angle };
525 state[i] *= Complex64::from_polar(1.0, phase / 2.0);
526 }
527
528 Ok(())
529 }
530
531 fn apply_crosstalk_error(
532 &self,
533 state: &mut Vec<Complex64>,
534 qubit: QubitId,
535 strength: f64,
536 ) -> QuantRS2Result<()> {
537 self.apply_rotation_error(state, qubit, strength)
539 }
540}
541
542pub struct NoiseModelBuilder {
544 calibration: DeviceCalibration,
545 coherent_factor: f64,
546 thermal_factor: f64,
547 crosstalk_factor: f64,
548 readout_factor: f64,
549}
550
551impl NoiseModelBuilder {
552 pub const fn from_calibration(calibration: DeviceCalibration) -> Self {
554 Self {
555 calibration,
556 coherent_factor: 1.0,
557 thermal_factor: 1.0,
558 crosstalk_factor: 1.0,
559 readout_factor: 1.0,
560 }
561 }
562
563 #[must_use]
565 pub const fn coherent_factor(mut self, factor: f64) -> Self {
566 self.coherent_factor = factor;
567 self
568 }
569
570 #[must_use]
572 pub const fn thermal_factor(mut self, factor: f64) -> Self {
573 self.thermal_factor = factor;
574 self
575 }
576
577 #[must_use]
579 pub const fn crosstalk_factor(mut self, factor: f64) -> Self {
580 self.crosstalk_factor = factor;
581 self
582 }
583
584 #[must_use]
586 pub const fn readout_factor(mut self, factor: f64) -> Self {
587 self.readout_factor = factor;
588 self
589 }
590
591 pub fn build(self) -> CalibrationNoiseModel {
593 let mut model = CalibrationNoiseModel::from_calibration(&self.calibration);
594
595 for noise in model.gate_noise.values_mut() {
597 noise.coherent_error *= self.coherent_factor;
598 }
599
600 for noise in model.qubit_noise.values_mut() {
601 noise.thermal_population *= self.thermal_factor;
602 }
603
604 for row in &mut model.crosstalk.crosstalk_matrix {
605 for val in row.iter_mut() {
606 *val *= self.crosstalk_factor;
607 }
608 }
609
610 for readout in model.readout_noise.values_mut() {
611 readout.assignment_matrix[0][1] *= self.readout_factor;
613 readout.assignment_matrix[1][0] *= self.readout_factor;
614 readout.assignment_matrix[0][0] = 1.0 - readout.assignment_matrix[0][1];
616 readout.assignment_matrix[1][1] = 1.0 - readout.assignment_matrix[1][0];
617 }
618
619 model
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626 use crate::calibration::create_ideal_calibration;
627
628 #[test]
629 fn test_noise_model_from_calibration() {
630 let cal = create_ideal_calibration("test".to_string(), 5);
631 let noise_model = CalibrationNoiseModel::from_calibration(&cal);
632
633 assert_eq!(noise_model.device_id, "test");
634 assert_eq!(noise_model.qubit_noise.len(), 5);
635 assert!(noise_model.gate_noise.contains_key("X"));
636 assert!(noise_model.gate_noise.contains_key("CNOT"));
637 }
638
639 #[test]
640 fn test_noise_model_builder() {
641 let cal = create_ideal_calibration("test".to_string(), 3);
642 let noise_model = NoiseModelBuilder::from_calibration(cal)
643 .coherent_factor(0.5)
644 .thermal_factor(2.0)
645 .crosstalk_factor(0.1)
646 .readout_factor(0.5)
647 .build();
648
649 let x_noise = noise_model
651 .gate_noise
652 .get("X")
653 .expect("X gate noise should exist");
654 assert!(x_noise.coherent_error < 0.001); }
656}