use crate::circuit::{Circuit, Instruction};
use crate::qis::Hamiltonian;
use super::{
ErrorMitigationError, Estimator, ExtrapolateMethod, VirtualDistillation, ZNEMitigation,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ZneConfig {
pub fold_levels: Vec<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VirtualDistillationConfig {
pub copies: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MitigationMethod {
Zne(ZneConfig),
VirtualDistillation(VirtualDistillationConfig),
}
#[derive(Debug, Clone)]
pub enum RunArgs {
Zne {
gate_set: Option<Vec<Instruction>>,
shots: Option<usize>,
},
VirtualDistillation {
shots_numerator: usize,
shots_denominator: usize,
},
}
impl PartialEq for RunArgs {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::Zne {
gate_set: lhs_gate_set,
shots: lhs_shots,
},
Self::Zne {
gate_set: rhs_gate_set,
shots: rhs_shots,
},
) => {
lhs_shots == rhs_shots
&& match (lhs_gate_set, rhs_gate_set) {
(None, None) => true,
(Some(lhs), Some(rhs)) => {
lhs.len() == rhs.len()
&& lhs
.iter()
.zip(rhs.iter())
.all(|(lhs, rhs)| lhs.name() == rhs.name())
}
_ => false,
}
}
(
Self::VirtualDistillation {
shots_numerator: lhs_numerator,
shots_denominator: lhs_denominator,
},
Self::VirtualDistillation {
shots_numerator: rhs_numerator,
shots_denominator: rhs_denominator,
},
) => lhs_numerator == rhs_numerator && lhs_denominator == rhs_denominator,
_ => false,
}
}
}
impl Eq for RunArgs {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessArgs {
Zne {
method: ExtrapolateMethod,
degree: Option<usize>,
},
VirtualDistillation,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MitigatedResult {
pub expectation: f64,
pub variance: Option<f64>,
}
#[derive(Debug, Clone)]
enum ErrorMitigationState {
Initialized,
RunCompleted(Box<RunRecord>),
}
#[derive(Debug, Clone)]
enum RunRecord {
Zne(ZneRunRecord),
VirtualDistillation(Box<VirtualDistillationRunRecord>),
}
#[derive(Debug, Clone)]
struct ZneRunRecord {
noisy_expectations: Vec<f64>,
noise_factors: Vec<i32>,
}
#[derive(Debug, Clone)]
struct VirtualDistillationRunRecord {
numerator: (f64, f64),
denominator: (f64, f64),
}
#[derive(Debug, Clone)]
pub struct ErrorMitigation {
circuit: Circuit,
method: MitigationMethod,
state: ErrorMitigationState,
}
impl ErrorMitigation {
pub fn new(circuit: Circuit, method: MitigationMethod) -> Result<Self, ErrorMitigationError> {
Self::validate_method(&method)?;
Ok(Self {
circuit,
method,
state: ErrorMitigationState::Initialized,
})
}
pub fn run(
&mut self,
hamiltonian: &Hamiltonian,
run_args: RunArgs,
estimator: &Estimator<'_>,
) -> Result<(), ErrorMitigationError> {
match self.state {
ErrorMitigationState::Initialized => {}
ErrorMitigationState::RunCompleted(_) => return Err(ErrorMitigationError::AlreadyRun),
}
let run_record = match (&self.method, run_args) {
(MitigationMethod::Zne(config), RunArgs::Zne { gate_set, shots }) => {
RunRecord::Zne(self.run_zne(config, hamiltonian, gate_set, shots, estimator)?)
}
(
MitigationMethod::VirtualDistillation(config),
RunArgs::VirtualDistillation {
shots_numerator,
shots_denominator,
},
) => RunRecord::VirtualDistillation(Box::new(self.run_virtual_distillation(
config,
hamiltonian,
shots_numerator,
shots_denominator,
estimator,
)?)),
(MitigationMethod::Zne(_), _) | (MitigationMethod::VirtualDistillation(_), _) => {
return Err(ErrorMitigationError::RunArgsMethodMismatch);
}
};
self.state = ErrorMitigationState::RunCompleted(Box::new(run_record));
Ok(())
}
pub fn get_mitigated(
&self,
process_args: ProcessArgs,
) -> Result<MitigatedResult, ErrorMitigationError> {
let mitigated = match (&self.method, &self.state, process_args) {
(MitigationMethod::Zne(_), ErrorMitigationState::Initialized, _) => {
return Err(ErrorMitigationError::RunRequiredBeforeMitigation);
}
(MitigationMethod::VirtualDistillation(_), ErrorMitigationState::Initialized, _) => {
return Err(ErrorMitigationError::RunRequiredBeforeMitigation);
}
(
MitigationMethod::Zne(config),
ErrorMitigationState::RunCompleted(record),
ProcessArgs::Zne { method, degree },
) => match record.as_ref() {
RunRecord::Zne(record) => self.get_zne_mitigated(config, record, method, degree)?,
RunRecord::VirtualDistillation(_) => {
return Err(ErrorMitigationError::ProcessArgsMethodMismatch);
}
},
(
MitigationMethod::VirtualDistillation(_),
ErrorMitigationState::RunCompleted(record),
ProcessArgs::VirtualDistillation,
) => match record.as_ref() {
RunRecord::VirtualDistillation(record) => {
self.get_virtual_distillation_mitigated(record.as_ref())?
}
RunRecord::Zne(_) => {
return Err(ErrorMitigationError::ProcessArgsMethodMismatch);
}
},
(MitigationMethod::Zne(_), ErrorMitigationState::RunCompleted(_), _) => {
return Err(ErrorMitigationError::ProcessArgsMethodMismatch);
}
(
MitigationMethod::VirtualDistillation(_),
ErrorMitigationState::RunCompleted(_),
_,
) => {
return Err(ErrorMitigationError::ProcessArgsMethodMismatch);
}
};
Ok(mitigated)
}
fn validate_method(method: &MitigationMethod) -> Result<(), ErrorMitigationError> {
match method {
MitigationMethod::Zne(config) => {
for &level in &config.fold_levels {
if level < 0 {
return Err(ErrorMitigationError::InvalidFoldLevel(level));
}
}
Ok(())
}
MitigationMethod::VirtualDistillation(config) => {
if config.copies < 2 {
return Err(ErrorMitigationError::InvalidCopies(config.copies));
}
Ok(())
}
}
}
fn run_zne(
&self,
config: &ZneConfig,
hamiltonian: &Hamiltonian,
gate_set: Option<Vec<Instruction>>,
shots: Option<usize>,
estimator: &Estimator<'_>,
) -> Result<ZneRunRecord, ErrorMitigationError> {
let zne = ZNEMitigation::new(self.circuit.clone(), config.fold_levels.clone());
if hamiltonian.num_qubits != self.circuit.width() {
return Err(ErrorMitigationError::HamiltonianQubitCountMismatch {
expected: self.circuit.width(),
actual: hamiltonian.num_qubits,
});
}
let folded_circuits = zne.fold_circuits(gate_set.as_deref())?;
let noisy_expectations = folded_circuits
.iter()
.map(|circuit| estimator(circuit, Some(hamiltonian), shots).0)
.collect();
Ok(ZneRunRecord {
noisy_expectations,
noise_factors: zne.noise_factors().to_vec(),
})
}
fn run_virtual_distillation(
&self,
config: &VirtualDistillationConfig,
hamiltonian: &Hamiltonian,
shots_numerator: usize,
shots_denominator: usize,
estimator: &Estimator<'_>,
) -> Result<VirtualDistillationRunRecord, ErrorMitigationError> {
let vd = VirtualDistillation::new(self.circuit.clone(), config.copies)?;
if hamiltonian.num_qubits != self.circuit.width() {
return Err(ErrorMitigationError::HamiltonianQubitCountMismatch {
expected: self.circuit.width(),
actual: hamiltonian.num_qubits,
});
}
let numerator = vd.run_numerator_circuit(hamiltonian, shots_numerator, estimator)?;
let denominator = vd.run_denominator_circuit(shots_denominator, estimator)?;
Ok(VirtualDistillationRunRecord {
numerator,
denominator,
})
}
fn get_zne_mitigated(
&self,
config: &ZneConfig,
record: &ZneRunRecord,
method: ExtrapolateMethod,
degree: Option<usize>,
) -> Result<MitigatedResult, ErrorMitigationError> {
let zne = ZNEMitigation::new(self.circuit.clone(), config.fold_levels.clone());
debug_assert_eq!(record.noise_factors, zne.noise_factors().to_vec());
debug_assert_eq!(record.noise_factors.len(), record.noisy_expectations.len());
let degree = match method {
ExtrapolateMethod::Polynomial => {
degree.unwrap_or_else(|| record.noisy_expectations.len().saturating_sub(1).min(1))
}
ExtrapolateMethod::Exponential => degree.unwrap_or(0),
};
let expectation = zne.extrapolate(&record.noisy_expectations, method, degree)?;
Ok(MitigatedResult {
expectation,
variance: None,
})
}
fn get_virtual_distillation_mitigated(
&self,
record: &VirtualDistillationRunRecord,
) -> Result<MitigatedResult, ErrorMitigationError> {
let (expectation, variance) = VirtualDistillation::mitigate_from_statistics(
record.numerator.0,
record.numerator.1,
record.denominator.0,
record.denominator.1,
)?;
Ok(MitigatedResult {
expectation,
variance: Some(variance),
})
}
}