use super::{DecoderBudget, DecoderGraph, DetectorEdge, GraphLimits, GraphStatus, ThresholdCounts};
use crate::photonic::{
qec::{ParityCode, PlanarCode},
runtime::shot_seed,
};
use crate::{Error, Result};
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug)]
pub struct RepeatedMeasurements {
graph: DecoderGraph,
rounds: usize,
variables: usize,
checks: usize,
}
impl RepeatedMeasurements {
pub fn new(code: &ParityCode, rounds: usize, limits: GraphLimits) -> Result<Self> {
if rounds == 0 || code.checks().is_empty() || code.logicals().len() > 64 {
return Err(Error::InvalidDimensions(
"repeated-check graph dimensions".into(),
));
}
let variables = code.variables();
let checks = code.checks().len();
let detectors = rounds
.checked_add(1)
.and_then(|r| r.checked_mul(checks))
.ok_or(Error::Overflow)?;
let count = rounds
.checked_mul(variables.checked_add(checks).ok_or(Error::Overflow)?)
.ok_or(Error::Overflow)?;
for (resource, required, limit) in [
("repeated-check detectors", detectors, limits.max_detectors),
("repeated-check edges", count, limits.max_edges),
] {
if required > limit {
return Err(Error::ResourceLimit {
resource,
required,
limit,
});
}
}
let mut endpoints = Vec::with_capacity(variables);
for q in 0..variables {
let check: Vec<_> = code
.checks()
.iter()
.enumerate()
.filter_map(|(i, row)| row[q].then_some(i))
.collect();
if check.len() > 2 {
return Err(Error::Unsupported(
"hyperedge fault touches more than two detectors".into(),
));
}
endpoints.push(check);
}
let mut edges = Vec::with_capacity(count);
for t in 0..rounds {
for (q, check) in endpoints.iter().enumerate() {
edges.push(DetectorEdge {
a: check.first().map_or(detectors, |v| t * checks + v),
b: check.get(1).map_or(detectors, |v| t * checks + v),
logical: code
.logicals()
.iter()
.enumerate()
.fold(0, |m, (i, row)| m | ((row[q] as u64) << i)),
});
}
}
for t in 0..rounds {
for check in 0..checks {
edges.push(DetectorEdge {
a: t * checks + check,
b: (t + 1) * checks + check,
logical: 0,
});
}
}
Ok(Self {
graph: DecoderGraph::new(detectors, code.logicals().len(), edges, limits)?,
rounds,
variables,
checks,
})
}
pub fn graph(&self) -> &DecoderGraph {
&self.graph
}
pub fn rounds(&self) -> usize {
self.rounds
}
pub fn variables(&self) -> usize {
self.variables
}
pub fn checks(&self) -> usize {
self.checks
}
pub fn data_edge(&self, round: usize, variable: usize) -> Result<usize> {
if round >= self.rounds || variable >= self.variables {
return Err(Error::Basis);
}
Ok(round * self.variables + variable)
}
pub fn measurement_edge(&self, round: usize, check: usize) -> Result<usize> {
if round >= self.rounds || check >= self.checks {
return Err(Error::Basis);
}
Ok(self.rounds * self.variables + round * self.checks + check)
}
pub fn detectors_from_history(&self, history: &[Vec<bool>]) -> Result<Vec<bool>> {
if history.len() != self.rounds + 1 || history.iter().any(|row| row.len() != self.checks) {
return Err(Error::Shape("repeated-check record history".into()));
}
let mut out = vec![false; self.graph.detectors()];
for t in 0..=self.rounds {
for c in 0..self.checks {
out[t * self.checks + c] = history[t][c] ^ (t > 0 && history[t - 1][c]);
}
}
Ok(out)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct PhenomenologicalNoise {
pub data_x: f64,
pub data_z: f64,
pub measurement: f64,
pub data_erasure: f64,
pub measurement_erasure: f64,
}
impl PhenomenologicalNoise {
pub fn symmetric(p: f64) -> Result<Self> {
let out = Self {
data_x: p,
data_z: p,
measurement: p,
data_erasure: 0.,
measurement_erasure: 0.,
};
out.validate()?;
Ok(out)
}
fn validate(&self) -> Result<()> {
if [
self.data_x,
self.data_z,
self.measurement,
self.data_erasure,
self.measurement_erasure,
]
.iter()
.any(|p| !p.is_finite() || !(0.0..=1.0).contains(p))
{
Err(Error::InvalidValue(
"phenomenological noise probability".into(),
))
} else {
Ok(())
}
}
}
#[derive(Clone, Debug)]
pub struct PhenomenologicalMemory {
distance: usize,
channels: [RepeatedMeasurements; 2],
}
impl PhenomenologicalMemory {
pub fn new(code: &PlanarCode, rounds: usize, limits: GraphLimits) -> Result<Self> {
Ok(Self {
distance: code.distance(),
channels: [
RepeatedMeasurements::new(&code.x_error_code()?, rounds, limits)?,
RepeatedMeasurements::new(&code.z_error_code()?, rounds, limits)?,
],
})
}
pub fn distance(&self) -> usize {
self.distance
}
pub fn channel(&self, index: usize) -> Result<&RepeatedMeasurements> {
self.channels.get(index).ok_or(Error::Basis)
}
pub fn run(
&self,
noise: PhenomenologicalNoise,
start: u64,
shots: usize,
seed: u64,
budget: DecoderBudget,
) -> Result<ThresholdCounts> {
noise.validate()?;
start
.checked_add(u64::try_from(shots).map_err(|_| Error::Overflow)?)
.ok_or(Error::Overflow)?;
let base = shot_seed(seed, 0, 0x5048_454e_4f4d_0001);
let mut counts = ThresholdCounts {
attempts: shots,
..Default::default()
};
let mut errors: [Vec<bool>; 2] =
std::array::from_fn(|i| vec![false; self.channels[i].graph.edges().len()]);
let mut erased = errors.clone();
let rounds = self.channels[0].rounds;
let variables = self.channels[0].variables;
for shot in start..start + shots as u64 {
let mut rng = ChaCha8Rng::seed_from_u64(shot_seed(base, shot, 0));
for t in 0..rounds {
for q in 0..variables {
let lost = rng.random::<f64>() < noise.data_erasure;
for channel in 0..2 {
let e = t * variables + q;
erased[channel][e] = lost;
errors[channel][e] = rng.random::<f64>()
< if lost {
0.5
} else if channel == 0 {
noise.data_x
} else {
noise.data_z
};
}
}
}
for channel in 0..2 {
let c = &self.channels[channel];
for e in rounds * variables..c.graph.edges().len() {
let lost = rng.random::<f64>() < noise.measurement_erasure;
erased[channel][e] = lost;
errors[channel][e] =
rng.random::<f64>() < if lost { 0.5 } else { noise.measurement };
}
}
let mut failed = false;
let mut rank = 0;
let mut limit = false;
let mut inconsistent = false;
for channel in 0..2 {
let graph = &self.channels[channel].graph;
let syndrome = graph.syndrome(&errors[channel])?;
let result = graph.decode_union_find(&syndrome, &erased[channel], budget)?;
counts.edge_visits = counts
.edge_visits
.checked_add(result.work.edge_visits)
.ok_or(Error::Overflow)?;
counts.growth_rounds = counts
.growth_rounds
.checked_add(result.work.growth_rounds as u64)
.ok_or(Error::Overflow)?;
match result.status {
GraphStatus::WorkLimit => {
limit = true;
break;
}
GraphStatus::Inconsistent => {
inconsistent = true;
break;
}
_ => {}
}
rank += result.erased_logical_basis.len();
failed |= graph.logical_flips(&errors[channel])? ^ result.logical != 0;
}
if limit {
counts.decoder_limits += 1;
} else if inconsistent {
counts.inconsistent += 1;
} else {
counts.logical_rank_histogram[rank] += 1;
if rank > 0 {
counts.ambiguous_erasures += 1;
}
if failed {
counts.logical_failures += 1;
}
}
}
Ok(counts)
}
}