use std::collections::HashMap;
use std::fmt::Write;
use super::noise::lower_qec_program_to_deferred_circuit;
use super::parity_walk::compile_fault_sites;
use super::runner::QecParityProjection;
use super::{QecOp, QecProgram};
use crate::error::{PrismError, Result};
#[derive(Debug, Clone, PartialEq)]
pub struct ErrorMechanism {
probability: f64,
detectors: Vec<usize>,
observables: Vec<usize>,
components: Vec<Symptom>,
}
impl ErrorMechanism {
pub(super) fn new(
probability: f64,
detectors: Vec<usize>,
observables: Vec<usize>,
components: Vec<Symptom>,
) -> Self {
Self {
probability,
detectors,
observables,
components,
}
}
pub fn probability(&self) -> f64 {
self.probability
}
pub fn detectors(&self) -> &[usize] {
&self.detectors
}
pub fn observables(&self) -> &[usize] {
&self.observables
}
pub fn suggested_decomposition(&self) -> &[(Vec<usize>, Vec<usize>)] {
&self.components
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DetectorErrorModel {
mechanisms: Vec<ErrorMechanism>,
detector_coords: Vec<Vec<f64>>,
num_detectors: usize,
num_observables: usize,
}
impl DetectorErrorModel {
pub(super) fn from_parts(
mechanisms: Vec<ErrorMechanism>,
detector_coords: Vec<Vec<f64>>,
num_detectors: usize,
num_observables: usize,
) -> Self {
Self {
mechanisms,
detector_coords,
num_detectors,
num_observables,
}
}
pub fn from_text(text: &str) -> Result<Self> {
super::dem_text::parse_detector_error_model(text)
}
pub fn mechanisms(&self) -> &[ErrorMechanism] {
&self.mechanisms
}
pub fn num_mechanisms(&self) -> usize {
self.mechanisms.len()
}
pub fn num_detectors(&self) -> usize {
self.num_detectors
}
pub fn num_observables(&self) -> usize {
self.num_observables
}
pub fn detector_coords(&self) -> &[Vec<f64>] {
&self.detector_coords
}
pub fn decompose_graphlike(&self) -> Result<DetectorErrorModel> {
let mut graphlike: Vec<ErrorMechanism> = Vec::new();
for mechanism in &self.mechanisms {
if mechanism.detectors.len() <= 2 {
graphlike.push(mechanism.clone());
}
}
let index = CoverIndex::new(&graphlike, self.num_detectors);
let mut by_symptom: Option<HashMap<Symptom, usize>> = None;
for mechanism in &self.mechanisms {
if mechanism.detectors.len() <= 2 {
continue;
}
let p = mechanism.probability;
if !mechanism.components.is_empty()
&& mechanism
.components
.iter()
.all(|(detectors, _)| matches!(detectors.len(), 1 | 2))
{
let by_symptom = by_symptom.get_or_insert_with(|| {
graphlike
.iter()
.enumerate()
.map(|(at, m)| ((m.detectors.clone(), m.observables.clone()), at))
.collect()
});
for component in &mechanism.components {
let at = *by_symptom.entry(component.clone()).or_insert_with(|| {
graphlike.push(ErrorMechanism::new(
0.0,
component.0.clone(),
component.1.clone(),
Vec::new(),
));
graphlike.len() - 1
});
let prior = graphlike[at].probability;
graphlike[at].probability = prior * (1.0 - p) + p * (1.0 - prior);
}
continue;
}
let Some(components) = partition_cover(mechanism, &graphlike, &index) else {
return Err(PrismError::InvalidParameter {
message: format!(
"graphlike decomposition failed: mechanism `{}` has no \
cover by graphlike mechanisms",
symptom_label(mechanism)
),
});
};
for at in components {
let prior = graphlike[at].probability;
graphlike[at].probability = prior * (1.0 - p) + p * (1.0 - prior);
}
}
Ok(DetectorErrorModel {
mechanisms: graphlike,
detector_coords: self.detector_coords.clone(),
num_detectors: self.num_detectors,
num_observables: self.num_observables,
})
}
pub fn to_text(&self) -> String {
let mut out = String::new();
let mut cover: Option<(Vec<ErrorMechanism>, CoverIndex)> = None;
for mechanism in &self.mechanisms {
write!(out, "error({})", mechanism.probability).expect("String write");
if !mechanism.components.is_empty() {
for (k, (detectors, observables)) in mechanism.components.iter().enumerate() {
if k > 0 {
out.push_str(" ^");
}
write_symptom(&mut out, detectors, observables);
}
} else if mechanism.detectors.len() > 2 {
let (graphlike, index) = cover.get_or_insert_with(|| {
let graphlike: Vec<ErrorMechanism> = self
.mechanisms
.iter()
.filter(|m| m.detectors.len() <= 2)
.cloned()
.collect();
let index = CoverIndex::new(&graphlike, self.num_detectors);
(graphlike, index)
});
match partition_cover(mechanism, graphlike, index) {
Some(components) => {
for (k, at) in components.into_iter().enumerate() {
if k > 0 {
out.push_str(" ^");
}
let component = &graphlike[at];
write_symptom(&mut out, &component.detectors, &component.observables);
}
}
None => write_symptom(&mut out, &mechanism.detectors, &mechanism.observables),
}
} else {
write_symptom(&mut out, &mechanism.detectors, &mechanism.observables);
}
out.push('\n');
}
for (detector, coords) in self.detector_coords.iter().enumerate() {
out.push_str("detector");
if !coords.is_empty() {
out.push('(');
for (k, c) in coords.iter().enumerate() {
if k > 0 {
out.push_str(", ");
}
write!(out, "{c}").expect("String write");
}
out.push(')');
}
writeln!(out, " D{detector}").expect("String write");
}
for observable in 0..self.num_observables {
writeln!(out, "logical_observable L{observable}").expect("String write");
}
out
}
}
fn write_symptom(out: &mut String, detectors: &[usize], observables: &[usize]) {
for detector in detectors {
write!(out, " D{detector}").expect("String write");
}
for observable in observables {
write!(out, " L{observable}").expect("String write");
}
}
impl QecProgram {
pub fn detector_error_model(&self) -> Result<DetectorErrorModel> {
derive_detector_error_model(self)
}
}
pub(super) type Symptom = (Vec<usize>, Vec<usize>);
fn derive_detector_error_model(program: &QecProgram) -> Result<DetectorErrorModel> {
if program.has_leakage() {
return Err(super::leakage::leakage_rejection("detector error model"));
}
let detector_rows = program.detector_rows()?;
let observable_rows = program.observable_rows()?;
let num_detectors = detector_rows.len();
let num_observables = observable_rows.len();
let projection =
QecParityProjection::new(program.num_measurements(), &detector_rows, &observable_rows);
let deferred = lower_qec_program_to_deferred_circuit(program)?;
let sites = compile_fault_sites(&deferred, &projection)?;
let mut index: HashMap<Symptom, usize> = HashMap::new();
let mut mechanisms: Vec<ErrorMechanism> = Vec::new();
let mut local: Vec<(Symptom, f64)> = Vec::new();
for site in sites.sites() {
local.clear();
for (probability, outputs) in site {
if outputs.is_empty() {
continue;
}
let split = outputs.partition_point(|&output| (output as usize) < num_detectors);
let symptom: Symptom = (
outputs[..split]
.iter()
.map(|&output| output as usize)
.collect(),
outputs[split..]
.iter()
.map(|&output| output as usize - num_detectors)
.collect(),
);
match local.iter_mut().find(|(existing, _)| *existing == symptom) {
Some((_, total)) => *total += probability,
None => local.push((symptom, probability)),
}
}
for (symptom, probability) in local.drain(..) {
match index.get(&symptom) {
Some(&at) => {
let prior = mechanisms[at].probability;
mechanisms[at].probability =
prior * (1.0 - probability) + probability * (1.0 - prior);
}
None => {
index.insert(symptom.clone(), mechanisms.len());
let (detectors, observables) = symptom;
mechanisms.push(ErrorMechanism::new(
probability,
detectors,
observables,
Vec::new(),
));
}
}
}
}
Ok(DetectorErrorModel {
mechanisms,
detector_coords: detector_coordinates(program),
num_detectors,
num_observables,
})
}
struct CoverIndex {
by_detector: Vec<Vec<u32>>,
}
impl CoverIndex {
fn new(graphlike: &[ErrorMechanism], num_detectors: usize) -> Self {
let mut by_detector = vec![Vec::new(); num_detectors];
for (at, mechanism) in graphlike.iter().enumerate() {
for &detector in &mechanism.detectors {
by_detector[detector].push(at as u32);
}
}
Self { by_detector }
}
}
fn partition_cover(
mechanism: &ErrorMechanism,
graphlike: &[ErrorMechanism],
index: &CoverIndex,
) -> Option<Vec<usize>> {
fn search(
remaining: &[usize],
observables: &[usize],
start: usize,
candidates: &[usize],
graphlike: &[ErrorMechanism],
chosen: &mut Vec<usize>,
) -> bool {
if remaining.is_empty() {
return observables.is_empty();
}
for (next, &at) in candidates.iter().enumerate().skip(start) {
let candidate = &graphlike[at];
if candidate.detectors.is_empty() || !is_subset(&candidate.detectors, remaining) {
continue;
}
let next_remaining = symmetric_difference(remaining, &candidate.detectors);
let next_observables = symmetric_difference(observables, &candidate.observables);
chosen.push(at);
if search(
&next_remaining,
&next_observables,
next + 1,
candidates,
graphlike,
chosen,
) {
return true;
}
chosen.pop();
}
false
}
let mut candidates: Vec<usize> = mechanism
.detectors
.iter()
.filter_map(|&detector| index.by_detector.get(detector))
.flatten()
.map(|&at| at as usize)
.filter(|&at| is_subset(&graphlike[at].detectors, &mechanism.detectors))
.collect();
candidates.sort_unstable();
candidates.dedup();
let mut chosen = Vec::new();
search(
&mechanism.detectors,
&mechanism.observables,
0,
&candidates,
graphlike,
&mut chosen,
)
.then_some(chosen)
}
fn is_subset(a: &[usize], b: &[usize]) -> bool {
let mut j = 0;
'outer: for &x in a {
while j < b.len() {
match b[j].cmp(&x) {
std::cmp::Ordering::Less => j += 1,
std::cmp::Ordering::Equal => {
j += 1;
continue 'outer;
}
std::cmp::Ordering::Greater => return false,
}
}
return false;
}
true
}
fn symmetric_difference(a: &[usize], b: &[usize]) -> Vec<usize> {
let mut out = Vec::with_capacity(a.len() + b.len());
let (mut i, mut j) = (0, 0);
while i < a.len() && j < b.len() {
match a[i].cmp(&b[j]) {
std::cmp::Ordering::Less => {
out.push(a[i]);
i += 1;
}
std::cmp::Ordering::Greater => {
out.push(b[j]);
j += 1;
}
std::cmp::Ordering::Equal => {
i += 1;
j += 1;
}
}
}
out.extend_from_slice(&a[i..]);
out.extend_from_slice(&b[j..]);
out
}
pub(super) fn symptom_label(mechanism: &ErrorMechanism) -> String {
let mut label = String::new();
for detector in &mechanism.detectors {
if !label.is_empty() {
label.push(' ');
}
label.push_str(&format!("D{detector}"));
}
for observable in &mechanism.observables {
if !label.is_empty() {
label.push(' ');
}
label.push_str(&format!("L{observable}"));
}
label
}
fn detector_coordinates(program: &QecProgram) -> Vec<Vec<f64>> {
program
.ops()
.iter()
.filter_map(|op| match op {
QecOp::Detector { coords, .. } => Some(coords.clone()),
_ => None,
})
.collect()
}