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 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(&self.inner, options.decompose_errors, options.approximate_disjoint_errors, !options.flatten_loops)?;
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::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,
}
#[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,
}
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 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 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 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)?,
})
}
}