use super::Circuit;
use super::fusion::fuse_circuit;
use super::parameter::Parameters;
use super::plan::FusionPlan;
use crate::error::Result;
use crate::sim::unified_pauli::PauliTerm;
use crate::sim::{
BackendKind, ObservableExpectation, PauliObservable, PreparedRoute, RunOutcome, prepared_route,
simulate,
};
pub struct PreparedCircuit {
template: Circuit,
params: Parameters,
kind: BackendKind,
plan: Option<FusionPlan>,
route: Option<PreparedRoute>,
skeleton: Circuit,
bound: Circuit,
fused: Circuit,
fused_off_plan: bool,
}
impl Clone for PreparedCircuit {
fn clone(&self) -> Self {
Self {
template: self.template.clone(),
params: self.params.clone(),
kind: self.kind.clone(),
plan: self.plan.clone(),
route: prepared_route(&self.kind, &self.template),
skeleton: self.skeleton.clone(),
bound: self.bound.clone(),
fused: self.fused.clone(),
fused_off_plan: self.fused_off_plan,
}
}
}
impl PreparedCircuit {
pub fn new(template: Circuit, params: Parameters) -> Result<Self> {
Self::with_backend(template, params, BackendKind::Auto)
}
pub fn with_backend(template: Circuit, params: Parameters, kind: BackendKind) -> Result<Self> {
params.validate(&template)?;
let (skeleton, plan) = FusionPlan::capture(&template);
Ok(Self::assemble(template, params, kind, plan, skeleton))
}
fn assemble(
template: Circuit,
params: Parameters,
kind: BackendKind,
plan: Option<FusionPlan>,
skeleton: Circuit,
) -> Self {
let route = prepared_route(&kind, &template);
let bound = template.clone();
let fused = skeleton.clone();
Self {
template,
params,
kind,
plan,
route,
skeleton,
bound,
fused,
fused_off_plan: false,
}
}
pub fn template(&self) -> &Circuit {
&self.template
}
pub fn parameters(&self) -> &Parameters {
&self.params
}
pub fn reuses_fusion_plan(&self) -> bool {
self.plan.is_some()
}
pub fn bind(&mut self, values: &[f64]) -> Result<&Circuit> {
self.params.check_values(values)?;
self.params.write_angles(&mut self.bound, values);
Ok(&self.bound)
}
pub fn bind_fused(&mut self, values: &[f64]) -> Result<&Circuit> {
self.params.check_values(values)?;
self.params.write_angles(&mut self.bound, values);
if let Some(plan) = &self.plan {
if self.fused_off_plan {
self.fused.clone_from(&self.skeleton);
self.fused_off_plan = false;
}
if plan.replay(&self.bound, &mut self.fused) {
return Ok(&self.fused);
}
self.fused_off_plan = true;
}
self.fused = fuse_circuit(&self.bound, true).into_owned();
Ok(&self.fused)
}
pub fn run(&mut self, values: &[f64], seed: u64) -> Result<RunOutcome> {
let fused = self.bind_for_route(values)?;
let Self {
kind,
route,
bound,
fused: fused_circuit,
..
} = self;
let circuit = if fused { &*fused_circuit } else { &*bound };
match route {
Some(route) => route.run(circuit, seed),
None => simulate(circuit).backend(kind.clone()).seed(seed).run(),
}
}
pub fn expectation_values(
&mut self,
values: &[f64],
observables: &[Vec<PauliTerm>],
seed: u64,
) -> Result<Vec<f64>> {
let fused = self.bind_for_route(values)?;
let Self {
kind,
route,
bound,
fused: fused_circuit,
..
} = self;
let applied = if fused { &*fused_circuit } else { &*bound };
if let Some(result) = route
.as_mut()
.and_then(|route| route.expectation_values(bound, applied, observables, seed))
{
return result;
}
simulate(bound)
.backend(kind.clone())
.seed(seed)
.expectation_values(observables)
}
pub fn observable_expectation(
&mut self,
values: &[f64],
observable: &PauliObservable,
seed: u64,
) -> Result<ObservableExpectation> {
let fused = self.bind_for_route(values)?;
let Self {
kind,
route,
bound,
fused: fused_circuit,
..
} = self;
let applied = if fused { &*fused_circuit } else { &*bound };
if let Some(result) = route
.as_mut()
.and_then(|route| route.observable_expectation(applied, observable, seed))
{
return result;
}
simulate(bound)
.backend(kind.clone())
.seed(seed)
.observable_expectation(observable)
}
pub fn run_many<V: AsRef<[f64]> + Sync>(
&mut self,
bindings: &[V],
seed: u64,
) -> Result<Vec<RunOutcome>> {
self.map_split(bindings, |prepared, values| {
prepared.run(values.as_ref(), seed)
})
}
pub fn expectation_values_many<V: AsRef<[f64]> + Sync>(
&mut self,
bindings: &[V],
observables: &[Vec<PauliTerm>],
seed: u64,
) -> Result<Vec<Vec<f64>>> {
self.map_split(bindings, |prepared, values| {
prepared.expectation_values(values.as_ref(), observables, seed)
})
}
pub fn observable_expectation_many<V: AsRef<[f64]> + Sync>(
&mut self,
bindings: &[V],
observable: &PauliObservable,
seed: u64,
) -> Result<Vec<ObservableExpectation>> {
self.map_split(bindings, |prepared, values| {
prepared.observable_expectation(values.as_ref(), observable, seed)
})
}
fn bind_for_route(&mut self, values: &[f64]) -> Result<bool> {
let fused = self
.route
.as_ref()
.is_some_and(PreparedRoute::supports_fused);
if fused {
self.bind_fused(values)?;
} else {
self.bind(values)?;
}
Ok(fused)
}
pub(crate) fn map_split<I, T, F>(&mut self, items: &[I], eval: F) -> Result<Vec<T>>
where
I: Sync,
T: Send,
F: Fn(&mut Self, &I) -> Result<T> + Sync,
{
#[cfg(feature = "parallel")]
if items.len() > 1
&& crate::sim::runs_split_across_workers(&self.kind, self.template.num_qubits)
{
use rayon::prelude::*;
use std::sync::atomic::{AtomicUsize, Ordering};
let Self {
template,
params,
kind,
plan,
skeleton,
..
} = &*self;
let next = AtomicUsize::new(0);
let claimed: Vec<Vec<(usize, Result<T>)>> = (0..rayon::current_num_threads())
.into_par_iter()
.map(|_| {
let mut worker = None;
let mut claimed = Vec::new();
loop {
let k = next.fetch_add(1, Ordering::Relaxed);
let Some(item) = items.get(k) else {
return claimed;
};
let worker = worker.get_or_insert_with(|| {
Self::assemble(
template.clone(),
params.clone(),
kind.clone(),
plan.clone(),
skeleton.clone(),
)
});
claimed.push((k, eval(worker, item)));
}
})
.collect();
let mut results: Vec<(usize, Result<T>)> = claimed.into_iter().flatten().collect();
results.sort_unstable_by_key(|&(k, _)| k);
return results.into_iter().map(|(_, result)| result).collect();
}
items.iter().map(|item| eval(self, item)).collect()
}
}