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::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> {
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(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)?;
self.inner.instrs.extend(other.inner.instrs.iter().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 program = crate::dem_build::build_with(&self.inner, options.decompose_errors, options.approximate_disjoint_errors, !options.flatten_loops, options.ignore_decomposition_failures)?;
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 diagram(&self, kind: DiagramKind) -> Result<String> {
use crate::diagram as d;
Ok(match kind {
DiagramKind::TimelineText => d::timeline_text(&self.inner)?,
DiagramKind::TimelineSvg => d::timeline_svg(&self.inner)?,
DiagramKind::DetectorSliceText { tick } => d::detslice_text(&self.inner, tick)?,
DiagramKind::DetectorSliceSvg { tick } => d::detslice_svg(&self.inner, tick)?,
DiagramKind::DetectorSlicesSvg { ticks, rows } => d::slices_svg(&self.inner, ticks.0..ticks.1, rows, true, false)?,
DiagramKind::TimeSliceSvg { ticks, rows } => d::slices_svg(&self.inner, ticks.0..ticks.1, rows, false, true)?,
DiagramKind::DetectorSliceWithOpsSvg { ticks, rows } => d::slices_svg(&self.inner, ticks.0..ticks.1, rows, true, true)?,
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)
}
}
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,
}
impl DemOptions {
pub fn new() -> DemOptions {
DemOptions::default()
}
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: Stats,
flat: OnceLock<std::result::Result<Dem, String>>,
}
impl DetectorErrorModel {
pub(crate) fn from_program(program: DemProgram) -> DetectorErrorModel {
DetectorErrorModel { stats: program.stats(), program, flat: OnceLock::new() }
}
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())
}
}
#[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"),
}
}
}
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)?,
})
}
}