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 {
201 1 => self.linear_extrapolation(values, scales),
202 2 => self.richardson_extrapolation(values, scales),
203 _ => Err(SimulatorError::NotImplemented(
204 "Polynomial degree > 2 not yet implemented".to_string(),
205 )),
206 }
207 }
208
209 fn exponential_extrapolation(&self, values: &[f64], scales: &[f64]) -> Result<f64> {
211 if values.len() < 3 {
212 return Err(SimulatorError::InvalidInput(
213 "Exponential extrapolation requires at least 3 points".to_string(),
214 ));
215 }
216
217 let x0 = scales[0];
220 let x1 = scales[1];
221 let y0 = values[0];
222 let y1 = values[1];
223
224 if (y1 - y0).abs() < 1e-10 {
226 return Ok(y0); }
228
229 let b_estimate = -((y1 - y0) / (x1 - x0)) / y0.max(1e-10);
230 let c_estimate = y0 / (1.0 + b_estimate * x0).max(1e-10);
231
232 Ok(c_estimate)
233 }
234
235 pub fn scale_factors(&self) -> &[f64] {
237 &self.scale_factors
238 }
239}
240
241#[derive(Debug, Clone)]
250pub struct MeasurementErrorMitigation {
251 calibration_matrix: Array2<f64>,
253 inverse_matrix: Option<Array2<f64>>,
255 n_qubits: usize,
257}
258
259impl MeasurementErrorMitigation {
260 pub fn new(n_qubits: usize) -> Self {
266 let dim = 1 << n_qubits; let calibration_matrix = Array2::eye(dim); Self {
270 calibration_matrix,
271 inverse_matrix: None,
272 n_qubits,
273 }
274 }
275
276 pub fn set_calibration_matrix(&mut self, matrix: Array2<f64>) -> Result<()> {
281 let expected_dim = 1 << self.n_qubits;
282 if matrix.nrows() != expected_dim || matrix.ncols() != expected_dim {
283 return Err(SimulatorError::InvalidInput(format!(
284 "Calibration matrix must be {}x{} for {} qubits",
285 expected_dim, expected_dim, self.n_qubits
286 )));
287 }
288
289 for col in 0..matrix.ncols() {
291 let sum: f64 = (0..matrix.nrows()).map(|row| matrix[[row, col]]).sum();
292 if (sum - 1.0).abs() > 1e-6 {
293 return Err(SimulatorError::InvalidInput(format!(
294 "Column {} does not sum to 1.0 (sum = {})",
295 col, sum
296 )));
297 }
298 }
299
300 self.calibration_matrix = matrix;
301 self.inverse_matrix = None; Ok(())
303 }
304
305 fn compute_inverse(&mut self) -> Result<()> {
307 if self.inverse_matrix.is_some() {
308 return Ok(());
309 }
310
311 let matrix = &self.calibration_matrix;
314 let n = matrix.nrows();
315
316 let mut augmented = Array2::zeros((n, 2 * n));
318 for i in 0..n {
319 for j in 0..n {
320 augmented[[i, j]] = matrix[[i, j]];
321 augmented[[i, j + n]] = if i == j { 1.0 } else { 0.0 };
322 }
323 }
324
325 for i in 0..n {
327 let pivot = augmented[[i, i]];
329 if pivot.abs() < 1e-10 {
330 return Err(SimulatorError::InvalidInput(
331 "Calibration matrix is singular or nearly singular".to_string(),
332 ));
333 }
334
335 for j in 0..(2 * n) {
337 augmented[[i, j]] /= pivot;
338 }
339
340 for k in 0..n {
342 if k != i {
343 let factor = augmented[[k, i]];
344 for j in 0..(2 * n) {
345 augmented[[k, j]] -= factor * augmented[[i, j]];
346 }
347 }
348 }
349 }
350
351 let mut inverse = Array2::zeros((n, n));
353 for i in 0..n {
354 for j in 0..n {
355 inverse[[i, j]] = augmented[[i, j + n]];
356 }
357 }
358
359 self.inverse_matrix = Some(inverse);
360 Ok(())
361 }
362
363 pub fn apply(&mut self, noisy_counts: &HashMap<String, usize>) -> Result<HashMap<String, f64>> {
373 self.compute_inverse()?;
374 let inverse = self.inverse_matrix.as_ref().ok_or_else(|| {
375 SimulatorError::InvalidInput(
376 "Inverse matrix not yet computed — call compute_inverse() first".to_string(),
377 )
378 })?;
379
380 let dim = 1 << self.n_qubits;
381 let total_shots: usize = noisy_counts.values().sum();
382
383 let mut noisy_probs = Array1::zeros(dim);
385 for (bitstring, count) in noisy_counts {
386 if bitstring.len() != self.n_qubits {
387 return Err(SimulatorError::InvalidInput(format!(
388 "Bitstring {} has wrong length (expected {})",
389 bitstring, self.n_qubits
390 )));
391 }
392 let index = usize::from_str_radix(bitstring, 2).map_err(|_| {
393 SimulatorError::InvalidInput(format!("Invalid bitstring: {}", bitstring))
394 })?;
395 noisy_probs[index] = *count as f64 / total_shots as f64;
396 }
397
398 let mitigated_probs = inverse.dot(&noisy_probs);
400
401 let mut mitigated_counts = HashMap::new();
403 for i in 0..dim {
404 let bitstring = format!("{:0width$b}", i, width = self.n_qubits);
405 let mitigated_count = mitigated_probs[i] * total_shots as f64;
406 if mitigated_count.abs() > 1e-10 {
407 mitigated_counts.insert(bitstring, mitigated_count);
408 }
409 }
410
411 Ok(mitigated_counts)
412 }
413
414 pub fn from_error_rates(
421 n_qubits: usize,
422 readout_error_0: f64,
423 readout_error_1: f64,
424 ) -> Result<Self> {
425 if !(0.0..=1.0).contains(&readout_error_0) {
426 return Err(SimulatorError::InvalidInput(
427 "readout_error_0 must be in [0, 1]".to_string(),
428 ));
429 }
430 if !(0.0..=1.0).contains(&readout_error_1) {
431 return Err(SimulatorError::InvalidInput(
432 "readout_error_1 must be in [0, 1]".to_string(),
433 ));
434 }
435
436 let dim = 1 << n_qubits;
437 let mut matrix = Array2::zeros((dim, dim));
438
439 for prepared in 0..dim {
442 for measured in 0..dim {
443 let mut prob = 1.0;
444 for qubit in 0..n_qubits {
445 let prepared_bit = (prepared >> qubit) & 1;
446 let measured_bit = (measured >> qubit) & 1;
447
448 let p = if prepared_bit == 0 {
449 if measured_bit == 0 {
450 1.0 - readout_error_0
451 } else {
452 readout_error_0
453 }
454 } else if measured_bit == 1 {
455 1.0 - readout_error_1
456 } else {
457 readout_error_1
458 };
459
460 prob *= p;
461 }
462 matrix[[measured, prepared]] = prob;
463 }
464 }
465
466 let mut mem = Self::new(n_qubits);
467 mem.set_calibration_matrix(matrix)?;
468 Ok(mem)
469 }
470}
471
472#[derive(Debug, Clone)]
482pub struct SymmetryVerification {
483 symmetry_type: SymmetryType,
485 expected_value: Option<i32>,
487}
488
489#[derive(Debug, Clone, Copy, PartialEq, Eq)]
491pub enum SymmetryType {
492 ParticleNumber,
494 Parity,
496 Custom,
498}
499
500impl SymmetryVerification {
501 pub fn new(symmetry_type: SymmetryType) -> Self {
503 Self {
504 symmetry_type,
505 expected_value: None,
506 }
507 }
508
509 pub fn with_expected_value(mut self, value: i32) -> Self {
511 self.expected_value = Some(value);
512 self
513 }
514
515 pub fn verify(&self, bitstring: &str) -> bool {
517 let symmetry_value = self.compute_symmetry(bitstring);
518
519 if let Some(expected) = self.expected_value {
520 symmetry_value == expected
521 } else {
522 true }
524 }
525
526 fn compute_symmetry(&self, bitstring: &str) -> i32 {
528 match self.symmetry_type {
529 SymmetryType::ParticleNumber => bitstring.chars().filter(|&c| c == '1').count() as i32,
530 SymmetryType::Parity => {
531 let ones = bitstring.chars().filter(|&c| c == '1').count();
532 (ones % 2) as i32
533 }
534 SymmetryType::Custom => 0, }
536 }
537
538 pub fn filter_counts(&self, counts: &HashMap<String, usize>) -> HashMap<String, usize> {
540 counts
541 .iter()
542 .filter(|(bitstring, _)| self.verify(bitstring))
543 .map(|(k, v)| (k.clone(), *v))
544 .collect()
545 }
546
547 pub fn filter_and_normalize(&self, counts: &HashMap<String, usize>) -> HashMap<String, usize> {
549 let total_shots: usize = counts.values().sum();
550 let filtered = self.filter_counts(counts);
551 let filtered_total: usize = filtered.values().sum();
552
553 if filtered_total == 0 {
554 return HashMap::new();
555 }
556
557 let scale = total_shots as f64 / filtered_total as f64;
559 filtered
560 .iter()
561 .map(|(k, v)| (k.clone(), (*v as f64 * scale).round() as usize))
562 .collect()
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569
570 #[test]
571 fn test_zne_linear_extrapolation() {
572 let zne = ZeroNoiseExtrapolation::new(ExtrapolationMethod::Linear);
573 let values = vec![0.8, 0.6, 0.4];
574 let scales = vec![1.0, 2.0, 3.0];
575
576 let result = zne.apply(&values, &scales).unwrap();
577 assert!((result - 1.0).abs() < 0.01); }
579
580 #[test]
581 fn test_zne_richardson_extrapolation() {
582 let zne = ZeroNoiseExtrapolation::new(ExtrapolationMethod::Richardson);
583 let values = vec![1.0, 0.9, 0.6];
585 let scales = vec![0.0, 1.0, 2.0];
586
587 let result = zne.apply(&values, &scales).unwrap();
588 assert!((result - 1.0).abs() < 0.01);
589 }
590
591 #[test]
592 fn test_zne_invalid_input() {
593 let zne = ZeroNoiseExtrapolation::new(ExtrapolationMethod::Linear);
594 let values = vec![0.8];
595 let scales = vec![1.0, 2.0];
596
597 let result = zne.apply(&values, &scales);
598 assert!(result.is_err());
599 }
600
601 #[test]
602 fn test_measurement_error_mitigation_identity() {
603 let mut mem = MeasurementErrorMitigation::new(2);
604
605 let mut counts = HashMap::new();
606 counts.insert("00".to_string(), 100);
607 counts.insert("11".to_string(), 50);
608
609 let mitigated = mem.apply(&counts).unwrap();
610
611 assert!((mitigated["00"] - 100.0).abs() < 1e-6);
613 assert!((mitigated["11"] - 50.0).abs() < 1e-6);
614 }
615
616 #[test]
617 fn test_measurement_error_mitigation_from_error_rates() {
618 let mem = MeasurementErrorMitigation::from_error_rates(1, 0.1, 0.05).unwrap();
619
620 let matrix = &mem.calibration_matrix;
622 assert_eq!(matrix.nrows(), 2);
623 assert_eq!(matrix.ncols(), 2);
624
625 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); }
631
632 #[test]
633 fn test_symmetry_particle_number() {
634 let sym = SymmetryVerification::new(SymmetryType::ParticleNumber).with_expected_value(2);
635
636 assert!(sym.verify("0011"));
637 assert!(sym.verify("1100"));
638 assert!(sym.verify("1010"));
639 assert!(!sym.verify("0001"));
640 assert!(!sym.verify("1111"));
641 }
642
643 #[test]
644 fn test_symmetry_parity() {
645 let sym = SymmetryVerification::new(SymmetryType::Parity).with_expected_value(0);
646
647 assert!(sym.verify("0011")); assert!(sym.verify("1100")); assert!(!sym.verify("0001")); assert!(!sym.verify("0111")); }
652
653 #[test]
654 fn test_symmetry_filter_counts() {
655 let sym = SymmetryVerification::new(SymmetryType::ParticleNumber).with_expected_value(2);
656
657 let mut counts = HashMap::new();
658 counts.insert("0011".to_string(), 100);
659 counts.insert("1100".to_string(), 50);
660 counts.insert("0001".to_string(), 30); counts.insert("1111".to_string(), 20); let filtered = sym.filter_counts(&counts);
664
665 assert_eq!(filtered.len(), 2);
666 assert_eq!(filtered["0011"], 100);
667 assert_eq!(filtered["1100"], 50);
668 assert!(!filtered.contains_key("0001"));
669 assert!(!filtered.contains_key("1111"));
670 }
671
672 #[test]
673 fn test_zne_custom_scale_factors() {
674 let result = ZeroNoiseExtrapolation::with_scale_factors(
675 ExtrapolationMethod::Linear,
676 vec![1.0, 1.5, 2.0],
677 );
678 assert!(result.is_ok());
679
680 let bad_result = ZeroNoiseExtrapolation::with_scale_factors(
681 ExtrapolationMethod::Linear,
682 vec![2.0, 1.0], );
684 assert!(bad_result.is_err());
685 }
686}