cqlib_core/error_mitigation/
zne_mitigation.rs1use crate::circuit::{Circuit, CircuitError, Instruction, Operation, Parameter};
14use crate::error_mitigation::ErrorMitigationError;
15use crate::error_mitigation::Estimator;
16use crate::qis::Hamiltonian;
17use std::collections::HashSet;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum ExtrapolateMethod {
22 Polynomial,
23 Exponential,
24}
25
26#[derive(Debug, Clone)]
59pub struct ZNEMitigation {
60 circuit: Circuit,
61 fold_levels: Vec<i32>,
62 noise_factors: Vec<i32>,
63}
64
65impl ZNEMitigation {
66 pub fn new(circuit: Circuit, fold_levels: Vec<i32>) -> Self {
70 let noise_factors = fold_levels.iter().map(|level| 2 * level + 1).collect();
71 Self {
72 circuit,
73 fold_levels,
74 noise_factors,
75 }
76 }
77
78 pub fn circuit(&self) -> &Circuit {
80 &self.circuit
81 }
82
83 pub fn fold_levels(&self) -> &[i32] {
85 &self.fold_levels
86 }
87
88 pub fn noise_factors(&self) -> &[i32] {
92 &self.noise_factors
93 }
94
95 pub fn fold_circuits(
103 &self,
104 gate_set: Option<&[Instruction]>,
105 ) -> Result<Vec<Circuit>, CircuitError> {
106 self.fold_levels
107 .iter()
108 .map(|level| self.fold_to_level(*level, gate_set))
109 .collect()
110 }
111
112 pub fn run_em_sequence(
123 &self,
124 gate_set: Option<&[Instruction]>,
125 hamiltonian: &Hamiltonian,
126 estimator: &Estimator<'_>,
127 ) -> Result<Vec<f64>, ErrorMitigationError> {
128 self.run_em_sequence_with_shots(gate_set, hamiltonian, None, estimator)
129 }
130
131 pub fn run_em_sequence_with_shots(
133 &self,
134 gate_set: Option<&[Instruction]>,
135 hamiltonian: &Hamiltonian,
136 shots: Option<usize>,
137 estimator: &Estimator<'_>,
138 ) -> Result<Vec<f64>, ErrorMitigationError> {
139 if hamiltonian.num_qubits != self.circuit.width() {
140 return Err(ErrorMitigationError::HamiltonianQubitCountMismatch {
141 expected: self.circuit.width(),
142 actual: hamiltonian.num_qubits,
143 });
144 }
145
146 let mut hexp_seq = Vec::new();
147 for level in self.fold_levels() {
148 let circuit = self.fold_to_level(*level, gate_set)?;
149 let (expectation, _variance) = estimator(&circuit, Some(hamiltonian), shots);
150 hexp_seq.push(expectation);
151 }
152 Ok(hexp_seq)
153 }
154
155 pub fn extrapolate(
162 &self,
163 noisy_results: &[f64],
164 method: ExtrapolateMethod,
165 degree: usize,
166 ) -> Result<f64, ErrorMitigationError> {
167 match method {
168 ExtrapolateMethod::Polynomial => self.poly_extrapolate(noisy_results, degree),
169 ExtrapolateMethod::Exponential => self.exp_extrapolate(noisy_results),
170 }
171 }
172
173 pub fn poly_extrapolate(
180 &self,
181 noisy_results: &[f64],
182 degree: usize,
183 ) -> Result<f64, ErrorMitigationError> {
184 let n = self.noise_factors.len();
185 self.validate_noisy_results(noisy_results)?;
186 if degree >= n {
187 return Err(ErrorMitigationError::InvalidPolynomialDegree {
188 degree,
189 num_points: n,
190 });
191 }
192
193 let d = degree + 1;
194 let x: Vec<f64> = self.noise_factors.iter().map(|&v| v as f64).collect();
195 let y = noisy_results;
196
197 let mut a = vec![vec![0.0; d]; d];
200 let mut b = vec![0.0; d];
201 for row in 0..n {
202 let mut x_pows = vec![1.0; d];
203 for j in 1..d {
204 x_pows[j] = x_pows[j - 1] * x[row];
205 }
206
207 for j in 0..d {
208 b[j] += y[row] * x_pows[j];
209 for k in 0..d {
210 a[j][k] += x_pows[j] * x_pows[k];
211 }
212 }
213 }
214
215 let coeffs = Self::solve_linear_system(a, b)?;
216 Ok(coeffs[0])
217 }
218
219 pub fn exp_extrapolate(&self, noisy_results: &[f64]) -> Result<f64, ErrorMitigationError> {
230 let n = self.noise_factors.len();
231 self.validate_noisy_results(noisy_results)?;
232 if !noisy_results.iter().all(|&v| v > 0.0) {
233 return Err(ErrorMitigationError::NonPositiveNoisyResults);
234 }
235
236 let x: Vec<f64> = self.noise_factors.iter().map(|&v| v as f64).collect();
237 let y_log: Vec<f64> = noisy_results.iter().map(|&v| v.ln()).collect();
238
239 let n_f = n as f64;
240 let sum_x: f64 = x.iter().sum();
241 let sum_y: f64 = y_log.iter().sum();
242 let sum_xx: f64 = x.iter().map(|v| v * v).sum();
243 let sum_xy: f64 = x.iter().zip(y_log.iter()).map(|(xi, yi)| xi * yi).sum();
244
245 let denom = n_f * sum_xx - sum_x * sum_x;
246 if denom.abs() <= 1e-14 {
247 return Err(ErrorMitigationError::SingularExponentialFit);
248 }
249
250 let slope = (n_f * sum_xy - sum_x * sum_y) / denom;
251 let intercept = (sum_y - slope * sum_x) / n_f;
252 Ok(intercept.exp())
253 }
254
255 fn solve_linear_system(
256 mut a: Vec<Vec<f64>>,
257 mut b: Vec<f64>,
258 ) -> Result<Vec<f64>, ErrorMitigationError> {
259 let n = a.len();
260 debug_assert!(n > 0, "Coefficient matrix must not be empty.");
261 debug_assert_eq!(b.len(), n, "Right-hand side length must match matrix size.");
262 for row in &a {
263 debug_assert_eq!(row.len(), n, "Coefficient matrix must be square.");
264 }
265
266 let eps = 1e-14_f64;
267
268 for i in 0..n {
269 let mut pivot_row = i;
271 let mut pivot_abs = a[i][i].abs();
272
273 #[allow(clippy::needless_range_loop)]
274 for r in (i + 1)..n {
275 let cand = a[r][i].abs();
276 if cand > pivot_abs {
277 pivot_abs = cand;
278 pivot_row = r;
279 }
280 }
281
282 if pivot_abs <= eps {
283 return Err(ErrorMitigationError::SingularPolynomialFit);
284 }
285
286 if pivot_row != i {
287 a.swap(i, pivot_row);
288 b.swap(i, pivot_row);
289 }
290
291 for r in (i + 1)..n {
292 let factor = a[r][i] / a[i][i];
293 a[r][i] = 0.0;
294 #[allow(clippy::needless_range_loop)]
295 for c in (i + 1)..n {
296 a[r][c] -= factor * a[i][c];
297 }
298 b[r] -= factor * b[i];
299 }
300 }
301
302 let mut x = vec![0.0; n];
303 for i in (0..n).rev() {
304 let mut sum = b[i];
305 for (j, xj) in x.iter().enumerate().skip(i + 1) {
306 sum -= a[i][j] * *xj;
307 }
308 x[i] = sum / a[i][i];
309 }
310
311 Ok(x)
312 }
313
314 fn validate_noisy_results(&self, noisy_results: &[f64]) -> Result<(), ErrorMitigationError> {
315 if noisy_results.is_empty() {
316 return Err(ErrorMitigationError::EmptyNoisyResults);
317 }
318
319 let expected = self.noise_factors.len();
320 let actual = noisy_results.len();
321 if actual != expected {
322 return Err(ErrorMitigationError::NoisyResultsLengthMismatch { expected, actual });
323 }
324
325 Ok(())
326 }
327
328 fn fold_to_level(
329 &self,
330 level: i32,
331 gate_set: Option<&[Instruction]>,
332 ) -> Result<Circuit, CircuitError> {
333 if level < 0 {
334 return Err(CircuitError::InvalidControlOperation(
335 "Fold level must be non-negative.".to_string(),
336 ));
337 }
338
339 if level == 0 {
340 return Ok(self.circuit.clone());
341 }
342
343 match gate_set {
344 None => self.fold_all(level as usize),
345 Some(gates) => self.fold_selected(level as usize, gates),
346 }
347 }
348
349 fn fold_all(&self, level: usize) -> Result<Circuit, CircuitError> {
350 let mut folded = Circuit::from_qubits(self.circuit.qubits())?;
351 folded.set_global_phase(self.circuit.global_phase());
352 let inverse = self.circuit.inverse()?;
353
354 self.append_circuit_ops(&mut folded, &self.circuit)?;
355 for _ in 0..level {
356 self.append_circuit_ops(&mut folded, &inverse)?;
357 self.append_circuit_ops(&mut folded, &self.circuit)?;
358 }
359
360 Ok(folded)
361 }
362
363 fn fold_selected(
364 &self,
365 level: usize,
366 gate_set: &[Instruction],
367 ) -> Result<Circuit, CircuitError> {
368 let gate_names: HashSet<String> = gate_set.iter().map(Instruction::name).collect();
369 let mut folded = Circuit::from_qubits(self.circuit.qubits())?;
370 folded.set_global_phase(self.circuit.global_phase());
371
372 for op in self.circuit.operations() {
373 self.append_operation(&mut folded, &self.circuit, op)?;
374 if gate_names.contains(&op.instruction.name()) {
375 for _ in 0..level {
376 let inv = self.invert_operation(op)?;
377 self.append_operation(&mut folded, &inv.0, &inv.1)?;
378 self.append_operation(&mut folded, &self.circuit, op)?;
379 }
380 }
381 }
382
383 Ok(folded)
384 }
385
386 fn append_circuit_ops(
387 &self,
388 target: &mut Circuit,
389 source: &Circuit,
390 ) -> Result<(), CircuitError> {
391 for op in source.operations() {
392 self.append_operation(target, source, op)?;
393 }
394 Ok(())
395 }
396
397 fn append_operation(
398 &self,
399 target: &mut Circuit,
400 source: &Circuit,
401 op: &Operation,
402 ) -> Result<(), CircuitError> {
403 let params = op.params.iter().map(|param| match param {
404 crate::circuit::circuit_param::CircuitParam::Fixed(value) => (*value).into(),
405 crate::circuit::circuit_param::CircuitParam::Index(index) => {
406 source.parameters()[*index as usize].clone().into()
407 }
408 });
409
410 target.append(
411 op.instruction.clone(),
412 op.qubits.iter().copied(),
413 params,
414 op.label.as_deref(),
415 )
416 }
417
418 fn invert_operation(&self, op: &Operation) -> Result<(Circuit, Operation), CircuitError> {
419 let op_params: Vec<Parameter> = op
420 .params
421 .iter()
422 .map(|param| match param {
423 crate::circuit::circuit_param::CircuitParam::Fixed(value) => {
424 Parameter::from(*value)
425 }
426 crate::circuit::circuit_param::CircuitParam::Index(index) => {
427 self.circuit.parameters()[*index as usize].clone()
428 }
429 })
430 .collect();
431
432 let (inv_inst, inv_params) = op
433 .instruction
434 .inverse(&op_params)
435 .ok_or(CircuitError::IrreversibleOperation)?;
436
437 let mut inv_circuit = Circuit::from_qubits(self.circuit.qubits())?;
438 inv_circuit.append(
439 inv_inst,
440 op.qubits.iter().copied(),
441 inv_params.into_iter().map(Into::into),
442 op.label.as_deref(),
443 )?;
444 let inv_op = inv_circuit.operations()[0].clone();
445
446 Ok((inv_circuit, inv_op))
447 }
448}
449
450#[cfg(test)]
451#[path = "./zne_mitigation_test.rs"]
452mod zne_mitigation_test;