use std::collections::HashMap;
use crate::circuit::{Circuit, CircuitError, CircuitParam, ParameterValue, Qubit};
use crate::error_mitigation::ErrorMitigationError;
use crate::error_mitigation::Estimator;
use crate::qis::{Hamiltonian, Pauli, PauliString};
#[derive(Debug, Clone)]
pub struct VirtualDistillation {
circuit: Circuit,
copies: usize,
}
impl VirtualDistillation {
pub fn new(circuit: Circuit, copies: usize) -> Result<Self, ErrorMitigationError> {
if copies < 2 {
return Err(ErrorMitigationError::InvalidCopies(copies));
}
Ok(Self { circuit, copies })
}
pub fn copies(&self) -> usize {
self.copies
}
pub fn set_copies(&mut self, copies: usize) -> Result<(), ErrorMitigationError> {
if copies < 2 {
return Err(ErrorMitigationError::InvalidCopies(copies));
}
self.copies = copies;
Ok(())
}
pub fn build_copy_swap_circuit(&self) -> Result<Circuit, CircuitError> {
let base_circuit = self.circuit.decompose()?;
let base_width = base_circuit.width();
let mut copy_swap_circuit = Circuit::new(self.copies * base_width);
for copy_index in 0..self.copies {
let copy_offset = copy_index * base_width;
Self::append_circuit_with_offset(&mut copy_swap_circuit, &base_circuit, copy_offset)?;
}
for other_copy in 1..self.copies {
let first_copy_offset = 0;
let other_copy_offset = other_copy * base_width;
for qubit_index in 0..base_width {
let left = Qubit::new((first_copy_offset + qubit_index) as u32);
let right = Qubit::new((other_copy_offset + qubit_index) as u32);
copy_swap_circuit.swap(left, right)?;
}
}
Ok(copy_swap_circuit)
}
pub(crate) fn expand_hamiltonian(
hamiltonian: &Hamiltonian,
n: usize,
) -> Result<Hamiltonian, ErrorMitigationError> {
if hamiltonian.terms.is_empty() {
return Ok(Hamiltonian::new(hamiltonian.num_qubits + n));
}
let expanded_terms = hamiltonian
.terms
.iter()
.map(|(term, coeff)| {
let mut expanded_term = PauliString::new(hamiltonian.num_qubits + n);
expanded_term.phase = term.phase;
for qubit in 0..hamiltonian.num_qubits {
let pauli = match (term.x[qubit], term.z[qubit]) {
(false, false) => Pauli::I,
(true, false) => Pauli::X,
(false, true) => Pauli::Z,
(true, true) => Pauli::Y,
};
expanded_term.set_pauli(qubit, pauli);
}
for qubit in hamiltonian.num_qubits..(hamiltonian.num_qubits + n) {
expanded_term.set_pauli(qubit, Pauli::Z);
}
(expanded_term, *coeff)
})
.collect();
Ok(Hamiltonian::from_list(expanded_terms)?)
}
pub fn run_denominator_circuit(
&self,
shots: usize,
estimator: &Estimator<'_>,
) -> Result<(f64, f64), CircuitError> {
let denominator_circuit = self.build_copy_swap_circuit()?;
Ok(estimator(&denominator_circuit, None, Some(shots)))
}
pub fn run_numerator_circuit(
&self,
hamiltonian: &Hamiltonian,
shots: usize,
estimator: &Estimator<'_>,
) -> Result<(f64, f64), ErrorMitigationError> {
let numerator_circuit = self.build_copy_swap_circuit()?;
let extra_qubits = (self.copies - 1) * hamiltonian.num_qubits;
let expanded_hamiltonian = Self::expand_hamiltonian(hamiltonian, extra_qubits)?;
if expanded_hamiltonian.num_qubits != numerator_circuit.width() {
return Err(CircuitError::QubitCountMismatch {
expected: numerator_circuit.width(),
actual: expanded_hamiltonian.num_qubits,
}
.into());
}
Ok(estimator(
&numerator_circuit,
Some(&expanded_hamiltonian),
Some(shots),
))
}
pub fn run_vd(
&self,
hamiltonian: &Hamiltonian,
shots_numerator: usize,
shots_denominator: usize,
estimator: &Estimator<'_>,
) -> Result<(f64, f64), ErrorMitigationError> {
if hamiltonian.num_qubits != self.circuit.width() {
return Err(ErrorMitigationError::HamiltonianQubitCountMismatch {
expected: self.circuit.width(),
actual: hamiltonian.num_qubits,
});
}
let (mu_numerator, var_numerator) =
self.run_numerator_circuit(hamiltonian, shots_numerator, estimator)?;
let (mu_denominator, var_denominator) =
self.run_denominator_circuit(shots_denominator, estimator)?;
let (mu_vd, var_vd) = Self::mitigate_from_statistics(
mu_numerator,
var_numerator,
mu_denominator,
var_denominator,
)?;
Ok((mu_vd, var_vd))
}
pub(crate) fn mitigate_from_statistics(
mu_numerator: f64,
var_numerator: f64,
mu_denominator: f64,
var_denominator: f64,
) -> Result<(f64, f64), ErrorMitigationError> {
if mu_denominator == 0.0 {
return Err(ErrorMitigationError::ZeroDenominatorMean);
}
let mu_vd = mu_numerator / mu_denominator;
let var_vd = var_numerator / mu_denominator.powi(2)
+ mu_numerator.powi(2) * var_denominator / mu_denominator.powi(4);
Ok((mu_vd, var_vd))
}
fn append_circuit_with_offset(
target_circuit: &mut Circuit,
source_circuit: &Circuit,
qubit_offset: usize,
) -> Result<(), CircuitError> {
let source_qubits = source_circuit.qubits();
let qubit_positions: HashMap<_, _> = source_qubits
.iter()
.enumerate()
.map(|(position, qubit)| (*qubit, position))
.collect();
for op in source_circuit.operations() {
let mapped_qubits: Vec<Qubit> = op
.qubits
.iter()
.map(|qubit| {
let position = qubit_positions[qubit];
Qubit::new((qubit_offset + position) as u32)
})
.collect();
let mapped_params: Vec<ParameterValue> = op
.params
.iter()
.map(|param| match param {
CircuitParam::Fixed(value) => ParameterValue::Fixed(*value),
CircuitParam::Index(index) => {
source_circuit.parameters()[*index as usize].clone().into()
}
})
.collect();
target_circuit.append(
op.instruction.clone(),
mapped_qubits,
mapped_params,
op.label.as_deref(),
)?;
}
Ok(())
}
}
#[cfg(test)]
#[path = "./virtual_distillation_test.rs"]
mod virtual_distillation_test;