1use crate::error::{Result, SimulatorError};
24use scirs2_core::ndarray::{Array1, Array2};
25use scirs2_core::random::prelude::*;
26use scirs2_core::Complex64;
27use std::collections::HashMap;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum ExtrapolationMethod {
36 Linear,
38 Richardson,
40 Polynomial { degree: usize },
42 Exponential,
44}
45
46#[derive(Debug, Clone)]
51pub struct ZeroNoiseExtrapolation {
52 method: ExtrapolationMethod,
54 scale_factors: Vec<f64>,
56}
57
58impl ZeroNoiseExtrapolation {
59 pub fn new(method: ExtrapolationMethod) -> Self {
61 Self {
62 method,
63 scale_factors: vec![1.0, 1.5, 2.0, 2.5, 3.0],
64 }
65 }
66
67 pub fn with_scale_factors(
69 method: ExtrapolationMethod,
70 scale_factors: Vec<f64>,
71 ) -> Result<Self> {
72 if scale_factors.is_empty() {
73 return Err(SimulatorError::InvalidInput(
74 "Scale factors cannot be empty".to_string(),
75 ));
76 }
77
78 if scale_factors[0] != 1.0 {
80 return Err(SimulatorError::InvalidInput(
81 "First scale factor must be 1.0 (original noise level)".to_string(),
82 ));
83 }
84
85 for i in 1..scale_factors.len() {
86 if scale_factors[i] <= scale_factors[i - 1] {
87 return Err(SimulatorError::InvalidInput(
88 "Scale factors must be in strictly ascending order".to_string(),
89 ));
90 }
91 }
92
93 Ok(Self {
94 method,
95 scale_factors,
96 })
97 }
98
99 pub fn apply(&self, noisy_values: &[f64], noise_scales: &[f64]) -> Result<f64> {
106 if noisy_values.len() != noise_scales.len() {
107 return Err(SimulatorError::InvalidInput(
108 "Number of values must match number of scales".to_string(),
109 ));
110 }
111
112 if noisy_values.len() < 2 {
113 return Err(SimulatorError::InvalidInput(
114 "At least 2 data points required for extrapolation".to_string(),
115 ));
116 }
117
118 match self.method {
119 ExtrapolationMethod::Linear => self.linear_extrapolation(noisy_values, noise_scales),
120 ExtrapolationMethod::Richardson => {
121 self.richardson_extrapolation(noisy_values, noise_scales)
122 }
123 ExtrapolationMethod::Polynomial { degree } => {
124 self.polynomial_extrapolation(noisy_values, noise_scales, degree)
125 }
126 ExtrapolationMethod::Exponential => {
127 self.exponential_extrapolation(noisy_values, noise_scales)
128 }
129 }
130 }
131
132 fn linear_extrapolation(&self, values: &[f64], scales: &[f64]) -> Result<f64> {
134 if values.len() < 2 {
135 return Err(SimulatorError::InvalidInput(
136 "Linear extrapolation requires at least 2 points".to_string(),
137 ));
138 }
139
140 let x1 = scales[0];
141 let y1 = values[0];
142 let x2 = scales[1];
143 let y2 = values[1];
144
145 let slope = (y2 - y1) / (x2 - x1);
147 Ok(y1 - slope * x1)
148 }
149
150 fn richardson_extrapolation(&self, values: &[f64], scales: &[f64]) -> Result<f64> {
152 if values.len() < 3 {
153 return Err(SimulatorError::InvalidInput(
154 "Richardson extrapolation requires at least 3 points".to_string(),
155 ));
156 }
157
158 let x0 = scales[0];
160 let x1 = scales[1];
161 let x2 = scales[2];
162 let y0 = values[0];
163 let y1 = values[1];
164 let y2 = values[2];
165
166 let denom = (x0 - x1) * (x0 - x2) * (x1 - x2);
169 if denom.abs() < 1e-10 {
170 return Err(SimulatorError::InvalidInput(
171 "Scale factors too close for stable extrapolation".to_string(),
172 ));
173 }
174
175 let a = (x2 * (y1 - y0) + x0 * (y2 - y1) + x1 * (y0 - y2)) / denom;
176 let b = (x2 * x2 * (y0 - y1) + x0 * x0 * (y1 - y2) + x1 * x1 * (y2 - y0)) / denom;
177 let c = (x1 * x2 * (x1 - x2) * y0 + x2 * x0 * (x2 - x0) * y1 + x0 * x1 * (x0 - x1) * y2)
178 / denom;
179
180 Ok(c)
181 }
182
183 fn polynomial_extrapolation(
185 &self,
186 values: &[f64],
187 scales: &[f64],
188 degree: usize,
189 ) -> Result<f64> {
190 if values.len() <= degree {
191 return Err(SimulatorError::InvalidInput(format!(
192 "Need at least {} points for degree {} polynomial",
193 degree + 1,
194 degree
195 )));
196 }
197
198 match degree {
202 1 => self.linear_extrapolation(values, scales),
203 2 => self.richardson_extrapolation(values, scales),
204 _ => {
205 let n_pts = degree + 1;
208 let xs = &scales[..n_pts];
209 let ys = &values[..n_pts];
210 let mut result = 0.0_f64;
211 for i in 0..n_pts {
212 let mut num = 1.0_f64;
213 let mut den = 1.0_f64;
214 for j in 0..n_pts {
215 if j != i {
216 num *= -xs[j];
217 den *= xs[i] - xs[j];
218 }
219 }
220 if den.abs() < 1e-15 {
221 return Err(SimulatorError::InvalidInput(
222 "Degenerate scale factors — cannot form stable Lagrange basis"
223 .to_string(),
224 ));
225 }
226 result += ys[i] * num / den;
227 }
228 Ok(result)
229 }
230 }
231 }
232
233 fn exponential_extrapolation(&self, values: &[f64], scales: &[f64]) -> Result<f64> {
235 if values.len() < 3 {
236 return Err(SimulatorError::InvalidInput(
237 "Exponential extrapolation requires at least 3 points".to_string(),
238 ));
239 }
240
241 let x0 = scales[0];
244 let x1 = scales[1];
245 let y0 = values[0];
246 let y1 = values[1];
247
248 if (y1 - y0).abs() < 1e-10 {
250 return Ok(y0); }
252
253 let b_estimate = -((y1 - y0) / (x1 - x0)) / y0.max(1e-10);
254 let c_estimate = y0 / (1.0 + b_estimate * x0).max(1e-10);
255
256 Ok(c_estimate)
257 }
258
259 pub fn scale_factors(&self) -> &[f64] {
261 &self.scale_factors
262 }
263}
264
265#[derive(Debug, Clone)]
274pub struct MeasurementErrorMitigation {
275 calibration_matrix: Array2<f64>,
277 inverse_matrix: Option<Array2<f64>>,
279 n_qubits: usize,
281}
282
283impl MeasurementErrorMitigation {
284 pub fn new(n_qubits: usize) -> Self {
290 let dim = 1 << n_qubits; let calibration_matrix = Array2::eye(dim); Self {
294 calibration_matrix,
295 inverse_matrix: None,
296 n_qubits,
297 }
298 }
299
300 pub fn set_calibration_matrix(&mut self, matrix: Array2<f64>) -> Result<()> {
305 let expected_dim = 1 << self.n_qubits;
306 if matrix.nrows() != expected_dim || matrix.ncols() != expected_dim {
307 return Err(SimulatorError::InvalidInput(format!(
308 "Calibration matrix must be {}x{} for {} qubits",
309 expected_dim, expected_dim, self.n_qubits
310 )));
311 }
312
313 for col in 0..matrix.ncols() {
315 let sum: f64 = (0..matrix.nrows()).map(|row| matrix[[row, col]]).sum();
316 if (sum - 1.0).abs() > 1e-6 {
317 return Err(SimulatorError::InvalidInput(format!(
318 "Column {} does not sum to 1.0 (sum = {})",
319 col, sum
320 )));
321 }
322 }
323
324 self.calibration_matrix = matrix;
325 self.inverse_matrix = None; Ok(())
327 }
328
329 fn compute_inverse(&mut self) -> Result<()> {
331 if self.inverse_matrix.is_some() {
332 return Ok(());
333 }
334
335 let matrix = &self.calibration_matrix;
338 let n = matrix.nrows();
339
340 let mut augmented = Array2::zeros((n, 2 * n));
342 for i in 0..n {
343 for j in 0..n {
344 augmented[[i, j]] = matrix[[i, j]];
345 augmented[[i, j + n]] = if i == j { 1.0 } else { 0.0 };
346 }
347 }
348
349 for i in 0..n {
351 let pivot = augmented[[i, i]];
353 if pivot.abs() < 1e-10 {
354 return Err(SimulatorError::InvalidInput(
355 "Calibration matrix is singular or nearly singular".to_string(),
356 ));
357 }
358
359 for j in 0..(2 * n) {
361 augmented[[i, j]] /= pivot;
362 }
363
364 for k in 0..n {
366 if k != i {
367 let factor = augmented[[k, i]];
368 for j in 0..(2 * n) {
369 augmented[[k, j]] -= factor * augmented[[i, j]];
370 }
371 }
372 }
373 }
374
375 let mut inverse = Array2::zeros((n, n));
377 for i in 0..n {
378 for j in 0..n {
379 inverse[[i, j]] = augmented[[i, j + n]];
380 }
381 }
382
383 self.inverse_matrix = Some(inverse);
384 Ok(())
385 }
386
387 pub fn apply(&mut self, noisy_counts: &HashMap<String, usize>) -> Result<HashMap<String, f64>> {
397 self.compute_inverse()?;
398 let inverse = self.inverse_matrix.as_ref().ok_or_else(|| {
399 SimulatorError::InvalidInput(
400 "Inverse matrix not yet computed — call compute_inverse() first".to_string(),
401 )
402 })?;
403
404 let dim = 1 << self.n_qubits;
405 let total_shots: usize = noisy_counts.values().sum();
406
407 let mut noisy_probs = Array1::zeros(dim);
409 for (bitstring, count) in noisy_counts {
410 if bitstring.len() != self.n_qubits {
411 return Err(SimulatorError::InvalidInput(format!(
412 "Bitstring {} has wrong length (expected {})",
413 bitstring, self.n_qubits
414 )));
415 }
416 let index = usize::from_str_radix(bitstring, 2).map_err(|_| {
417 SimulatorError::InvalidInput(format!("Invalid bitstring: {}", bitstring))
418 })?;
419 noisy_probs[index] = *count as f64 / total_shots as f64;
420 }
421
422 let mitigated_probs = inverse.dot(&noisy_probs);
424
425 let mut mitigated_counts = HashMap::new();
427 for i in 0..dim {
428 let bitstring = format!("{:0width$b}", i, width = self.n_qubits);
429 let mitigated_count = mitigated_probs[i] * total_shots as f64;
430 if mitigated_count.abs() > 1e-10 {
431 mitigated_counts.insert(bitstring, mitigated_count);
432 }
433 }
434
435 Ok(mitigated_counts)
436 }
437
438 pub fn from_error_rates(
445 n_qubits: usize,
446 readout_error_0: f64,
447 readout_error_1: f64,
448 ) -> Result<Self> {
449 if !(0.0..=1.0).contains(&readout_error_0) {
450 return Err(SimulatorError::InvalidInput(
451 "readout_error_0 must be in [0, 1]".to_string(),
452 ));
453 }
454 if !(0.0..=1.0).contains(&readout_error_1) {
455 return Err(SimulatorError::InvalidInput(
456 "readout_error_1 must be in [0, 1]".to_string(),
457 ));
458 }
459
460 let dim = 1 << n_qubits;
461 let mut matrix = Array2::zeros((dim, dim));
462
463 for prepared in 0..dim {
466 for measured in 0..dim {
467 let mut prob = 1.0;
468 for qubit in 0..n_qubits {
469 let prepared_bit = (prepared >> qubit) & 1;
470 let measured_bit = (measured >> qubit) & 1;
471
472 let p = if prepared_bit == 0 {
473 if measured_bit == 0 {
474 1.0 - readout_error_0
475 } else {
476 readout_error_0
477 }
478 } else if measured_bit == 1 {
479 1.0 - readout_error_1
480 } else {
481 readout_error_1
482 };
483
484 prob *= p;
485 }
486 matrix[[measured, prepared]] = prob;
487 }
488 }
489
490 let mut mem = Self::new(n_qubits);
491 mem.set_calibration_matrix(matrix)?;
492 Ok(mem)
493 }
494}
495
496#[derive(Debug, Clone)]
506pub struct SymmetryVerification {
507 symmetry_type: SymmetryType,
509 expected_value: Option<i32>,
511}
512
513#[derive(Debug, Clone, Copy, PartialEq, Eq)]
515pub enum SymmetryType {
516 ParticleNumber,
518 Parity,
520 Custom,
522}
523
524impl SymmetryVerification {
525 pub fn new(symmetry_type: SymmetryType) -> Self {
527 Self {
528 symmetry_type,
529 expected_value: None,
530 }
531 }
532
533 pub fn with_expected_value(mut self, value: i32) -> Self {
535 self.expected_value = Some(value);
536 self
537 }
538
539 pub fn verify(&self, bitstring: &str) -> bool {
541 let symmetry_value = self.compute_symmetry(bitstring);
542
543 if let Some(expected) = self.expected_value {
544 symmetry_value == expected
545 } else {
546 true }
548 }
549
550 fn compute_symmetry(&self, bitstring: &str) -> i32 {
552 match self.symmetry_type {
553 SymmetryType::ParticleNumber => bitstring.chars().filter(|&c| c == '1').count() as i32,
554 SymmetryType::Parity => {
555 let ones = bitstring.chars().filter(|&c| c == '1').count();
556 (ones % 2) as i32
557 }
558 SymmetryType::Custom => 0, }
560 }
561
562 pub fn filter_counts(&self, counts: &HashMap<String, usize>) -> HashMap<String, usize> {
564 counts
565 .iter()
566 .filter(|(bitstring, _)| self.verify(bitstring))
567 .map(|(k, v)| (k.clone(), *v))
568 .collect()
569 }
570
571 pub fn filter_and_normalize(&self, counts: &HashMap<String, usize>) -> HashMap<String, usize> {
573 let total_shots: usize = counts.values().sum();
574 let filtered = self.filter_counts(counts);
575 let filtered_total: usize = filtered.values().sum();
576
577 if filtered_total == 0 {
578 return HashMap::new();
579 }
580
581 let scale = total_shots as f64 / filtered_total as f64;
583 filtered
584 .iter()
585 .map(|(k, v)| (k.clone(), (*v as f64 * scale).round() as usize))
586 .collect()
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 #[test]
595 fn test_zne_linear_extrapolation() {
596 let zne = ZeroNoiseExtrapolation::new(ExtrapolationMethod::Linear);
597 let values = vec![0.8, 0.6, 0.4];
598 let scales = vec![1.0, 2.0, 3.0];
599
600 let result = zne.apply(&values, &scales).unwrap();
601 assert!((result - 1.0).abs() < 0.01); }
603
604 #[test]
605 fn test_zne_richardson_extrapolation() {
606 let zne = ZeroNoiseExtrapolation::new(ExtrapolationMethod::Richardson);
607 let values = vec![1.0, 0.9, 0.6];
609 let scales = vec![0.0, 1.0, 2.0];
610
611 let result = zne.apply(&values, &scales).unwrap();
612 assert!((result - 1.0).abs() < 0.01);
613 }
614
615 #[test]
616 fn test_zne_invalid_input() {
617 let zne = ZeroNoiseExtrapolation::new(ExtrapolationMethod::Linear);
618 let values = vec![0.8];
619 let scales = vec![1.0, 2.0];
620
621 let result = zne.apply(&values, &scales);
622 assert!(result.is_err());
623 }
624
625 #[test]
626 fn test_measurement_error_mitigation_identity() {
627 let mut mem = MeasurementErrorMitigation::new(2);
628
629 let mut counts = HashMap::new();
630 counts.insert("00".to_string(), 100);
631 counts.insert("11".to_string(), 50);
632
633 let mitigated = mem.apply(&counts).unwrap();
634
635 assert!((mitigated["00"] - 100.0).abs() < 1e-6);
637 assert!((mitigated["11"] - 50.0).abs() < 1e-6);
638 }
639
640 #[test]
641 fn test_measurement_error_mitigation_from_error_rates() {
642 let mem = MeasurementErrorMitigation::from_error_rates(1, 0.1, 0.05).unwrap();
643
644 let matrix = &mem.calibration_matrix;
646 assert_eq!(matrix.nrows(), 2);
647 assert_eq!(matrix.ncols(), 2);
648
649 assert!((matrix[[0, 0]] - 0.9).abs() < 1e-10); assert!((matrix[[1, 0]] - 0.1).abs() < 1e-10); assert!((matrix[[0, 1]] - 0.05).abs() < 1e-10); assert!((matrix[[1, 1]] - 0.95).abs() < 1e-10); }
655
656 #[test]
657 fn test_symmetry_particle_number() {
658 let sym = SymmetryVerification::new(SymmetryType::ParticleNumber).with_expected_value(2);
659
660 assert!(sym.verify("0011"));
661 assert!(sym.verify("1100"));
662 assert!(sym.verify("1010"));
663 assert!(!sym.verify("0001"));
664 assert!(!sym.verify("1111"));
665 }
666
667 #[test]
668 fn test_symmetry_parity() {
669 let sym = SymmetryVerification::new(SymmetryType::Parity).with_expected_value(0);
670
671 assert!(sym.verify("0011")); assert!(sym.verify("1100")); assert!(!sym.verify("0001")); assert!(!sym.verify("0111")); }
676
677 #[test]
678 fn test_symmetry_filter_counts() {
679 let sym = SymmetryVerification::new(SymmetryType::ParticleNumber).with_expected_value(2);
680
681 let mut counts = HashMap::new();
682 counts.insert("0011".to_string(), 100);
683 counts.insert("1100".to_string(), 50);
684 counts.insert("0001".to_string(), 30); counts.insert("1111".to_string(), 20); let filtered = sym.filter_counts(&counts);
688
689 assert_eq!(filtered.len(), 2);
690 assert_eq!(filtered["0011"], 100);
691 assert_eq!(filtered["1100"], 50);
692 assert!(!filtered.contains_key("0001"));
693 assert!(!filtered.contains_key("1111"));
694 }
695
696 #[test]
697 fn test_zne_custom_scale_factors() {
698 let result = ZeroNoiseExtrapolation::with_scale_factors(
699 ExtrapolationMethod::Linear,
700 vec![1.0, 1.5, 2.0],
701 );
702 assert!(result.is_ok());
703
704 let bad_result = ZeroNoiseExtrapolation::with_scale_factors(
705 ExtrapolationMethod::Linear,
706 vec![2.0, 1.0], );
708 assert!(bad_result.is_err());
709 }
710}