use std::fmt;
use std::ops::{Add, AddAssign, Mul, MulAssign};
use std::str::FromStr;
use super::{BitTable, Error, Result};
use crate::batch_sampler::{BatchSampler, Counts};
use crate::circuit;
use crate::clifford::ir;
use crate::dem::Dem;
use crate::dem_program::{DemProgram, Stats};
use std::sync::OnceLock;
use crate::m2d::M2d;
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Circuit {
pub(crate) inner: circuit::Circuit,
counts: Counts,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Target {
Qubit(u32),
Inverted(u32),
Rec(u32),
Sweep(u32),
Pauli {
pauli: Pauli,
qubit: u32,
inverted: bool,
},
Combiner,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Pauli {
X,
Y,
Z,
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Target::Qubit(q) => write!(f, "{q}"),
Target::Inverted(q) => write!(f, "!{q}"),
Target::Rec(k) => write!(f, "rec[-{k}]"),
Target::Sweep(k) => write!(f, "sweep[{k}]"),
Target::Pauli { pauli, qubit, inverted } => {
let p = match pauli {
Pauli::X => 'X',
Pauli::Y => 'Y',
Pauli::Z => 'Z',
};
write!(f, "{}{p}{qubit}", if inverted { "!" } else { "" })
}
Target::Combiner => f.write_str("*"),
}
}
}
impl Circuit {
pub fn new() -> Circuit {
Circuit::default()
}
pub fn parse(text: &str) -> Result<Circuit> {
let engine = circuit::Circuit::parse(text)?;
match ir::Circuit::parse(text) {
Ok(fused) if !same_shape(&engine.instrs, &fused.items) => Circuit::from_engine(circuit::Circuit::parse(&fused.exact_text())?),
_ => Circuit::from_engine(engine),
}
}
#[cfg_attr(not(feature = "python"), allow(dead_code))]
pub(crate) fn parse_unfused(text: &str) -> Result<Circuit> {
Circuit::from_engine(circuit::Circuit::parse(text)?)
}
pub fn explain_errors(&self, filter: Option<&DetectorErrorModel>, reduce: bool) -> Result<Vec<ExplainedError>> {
let terms = match filter {
Some(f) => Some(
f.flat()?
.mechanisms
.iter()
.map(|m| {
let mut t: Vec<u64> = m.detectors.iter().map(|&d| u64::from(d)).collect();
t.extend((0..64).filter(|k| m.observables >> k & 1 == 1).map(|k| crate::dem_program::OBS | k));
t
})
.collect::<Vec<_>>(),
),
None => None,
};
self.explained(terms.as_deref(), reduce)
}
fn explained(&self, terms: Option<&[Vec<u64>]>, reduce: bool) -> Result<Vec<ExplainedError>> {
let coords = std::sync::Arc::new(crate::explain::Coords::of(&self.inner)?);
let out = crate::explain::explain(&self.inner, terms, reduce)?;
Ok(out.into_iter().map(|inner| ExplainedError { inner, coords: coords.clone() }).collect())
}
pub fn shortest_graphlike_error(&self, ignore_ungraphlike: bool, canonicalize: bool) -> Result<Vec<ExplainedError>> {
let dem = self.detector_error_model(&DemOptions::new().decompose_errors(true).approximate_disjoint_errors(Some(1.0)).ignore_decomposition_failures(true))?;
let faults = crate::distance::shortest_graphlike(dem.flat()?, ignore_ungraphlike)?;
self.explained(Some(&crate::distance::as_terms(&faults)), canonicalize)
}
pub fn search_for_undetectable_logical_errors(&self, max_symptoms: usize, max_degree: usize, no_increase: bool, canonicalize: bool) -> Result<Vec<ExplainedError>> {
let dem = self.detector_error_model(&DemOptions::new().approximate_disjoint_errors(Some(1.0)))?;
let faults = crate::distance::search_undetectable(dem.flat()?, max_symptoms, max_degree, no_increase)?;
self.explained(Some(&crate::distance::as_terms(&faults)), canonicalize)
}
pub fn generated(task: &str, distance: u32, rounds: u64, noise: &GeneratedNoise) -> Result<Circuit> {
Circuit::parse(&crate::generated::generate(task, distance, rounds, &noise.inner)?)
}
pub fn generated_with_colors(task: &str, distance: u32, rounds: u64, noise: &GeneratedNoise) -> Result<Circuit> {
Circuit::parse(&crate::generated::generate_annotated(task, distance, rounds, &noise.inner)?)
}
pub(crate) fn from_engine(inner: circuit::Circuit) -> Result<Circuit> {
let counts = Counts::of(&inner.instrs)?;
fits(&counts)?;
Ok(Circuit { inner, counts })
}
pub fn append(&mut self, name: &str, targets: &[Target], args: &[f64]) -> Result<()> {
self.append_tagged(name, targets, args, "")
}
pub fn append_tagged(&mut self, name: &str, targets: &[Target], args: &[f64], tag: &str) -> Result<()> {
let targets: Vec<String> = targets.iter().map(Target::to_string).collect();
let piece = Circuit::from_engine(circuit::Circuit::instruction(name, tag, args, &targets)?)?;
self.append_circuit(&piece)
}
pub fn append_text(&mut self, text: &str) -> Result<()> {
let piece = Circuit::parse(text)?;
self.append_circuit(&piece)
}
pub fn append_circuit(&mut self, other: &Circuit) -> Result<()> {
let counts = self.counts.then(other.counts)?;
fits(&counts)?;
let mut rest = other.inner.instrs.iter();
if let (Some(last), Some(first)) = (self.inner.instrs.last(), other.inner.instrs.first()) {
if let Some(both) = fused(last, first) {
*self.inner.instrs.last_mut().expect("nonempty") = both;
rest.next();
}
}
self.inner.instrs.extend(rest.cloned());
self.counts = counts;
Ok(())
}
pub fn repeated(&self, count: u64) -> Result<Circuit> {
let counts = if count == 0 { Counts::default() } else { self.counts.times(count)? };
fits(&counts)?;
Ok(Circuit { inner: self.inner.repeated(count), counts })
}
pub fn num_qubits(&self) -> usize {
self.counts.qubits
}
pub fn num_measurements(&self) -> usize {
self.counts.measurements as usize
}
pub fn num_detectors(&self) -> usize {
self.counts.detectors as usize
}
pub fn num_observables(&self) -> usize {
self.counts.observables
}
pub fn num_sweep_bits(&self) -> usize {
self.counts.sweep_bits
}
pub fn detector_error_model(&self, options: &DemOptions) -> Result<DetectorErrorModel> {
let o = options;
let build = if o.deltakit_leakage { crate::dem_build::build_deltakit } else { crate::dem_build::build_full };
let program = build(&self.inner, o.decompose_errors, o.approximate_disjoint_errors, !o.flatten_loops, o.ignore_decomposition_failures, o.allow_gauge_detectors, o.block_decomposition_from_introducing_remnant_edges)?;
Ok(DetectorErrorModel::from_program(program))
}
pub fn detector_sampler(&self, seed: u64) -> Result<DetectorSampler> {
Ok(DetectorSampler { sampler: BatchSampler::new(&self.inner)?, seed, next: 0 })
}
pub fn coherent_sampler(&self, seed: u64, options: CoherentOptions) -> Result<CoherentSampler> {
let inner = crate::coherent::sampler::CoherentSampler::new(&self.inner, options.inner)?;
Ok(CoherentSampler { inner, seed, next: 0 })
}
pub fn exact_sampler(&self, seed: u64) -> Result<ExactSampler> {
Ok(ExactSampler { program: crate::statevec::Program::new(&self.inner)?, seed, next: 0 })
}
pub fn joint_sampler(&self, seed: u64, options: CoherentOptions) -> Result<JointSampler> {
let inner = crate::coherent::joint::JointSampler::new(&self.inner, options.inner)?;
Ok(JointSampler { inner, seed, next: 0 })
}
pub fn stabilizer_rank_sampler(&self, seed: u64) -> Result<StabilizerRankSampler> {
Ok(StabilizerRankSampler { program: crate::stabrank::Program::new(&self.inner)?, seed, next: 0, peak: 0 })
}
pub fn twirled(&self, merge: bool) -> Result<Circuit> {
Circuit::from_engine(crate::coherent::twirled(&self.inner, merge)?)
}
pub fn diagram(&self, kind: DiagramKind) -> Result<String> {
self.diagram_with_filter(kind, &[crate::clifford::detslice::CoordFilter::default()])
}
pub fn diagram_with_filter(&self, kind: DiagramKind, filter: &[crate::clifford::detslice::CoordFilter]) -> Result<String> {
use crate::diagram as d;
let ir = || crate::clifford::ir::Circuit::parse(&self.inner.to_stim()).map_err(Error::new);
let keep = || -> Result<std::collections::BTreeSet<String>> {
let c = ir()?;
let n = crate::clifford::transform::count_detectors(&c);
let coords = crate::clifford::transform::detector_coordinates(&c, &(0..n).collect()).map_err(Error::new)?;
let mut out = std::collections::BTreeSet::new();
for (k, v) in &coords {
if filter.iter().any(|f| f.matches(v, *k)) {
out.insert(format!("D{k}"));
}
}
for f in filter {
if let Some(t) = f.target.filter(|&t| crate::clifford::rev_tracker::is_observable(t)) {
out.insert(crate::clifford::rev_tracker::dem_target_str(t));
}
}
Ok(out)
};
Ok(match kind {
DiagramKind::TimelineText => d::timeline_text(&self.inner)?,
DiagramKind::TimelineSvg => d::timeline_svg(&self.inner)?,
DiagramKind::DetectorSliceText { tick } => crate::clifford::detslice::SliceSet::from_circuit_ticks(&ir()?, tick, 1, filter).map_err(Error::new)?.text(),
DiagramKind::DetectorSliceSvg { tick } => d::detslice_svg_filtered(&self.inner, tick, Some(&keep()?))?,
DiagramKind::DetectorSlicesSvg { ticks, rows } => d::slices_svg_filtered(&self.inner, ticks.0..ticks.1, rows, true, false, Some(&keep()?))?,
DiagramKind::TimeSliceSvg { ticks, rows } => d::slices_svg(&self.inner, ticks.0..ticks.1, rows, false, true)?,
DiagramKind::DetectorSliceWithOpsSvg { ticks, rows } => d::slices_svg_filtered(&self.inner, ticks.0..ticks.1, rows, true, true, Some(&keep()?))?,
DiagramKind::MatchGraphSvg => {
let dem = self.detector_error_model(&DemOptions::new().decompose_errors(true).approximate_disjoint_errors(Some(1.0)))?;
dem.matchgraph_svg()?
}
})
}
pub fn measurement_converter(&self) -> Result<MeasurementConverter> {
Ok(MeasurementConverter { m2d: M2d::new(&self.inner)? })
}
}
fn fits(c: &Counts) -> Result<()> {
if usize::try_from(c.measurements).is_err() || usize::try_from(c.detectors).is_err() {
return Err(Error::new("too many measurements or detectors for this machine"));
}
Ok(())
}
impl Add<&Circuit> for &Circuit {
type Output = Circuit;
fn add(self, rhs: &Circuit) -> Circuit {
let mut out = self.clone();
out += rhs;
out
}
}
impl Add<&Circuit> for Circuit {
type Output = Circuit;
fn add(mut self, rhs: &Circuit) -> Circuit {
self += rhs;
self
}
}
impl AddAssign<&Circuit> for Circuit {
fn add_assign(&mut self, rhs: &Circuit) {
if let Err(e) = self.append_circuit(rhs) {
panic!("{e}");
}
}
}
impl Mul<u64> for &Circuit {
type Output = Circuit;
fn mul(self, count: u64) -> Circuit {
self.repeated(count).unwrap_or_else(|e| panic!("{e}"))
}
}
impl Mul<u64> for Circuit {
type Output = Circuit;
fn mul(self, count: u64) -> Circuit {
&self * count
}
}
impl MulAssign<u64> for Circuit {
fn mul_assign(&mut self, count: u64) {
*self = &*self * count;
}
}
impl FromStr for Circuit {
type Err = Error;
fn from_str(text: &str) -> Result<Circuit> {
Circuit::parse(text)
}
}
fn same_shape(instrs: &[circuit::Instr], items: &[ir::Item]) -> bool {
instrs.len() == items.len()
&& instrs.iter().zip(items).all(|(i, it)| match (i, it) {
(circuit::Instr::Repeat { body, .. }, ir::Item::Repeat { body: b, .. }) => same_shape(body, &b.items),
(circuit::Instr::Repeat { .. }, _) | (_, ir::Item::Repeat { .. }) => false,
_ => true,
})
}
fn fused(a: &circuit::Instr, b: &circuit::Instr) -> Option<circuit::Instr> {
if matches!(a, circuit::Instr::Repeat { .. }) || matches!(b, circuit::Instr::Repeat { .. }) {
return None;
}
let (la, lb) = (circuit::instr_line(a), circuit::instr_line(b));
let head = |l: &str| l.split(|c: char| c == ' ' || c == '(' || c == '[').next().unwrap_or("").to_string();
if head(&la) != head(&lb) {
return None;
}
let one = |l: &str| match ir::Circuit::parse(l).ok()?.items.pop()? {
ir::Item::Op(op) => Some(op),
_ => None,
};
let (mut x, y) = (one(&la)?, one(&lb)?);
if !x.fuse(&y) {
return None;
}
let mut c = circuit::Circuit::parse(&x.exact_line()).ok()?;
(c.instrs.len() == 1).then(|| c.instrs.pop().expect("one"))
}
impl fmt::Display for Circuit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.inner.to_stim())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DiagramKind {
TimelineText,
TimelineSvg,
DetectorSliceText {
tick: u64,
},
DetectorSliceSvg {
tick: u64,
},
MatchGraphSvg,
DetectorSlicesSvg {
ticks: (u64, u64),
rows: Option<u32>,
},
TimeSliceSvg {
ticks: (u64, u64),
rows: Option<u32>,
},
DetectorSliceWithOpsSvg {
ticks: (u64, u64),
rows: Option<u32>,
},
}
#[derive(Clone, Debug)]
pub struct ExplainedError {
inner: crate::explain::Explained,
coords: std::sync::Arc<crate::explain::Coords>,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct TargetWithCoords {
pub target: String,
pub coords: Vec<f64>,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct CircuitErrorLocation {
pub tick_offset: u64,
pub flipped_pauli_product: Vec<TargetWithCoords>,
pub flipped_measurement: Option<(u64, Vec<TargetWithCoords>)>,
pub gate: String,
pub tag: String,
pub args: Vec<f64>,
pub target_range: (u32, u32),
pub targets_in_range: Vec<TargetWithCoords>,
pub stack_frames: Vec<(u64, u64, u64)>,
text: String,
instruction: String,
}
impl CircuitErrorLocation {
pub fn instruction_text(&self) -> &str {
&self.instruction
}
}
impl fmt::Display for TargetWithCoords {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.coords.is_empty() {
f.write_str(&self.target)
} else {
write!(f, "{}[coords {}]", self.target, crate::explain::Coords::at(&self.coords))
}
}
}
impl fmt::Display for CircuitErrorLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.text)
}
}
impl ExplainedError {
pub fn dem_error_terms(&self) -> Vec<String> {
self.terms_with_coords().into_iter().map(|t| t.target).collect()
}
pub fn terms_with_coords(&self) -> Vec<TargetWithCoords> {
self.inner
.terms
.iter()
.map(|&t| {
if t & crate::dem_program::OBS != 0 {
TargetWithCoords { target: format!("L{}", t & !crate::dem_program::OBS), coords: Vec::new() }
} else {
TargetWithCoords { target: format!("D{t}"), coords: self.coords.of_detector(t) }
}
})
.collect()
}
pub fn circuit_error_locations(&self) -> Vec<CircuitErrorLocation> {
let c = &self.coords;
let paulis = |ps: &[(u32, u8)]| -> Vec<TargetWithCoords> {
ps.iter()
.map(|&(q, p)| TargetWithCoords {
target: format!("{}{}{q}", if p & 16 != 0 { "!" } else { "" }, ["I", "X", "Z", "Y"][(p & 3) as usize]),
coords: c.of_qubit(q),
})
.collect()
};
self.inner
.locations
.iter()
.map(|l| CircuitErrorLocation {
tick_offset: l.tick_offset,
flipped_pauli_product: paulis(&l.pauli),
flipped_measurement: l.measurement.as_ref().map(|(i, obs)| (*i, paulis(obs))),
gate: l.gate.clone(),
tag: l.tag.clone(),
args: l.args.clone(),
target_range: l.range,
targets_in_range: l
.targets
.iter()
.map(|t| {
let q = t.trim_start_matches('!').trim_start_matches(['X', 'Y', 'Z']).parse::<u32>().ok();
TargetWithCoords { target: t.clone(), coords: q.map(|q| c.of_qubit(q)).unwrap_or_default() }
})
.collect(),
stack_frames: l.stack.clone(),
text: l.to_stim(c),
instruction: l.instruction_text(c),
})
.collect()
}
}
impl fmt::Display for ExplainedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.inner.to_stim(&self.coords))
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GeneratedNoise {
inner: crate::generated::Noise,
}
impl GeneratedNoise {
pub fn new() -> GeneratedNoise {
GeneratedNoise::default()
}
pub fn after_clifford_depolarization(mut self, p: f64) -> GeneratedNoise {
self.inner.after_clifford_depolarization = p;
self
}
pub fn before_round_data_depolarization(mut self, p: f64) -> GeneratedNoise {
self.inner.before_round_data_depolarization = p;
self
}
pub fn before_measure_flip_probability(mut self, p: f64) -> GeneratedNoise {
self.inner.before_measure_flip_probability = p;
self
}
pub fn after_reset_flip_probability(mut self, p: f64) -> GeneratedNoise {
self.inner.after_reset_flip_probability = p;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct DemOptions {
decompose_errors: bool,
approximate_disjoint_errors: Option<f64>,
flatten_loops: bool,
ignore_decomposition_failures: bool,
allow_gauge_detectors: bool,
block_decomposition_from_introducing_remnant_edges: bool,
deltakit_leakage: bool,
}
impl DemOptions {
pub fn deltakit_leakage(mut self, yes: bool) -> DemOptions {
self.deltakit_leakage = yes;
self
}
pub fn new() -> DemOptions {
DemOptions::default()
}
pub fn allow_gauge_detectors(mut self, yes: bool) -> DemOptions {
self.allow_gauge_detectors = yes;
self
}
pub fn block_decomposition_from_introducing_remnant_edges(mut self, yes: bool) -> DemOptions {
self.block_decomposition_from_introducing_remnant_edges = yes;
self
}
pub fn decompose_errors(mut self, yes: bool) -> DemOptions {
self.decompose_errors = yes;
self
}
pub fn approximate_disjoint_errors(mut self, threshold: Option<f64>) -> DemOptions {
self.approximate_disjoint_errors = threshold;
self
}
pub fn ignore_decomposition_failures(mut self, yes: bool) -> DemOptions {
self.ignore_decomposition_failures = yes;
self
}
pub fn flatten_loops(mut self, yes: bool) -> DemOptions {
self.flatten_loops = yes;
self
}
}
#[derive(Clone, Debug)]
pub struct DetectorErrorModel {
program: DemProgram,
stats: OnceLock<Stats>,
flat: OnceLock<std::result::Result<Dem, String>>,
}
impl DetectorErrorModel {
pub(crate) fn from_program(program: DemProgram) -> DetectorErrorModel {
DetectorErrorModel { stats: OnceLock::new(), program, flat: OnceLock::new() }
}
fn stats(&self) -> &Stats {
self.stats.get_or_init(|| self.program.stats())
}
#[cfg_attr(not(feature = "python"), allow(dead_code))]
pub(crate) fn append_text(&mut self, text: &str) -> Result<()> {
let piece = DemProgram::parse(text)?;
self.program.instrs.extend(piece.instrs);
self.stats = OnceLock::new();
self.flat = OnceLock::new();
Ok(())
}
pub(crate) fn program(&self) -> &DemProgram {
&self.program
}
pub(crate) fn flat(&self) -> Result<&Dem> {
self.flat.get_or_init(|| self.program.to_dem()).as_ref().map_err(|e| Error::new(e.clone()))
}
pub fn parse(text: &str) -> Result<DetectorErrorModel> {
Ok(DetectorErrorModel::from_program(DemProgram::parse(text)?))
}
pub fn num_detectors(&self) -> usize {
usize::try_from(self.stats().num_detectors).unwrap_or(usize::MAX)
}
pub fn num_observables(&self) -> usize {
self.stats().num_observables
}
pub fn num_errors(&self) -> usize {
usize::try_from(self.stats().num_errors).unwrap_or(usize::MAX)
}
pub fn shortest_graphlike_error(&self, ignore_ungraphlike: bool) -> Result<DetectorErrorModel> {
let faults = crate::distance::shortest_graphlike(self.flat()?, ignore_ungraphlike)?;
DetectorErrorModel::parse(&crate::distance::to_dem_text(&faults))
}
pub fn search_for_undetectable_logical_errors(&self, max_symptoms: usize, max_degree: usize, no_increase: bool) -> Result<DetectorErrorModel> {
let faults = crate::distance::search_undetectable(self.flat()?, max_symptoms, max_degree, no_increase)?;
DetectorErrorModel::parse(&crate::distance::to_dem_text(&faults))
}
pub fn sampler(&self, seed: u64) -> Result<DemSampler> {
Ok(DemSampler { inner: crate::dem_sampler::DemSampler::new(self.flat()?), seed, next: 0 })
}
pub fn matchgraph_svg(&self) -> Result<String> {
Ok(crate::diagram::matchgraph_svg(self.flat()?)?)
}
pub fn flattened(&self) -> Result<DetectorErrorModel> {
Ok(DetectorErrorModel::from_program(self.program.flattened()?))
}
}
impl FromStr for DetectorErrorModel {
type Err = Error;
fn from_str(text: &str) -> Result<DetectorErrorModel> {
DetectorErrorModel::parse(text)
}
}
impl fmt::Display for DetectorErrorModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.program.to_stim())
}
}
impl DetectorErrorModel {
#[cfg_attr(not(feature = "python"), allow(dead_code))]
pub(crate) fn exact_text(&self) -> String {
self.program.to_exact_text()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Samples {
pub detectors: BitTable,
pub observables: BitTable,
}
pub struct DetectorSampler {
sampler: BatchSampler,
seed: u64,
next: u64,
}
impl DetectorSampler {
pub fn num_detectors(&self) -> usize {
self.sampler.num_detectors
}
pub fn num_observables(&self) -> usize {
self.sampler.num_observables
}
pub fn sample(&mut self, shots: usize, threads: usize) -> Samples {
let (d, o) = self.sampler.sample_seeded(self.seed, self.next, shots, threads);
self.next += shots.div_ceil(64) as u64;
Samples {
detectors: BitTable::from_packed(shots, self.num_detectors(), d).expect("the sampler writes whole rows"),
observables: BitTable::from_packed(shots, self.num_observables(), o).expect("the sampler writes whole rows"),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CoherentOptions {
inner: crate::coherent::kernel::CoherentOptions,
}
impl Default for CoherentOptions {
fn default() -> Self {
CoherentOptions::new()
}
}
impl CoherentOptions {
pub fn new() -> CoherentOptions {
CoherentOptions { inner: crate::coherent::kernel::CoherentOptions::default() }
}
pub fn order(mut self, order: usize) -> Self {
self.inner.order = order.max(1);
self
}
pub fn hops(mut self, hops: usize) -> Self {
self.inner.hops = hops;
self
}
}
pub struct CoherentSampler {
inner: crate::coherent::sampler::CoherentSampler,
seed: u64,
next: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct WeightedSamples {
pub detectors: BitTable,
pub observables: BitTable,
pub weights: Vec<f64>,
}
impl CoherentSampler {
pub fn num_detectors(&self) -> usize {
self.inner.program.num_detectors()
}
pub fn num_observables(&self) -> usize {
self.inner.program.num_observables()
}
pub fn num_locations(&self) -> usize {
self.inner.program.locations.len()
}
pub fn num_generators(&self) -> usize {
self.inner.kernel.generators.len()
}
pub fn num_nonlocal_generators(&self) -> usize {
self.inner.kernel.num_nonlocal
}
pub fn kernel_complete(&self) -> bool {
self.inner.kernel.complete
}
pub fn exact_weights(&self) -> bool {
self.inner.kernel.is_whole()
}
pub fn sample(&mut self, shots: usize, threads: usize) -> WeightedSamples {
let b = self.inner.sample_seeded(self.seed, self.next, shots, threads);
self.next += shots as u64;
WeightedSamples {
detectors: BitTable::from_packed(shots, self.num_detectors(), b.detectors).expect("the sampler writes whole rows"),
observables: BitTable::from_packed(shots, self.num_observables(), b.observables).expect("the sampler writes whole rows"),
weights: b.weights,
}
}
}
pub struct ExactSampler {
program: crate::statevec::Program,
seed: u64,
next: u64,
}
impl ExactSampler {
pub fn num_detectors(&self) -> usize {
self.program.num_detectors()
}
pub fn num_observables(&self) -> usize {
self.program.num_observables()
}
pub fn num_qubits(&self) -> usize {
self.program.num_qubits()
}
pub fn sample(&mut self, shots: usize, threads: usize) -> Samples {
let (d, o) = crate::statevec::sample_seeded(&self.program, self.seed, self.next, shots, threads);
self.next += shots as u64;
Samples {
detectors: BitTable::from_packed(shots, self.num_detectors(), d).expect("the sampler writes whole rows"),
observables: BitTable::from_packed(shots, self.num_observables(), o).expect("the sampler writes whole rows"),
}
}
}
pub struct JointSampler {
inner: crate::coherent::joint::JointSampler,
seed: u64,
next: u64,
}
impl JointSampler {
pub fn num_detectors(&self) -> usize {
self.inner.num_detectors()
}
pub fn num_observables(&self) -> usize {
self.inner.num_observables()
}
pub fn num_locations(&self) -> usize {
self.inner.num_locations()
}
pub fn num_exact(&self) -> usize {
self.inner.num_exact()
}
pub fn num_generators(&self) -> usize {
self.inner.num_generators()
}
pub fn num_nonlocal_generators(&self) -> usize {
self.inner.num_nonlocal_generators()
}
pub fn kernel_complete(&self) -> bool {
self.inner.kernel_complete()
}
pub fn exact_weights(&self) -> bool {
self.inner.is_whole()
}
pub fn sample(&mut self, shots: usize, threads: usize) -> WeightedSamples {
let b = self.inner.sample_seeded(self.seed, self.next, shots, threads);
self.next += shots as u64;
WeightedSamples {
detectors: BitTable::from_packed(shots, self.num_detectors(), b.detectors).expect("the sampler writes whole rows"),
observables: BitTable::from_packed(shots, self.num_observables(), b.observables).expect("the sampler writes whole rows"),
weights: b.weights,
}
}
}
pub struct StabilizerRankSampler {
program: crate::stabrank::Program,
seed: u64,
next: u64,
peak: usize,
}
impl StabilizerRankSampler {
pub fn num_detectors(&self) -> usize {
self.program.num_detectors()
}
pub fn num_observables(&self) -> usize {
self.program.num_observables()
}
pub fn num_measurements(&self) -> usize {
self.program.num_measurements()
}
pub fn num_qubits(&self) -> usize {
self.program.num_qubits()
}
pub fn num_non_clifford(&self) -> usize {
self.program.num_non_clifford()
}
pub fn peak_terms(&self) -> usize {
self.peak
}
pub fn sample(&mut self, shots: usize, threads: usize) -> Samples {
let (d, o, peak) = crate::stabrank::sample_seeded(&self.program, self.seed, self.next, shots, threads);
self.next += shots as u64;
self.peak = self.peak.max(peak);
Samples {
detectors: BitTable::from_packed(shots, self.num_detectors(), d).expect("the sampler writes whole rows"),
observables: BitTable::from_packed(shots, self.num_observables(), o).expect("the sampler writes whole rows"),
}
}
pub fn sample_measurements(&mut self, shots: usize, threads: usize) -> BitTable {
let (m, peak) = crate::stabrank::measurements_seeded(&self.program, self.seed, self.next, shots, threads);
self.next += shots as u64;
self.peak = self.peak.max(peak);
BitTable::from_packed(shots, self.num_measurements(), m).expect("the sampler writes whole rows")
}
pub fn probability_of(&self, record: &[bool], samples: usize) -> Result<f64> {
Ok(self.program.probability_of(record, samples, self.seed)?)
}
}
pub struct DemSampler {
inner: crate::dem_sampler::DemSampler,
seed: u64,
next: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DemSamples {
pub detectors: BitTable,
pub observables: BitTable,
pub errors: BitTable,
}
impl DemSampler {
pub fn sample(&mut self, shots: usize, threads: usize) -> DemSamples {
let (d, o, e) = self.inner.sample_seeded(self.seed, self.next, shots, threads, true);
self.next += shots.div_ceil(64) as u64;
DemSamples {
detectors: BitTable::from_packed(shots, self.inner.num_detectors, d).expect("the sampler writes whole rows"),
observables: BitTable::from_packed(shots, self.inner.num_observables, o).expect("the sampler writes whole rows"),
errors: BitTable::from_packed(shots, self.inner.num_errors(), e).expect("the sampler writes whole rows"),
}
}
}
pub struct MeasurementConverter {
m2d: M2d,
}
impl MeasurementConverter {
pub fn num_measurements(&self) -> usize {
self.m2d.num_measurements
}
pub fn num_sweep_bits(&self) -> usize {
self.m2d.num_sweep_bits
}
pub fn convert(&self, measurements: &BitTable, sweep_bits: Option<&BitTable>) -> Result<Samples> {
let shots = measurements.num_rows();
if measurements.num_bits() != self.num_measurements() {
return Err(Error::new(format!("{} measurement bits per shot, not {}", measurements.num_bits(), self.num_measurements())));
}
let zeros;
let sweeps = match sweep_bits {
Some(s) => {
if s.num_rows() != shots || s.num_bits() != self.num_sweep_bits() {
return Err(Error::new(format!(
"sweep bits are {} shots of {}, not {shots} of {}",
s.num_rows(),
s.num_bits(),
self.num_sweep_bits()
)));
}
s
}
None => {
zeros = BitTable::zeros(shots, self.num_sweep_bits());
&zeros
}
};
let (d, o) = self.m2d.convert_b8(measurements.as_bytes(), sweeps.as_bytes(), shots)?;
Ok(Samples {
detectors: BitTable::from_packed(shots, self.m2d.num_detectors, d)?,
observables: BitTable::from_packed(shots, self.m2d.num_observables, o)?,
})
}
}