cqlib_core/error_mitigation/
virtual_distillation.rs1use std::collections::HashMap;
14
15use crate::circuit::{Circuit, CircuitError, CircuitParam, ParameterValue, Qubit};
16use crate::error_mitigation::ErrorMitigationError;
17use crate::error_mitigation::Estimator;
18use crate::qis::{Hamiltonian, Pauli, PauliString};
19
20#[derive(Debug, Clone)]
38pub struct VirtualDistillation {
39 circuit: Circuit,
40 copies: usize,
41}
42
43impl VirtualDistillation {
44 pub fn new(circuit: Circuit, copies: usize) -> Result<Self, ErrorMitigationError> {
46 if copies < 2 {
47 return Err(ErrorMitigationError::InvalidCopies(copies));
48 }
49
50 Ok(Self { circuit, copies })
51 }
52
53 pub fn copies(&self) -> usize {
55 self.copies
56 }
57
58 pub fn set_copies(&mut self, copies: usize) -> Result<(), ErrorMitigationError> {
60 if copies < 2 {
61 return Err(ErrorMitigationError::InvalidCopies(copies));
62 }
63
64 self.copies = copies;
65 Ok(())
66 }
67
68 pub fn build_copy_swap_circuit(&self) -> Result<Circuit, CircuitError> {
74 let base_circuit = self.circuit.decompose()?;
75 let base_width = base_circuit.width();
76 let mut copy_swap_circuit = Circuit::new(self.copies * base_width);
77
78 for copy_index in 0..self.copies {
79 let copy_offset = copy_index * base_width;
80 Self::append_circuit_with_offset(&mut copy_swap_circuit, &base_circuit, copy_offset)?;
81 }
82
83 for other_copy in 1..self.copies {
84 let first_copy_offset = 0;
85 let other_copy_offset = other_copy * base_width;
86 for qubit_index in 0..base_width {
87 let left = Qubit::new((first_copy_offset + qubit_index) as u32);
88 let right = Qubit::new((other_copy_offset + qubit_index) as u32);
89 copy_swap_circuit.swap(left, right)?;
90 }
91 }
92
93 Ok(copy_swap_circuit)
94 }
95
96 pub(crate) fn expand_hamiltonian(
103 hamiltonian: &Hamiltonian,
104 n: usize,
105 ) -> Result<Hamiltonian, ErrorMitigationError> {
106 if hamiltonian.terms.is_empty() {
107 return Ok(Hamiltonian::new(hamiltonian.num_qubits + n));
108 }
109
110 let expanded_terms = hamiltonian
111 .terms
112 .iter()
113 .map(|(term, coeff)| {
114 let mut expanded_term = PauliString::new(hamiltonian.num_qubits + n);
115 expanded_term.phase = term.phase;
116
117 for qubit in 0..hamiltonian.num_qubits {
118 let pauli = match (term.x[qubit], term.z[qubit]) {
119 (false, false) => Pauli::I,
120 (true, false) => Pauli::X,
121 (false, true) => Pauli::Z,
122 (true, true) => Pauli::Y,
123 };
124 expanded_term.set_pauli(qubit, pauli);
125 }
126
127 for qubit in hamiltonian.num_qubits..(hamiltonian.num_qubits + n) {
128 expanded_term.set_pauli(qubit, Pauli::Z);
129 }
130
131 (expanded_term, *coeff)
132 })
133 .collect();
134
135 Ok(Hamiltonian::from_list(expanded_terms)?)
136 }
137
138 pub fn run_denominator_circuit(
148 &self,
149 shots: usize,
150 estimator: &Estimator<'_>,
151 ) -> Result<(f64, f64), CircuitError> {
152 let denominator_circuit = self.build_copy_swap_circuit()?;
153
154 Ok(estimator(&denominator_circuit, None, Some(shots)))
155 }
156
157 pub fn run_numerator_circuit(
168 &self,
169 hamiltonian: &Hamiltonian,
170 shots: usize,
171 estimator: &Estimator<'_>,
172 ) -> Result<(f64, f64), ErrorMitigationError> {
173 let numerator_circuit = self.build_copy_swap_circuit()?;
174 let extra_qubits = (self.copies - 1) * hamiltonian.num_qubits;
175 let expanded_hamiltonian = Self::expand_hamiltonian(hamiltonian, extra_qubits)?;
176 if expanded_hamiltonian.num_qubits != numerator_circuit.width() {
177 return Err(CircuitError::QubitCountMismatch {
178 expected: numerator_circuit.width(),
179 actual: expanded_hamiltonian.num_qubits,
180 }
181 .into());
182 }
183
184 Ok(estimator(
185 &numerator_circuit,
186 Some(&expanded_hamiltonian),
187 Some(shots),
188 ))
189 }
190
191 pub fn run_vd(
203 &self,
204 hamiltonian: &Hamiltonian,
205 shots_numerator: usize,
206 shots_denominator: usize,
207 estimator: &Estimator<'_>,
208 ) -> Result<(f64, f64), ErrorMitigationError> {
209 if hamiltonian.num_qubits != self.circuit.width() {
210 return Err(ErrorMitigationError::HamiltonianQubitCountMismatch {
211 expected: self.circuit.width(),
212 actual: hamiltonian.num_qubits,
213 });
214 }
215
216 let (mu_numerator, var_numerator) =
217 self.run_numerator_circuit(hamiltonian, shots_numerator, estimator)?;
218 let (mu_denominator, var_denominator) =
219 self.run_denominator_circuit(shots_denominator, estimator)?;
220 let (mu_vd, var_vd) = Self::mitigate_from_statistics(
221 mu_numerator,
222 var_numerator,
223 mu_denominator,
224 var_denominator,
225 )?;
226
227 Ok((mu_vd, var_vd))
228 }
229
230 pub(crate) fn mitigate_from_statistics(
231 mu_numerator: f64,
232 var_numerator: f64,
233 mu_denominator: f64,
234 var_denominator: f64,
235 ) -> Result<(f64, f64), ErrorMitigationError> {
236 if mu_denominator == 0.0 {
237 return Err(ErrorMitigationError::ZeroDenominatorMean);
238 }
239
240 let mu_vd = mu_numerator / mu_denominator;
241
242 let var_vd = var_numerator / mu_denominator.powi(2)
245 + mu_numerator.powi(2) * var_denominator / mu_denominator.powi(4);
246 Ok((mu_vd, var_vd))
247 }
248
249 fn append_circuit_with_offset(
250 target_circuit: &mut Circuit,
251 source_circuit: &Circuit,
252 qubit_offset: usize,
253 ) -> Result<(), CircuitError> {
254 let source_qubits = source_circuit.qubits();
255 let qubit_positions: HashMap<_, _> = source_qubits
256 .iter()
257 .enumerate()
258 .map(|(position, qubit)| (*qubit, position))
259 .collect();
260
261 for op in source_circuit.operations() {
262 let mapped_qubits: Vec<Qubit> = op
263 .qubits
264 .iter()
265 .map(|qubit| {
266 let position = qubit_positions[qubit];
267 Qubit::new((qubit_offset + position) as u32)
268 })
269 .collect();
270 let mapped_params: Vec<ParameterValue> = op
271 .params
272 .iter()
273 .map(|param| match param {
274 CircuitParam::Fixed(value) => ParameterValue::Fixed(*value),
275 CircuitParam::Index(index) => {
276 source_circuit.parameters()[*index as usize].clone().into()
277 }
278 })
279 .collect();
280
281 target_circuit.append(
282 op.instruction.clone(),
283 mapped_qubits,
284 mapped_params,
285 op.label.as_deref(),
286 )?;
287 }
288
289 Ok(())
290 }
291}
292
293#[cfg(test)]
294#[path = "./virtual_distillation_test.rs"]
295mod virtual_distillation_test;