use std::collections::VecDeque;
use crate::error::{QuantumError, QuantumResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StabKind {
X,
Z,
}
#[derive(Debug, Clone)]
pub struct Stabilizer {
pub kind: StabKind,
pub support: Vec<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PauliError {
pub x: Vec<bool>,
pub z: Vec<bool>,
}
impl PauliError {
#[must_use]
pub fn identity(num_qubits: usize) -> Self {
Self {
x: vec![false; num_qubits],
z: vec![false; num_qubits],
}
}
pub fn single_x(num_qubits: usize, q: usize) -> QuantumResult<Self> {
if q >= num_qubits {
return Err(QuantumError::QubitIndexOutOfRange {
index: q,
n_qubits: num_qubits,
});
}
let mut e = Self::identity(num_qubits);
e.x[q] = true;
Ok(e)
}
pub fn single_z(num_qubits: usize, q: usize) -> QuantumResult<Self> {
if q >= num_qubits {
return Err(QuantumError::QubitIndexOutOfRange {
index: q,
n_qubits: num_qubits,
});
}
let mut e = Self::identity(num_qubits);
e.z[q] = true;
Ok(e)
}
#[must_use]
pub fn weight(&self) -> usize {
self.x
.iter()
.zip(self.z.iter())
.filter(|(a, b)| **a || **b)
.count()
}
pub fn compose(&self, other: &Self) -> QuantumResult<Self> {
if self.x.len() != other.x.len() {
return Err(QuantumError::DimensionMismatch {
expected: self.x.len(),
got: other.x.len(),
});
}
let x = self
.x
.iter()
.zip(other.x.iter())
.map(|(a, b)| a ^ b)
.collect();
let z = self
.z
.iter()
.zip(other.z.iter())
.map(|(a, b)| a ^ b)
.collect();
Ok(Self { x, z })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Syndrome {
pub bits: Vec<bool>,
}
impl Syndrome {
#[must_use]
pub fn is_trivial(&self) -> bool {
self.bits.iter().all(|b| !b)
}
#[must_use]
pub fn weight(&self) -> usize {
self.bits.iter().filter(|b| **b).count()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SurfaceCodeConfig {
pub distance: usize,
}
#[derive(Debug, Clone)]
struct MatchingGraph {
num_nodes: usize,
boundary: usize,
adj: Vec<Vec<(usize, usize)>>,
}
#[derive(Debug, Clone)]
pub struct SurfaceCode {
distance: usize,
num_data: usize,
stabilizers: Vec<Stabilizer>,
x_stab_indices: Vec<usize>,
z_stab_indices: Vec<usize>,
logical_x_support: Vec<usize>,
logical_z_support: Vec<usize>,
graph_z: MatchingGraph,
graph_x: MatchingGraph,
}
impl SurfaceCode {
pub fn new(distance: usize) -> QuantumResult<Self> {
if distance == 0 || distance.is_multiple_of(2) {
return Err(QuantumError::InvalidParameter {
name: "distance must be a positive odd integer".into(),
});
}
let d = distance;
let num_data = d * d;
let stabilizers = build_stabilizers(d);
let x_stab_indices: Vec<usize> = stabilizers
.iter()
.enumerate()
.filter(|(_, s)| s.kind == StabKind::X)
.map(|(i, _)| i)
.collect();
let z_stab_indices: Vec<usize> = stabilizers
.iter()
.enumerate()
.filter(|(_, s)| s.kind == StabKind::Z)
.map(|(i, _)| i)
.collect();
let logical_z_support: Vec<usize> = (0..d).collect();
let logical_x_support: Vec<usize> = (0..d).map(|r| r * d).collect();
let graph_z = build_matching_graph(&stabilizers, &z_stab_indices, num_data);
let graph_x = build_matching_graph(&stabilizers, &x_stab_indices, num_data);
Ok(Self {
distance: d,
num_data,
stabilizers,
x_stab_indices,
z_stab_indices,
logical_x_support,
logical_z_support,
graph_z,
graph_x,
})
}
pub fn from_config(cfg: SurfaceCodeConfig) -> QuantumResult<Self> {
Self::new(cfg.distance)
}
#[must_use]
pub fn distance(&self) -> usize {
self.distance
}
#[must_use]
pub fn num_data_qubits(&self) -> usize {
self.num_data
}
#[must_use]
pub fn num_stabilizers(&self) -> usize {
self.stabilizers.len()
}
#[must_use]
pub fn num_x_stabilizers(&self) -> usize {
self.x_stab_indices.len()
}
#[must_use]
pub fn num_z_stabilizers(&self) -> usize {
self.z_stab_indices.len()
}
#[must_use]
pub fn stabilizers(&self) -> &[Stabilizer] {
&self.stabilizers
}
#[must_use]
pub fn logical_x_support(&self) -> &[usize] {
&self.logical_x_support
}
#[must_use]
pub fn logical_z_support(&self) -> &[usize] {
&self.logical_z_support
}
pub fn syndrome(&self, error: &PauliError) -> QuantumResult<Syndrome> {
if error.x.len() != self.num_data || error.z.len() != self.num_data {
return Err(QuantumError::DimensionMismatch {
expected: self.num_data,
got: error.x.len(),
});
}
let bits = self
.stabilizers
.iter()
.map(|s| {
let component = match s.kind {
StabKind::Z => &error.x,
StabKind::X => &error.z,
};
let overlap = s.support.iter().filter(|&&q| component[q]).count();
!overlap.is_multiple_of(2)
})
.collect();
Ok(Syndrome { bits })
}
pub fn decode(&self, syndrome: &Syndrome) -> QuantumResult<PauliError> {
if syndrome.bits.len() != self.stabilizers.len() {
return Err(QuantumError::DimensionMismatch {
expected: self.stabilizers.len(),
got: syndrome.bits.len(),
});
}
let z_defects: Vec<usize> = self
.z_stab_indices
.iter()
.enumerate()
.filter(|&(_, &g)| syndrome.bits[g])
.map(|(l, _)| l)
.collect();
let x_defects: Vec<usize> = self
.x_stab_indices
.iter()
.enumerate()
.filter(|&(_, &g)| syndrome.bits[g])
.map(|(l, _)| l)
.collect();
let x_correction = match_defects(&self.graph_z, &z_defects, self.num_data);
let z_correction = match_defects(&self.graph_x, &x_defects, self.num_data);
Ok(PauliError {
x: x_correction,
z: z_correction,
})
}
pub fn residual_after_correction(&self, error: &PauliError) -> QuantumResult<PauliError> {
let syndrome = self.syndrome(error)?;
let correction = self.decode(&syndrome)?;
error.compose(&correction)
}
pub fn logical_error(&self, error: &PauliError) -> QuantumResult<(bool, bool)> {
if error.x.len() != self.num_data || error.z.len() != self.num_data {
return Err(QuantumError::DimensionMismatch {
expected: self.num_data,
got: error.x.len(),
});
}
let x_flip = !self
.logical_z_support
.iter()
.filter(|&&q| error.x[q])
.count()
.is_multiple_of(2);
let z_flip = !self
.logical_x_support
.iter()
.filter(|&&q| error.z[q])
.count()
.is_multiple_of(2);
Ok((x_flip, z_flip))
}
}
fn build_stabilizers(d: usize) -> Vec<Stabilizer> {
let mut stabs = Vec::new();
for a in 0..=d {
for b in 0..=d {
let mut support = Vec::new();
for (dr, dc) in [(-1i64, -1i64), (-1, 0), (0, -1), (0, 0)] {
let r = a as i64 + dr;
let c = b as i64 + dc;
if r >= 0 && r < d as i64 && c >= 0 && c < d as i64 {
support.push((r as usize) * d + (c as usize));
}
}
let kind = if (a + b).is_multiple_of(2) {
StabKind::Z
} else {
StabKind::X
};
let keep = match support.len() {
4 => true,
2 => {
let on_top_or_bottom = a == 0 || a == d;
let on_left_or_right = b == 0 || b == d;
match kind {
StabKind::X => on_top_or_bottom,
StabKind::Z => on_left_or_right,
}
}
_ => false,
};
if keep {
stabs.push(Stabilizer { kind, support });
}
}
}
stabs
}
fn build_matching_graph(
stabilizers: &[Stabilizer],
local_indices: &[usize],
num_data: usize,
) -> MatchingGraph {
let num = local_indices.len();
let boundary = num;
let mut adj: Vec<Vec<(usize, usize)>> = vec![Vec::new(); num + 1];
for q in 0..num_data {
let touching: Vec<usize> = local_indices
.iter()
.enumerate()
.filter(|&(_, &g)| stabilizers[g].support.contains(&q))
.map(|(l, _)| l)
.collect();
match touching.as_slice() {
[s, t] => {
adj[*s].push((*t, q));
adj[*t].push((*s, q));
}
[s] => {
adj[*s].push((boundary, q));
adj[boundary].push((*s, q));
}
_ => {}
}
}
MatchingGraph {
num_nodes: num + 1,
boundary,
adj,
}
}
type BfsResult = (Vec<usize>, Vec<Option<(usize, usize)>>);
fn bfs(graph: &MatchingGraph, source: usize) -> BfsResult {
let n = graph.num_nodes;
let mut dist = vec![usize::MAX; n];
let mut pred: Vec<Option<(usize, usize)>> = vec![None; n];
let mut queue = VecDeque::new();
dist[source] = 0;
queue.push_back(source);
while let Some(u) = queue.pop_front() {
if u == graph.boundary {
continue; }
for &(v, q) in &graph.adj[u] {
if dist[v] == usize::MAX {
dist[v] = dist[u] + 1;
pred[v] = Some((u, q));
queue.push_back(v);
}
}
}
(dist, pred)
}
fn path_qubits(pred: &[Option<(usize, usize)>], source: usize, target: usize) -> Vec<usize> {
let mut qubits = Vec::new();
let mut cur = target;
while cur != source {
match pred[cur] {
Some((prev, q)) => {
qubits.push(q);
cur = prev;
}
None => break, }
}
qubits
}
fn match_defects(graph: &MatchingGraph, defects: &[usize], num_data: usize) -> Vec<bool> {
let mut correction = vec![false; num_data];
if defects.is_empty() {
return correction;
}
let k = defects.len();
let searches: Vec<BfsResult> = defects.iter().map(|&d| bfs(graph, d)).collect();
if k > 20 {
for (i, &_d) in defects.iter().enumerate() {
let path = path_qubits(&searches[i].1, defects[i], graph.boundary);
for q in path {
correction[q] ^= true;
}
}
return correction;
}
let pair_dist = |i: usize, j: usize| searches[i].0[defects[j]];
let bnd_dist = |i: usize| searches[i].0[graph.boundary];
let size = 1usize << k;
let mut dp = vec![usize::MAX; size];
let mut partner = vec![usize::MAX; size];
dp[0] = 0;
for mask in 1..size {
let i = mask.trailing_zeros() as usize;
let rest = mask & !(1 << i);
let bd = bnd_dist(i);
if bd != usize::MAX && dp[rest] != usize::MAX {
let cost = dp[rest] + bd;
if cost < dp[mask] {
dp[mask] = cost;
partner[mask] = usize::MAX;
}
}
let mut jbits = rest;
while jbits != 0 {
let j = jbits.trailing_zeros() as usize;
jbits &= jbits - 1;
let pd = pair_dist(i, j);
let sub = rest & !(1 << j);
if pd != usize::MAX && dp[sub] != usize::MAX {
let cost = dp[sub] + pd;
if cost < dp[mask] {
dp[mask] = cost;
partner[mask] = j;
}
}
}
}
let mut mask = size - 1;
while mask != 0 {
let i = mask.trailing_zeros() as usize;
let p = partner[mask];
if p == usize::MAX {
let path = path_qubits(&searches[i].1, defects[i], graph.boundary);
for q in path {
correction[q] ^= true;
}
mask &= !(1 << i);
} else {
let path = path_qubits(&searches[i].1, defects[i], defects[p]);
for q in path {
correction[q] ^= true;
}
mask &= !((1 << i) | (1 << p));
}
}
correction
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distance_three_layout_counts() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
assert_eq!(code.distance(), 3);
assert_eq!(code.num_data_qubits(), 9); assert_eq!(code.num_stabilizers(), 8); assert_eq!(code.num_x_stabilizers(), 4); assert_eq!(code.num_z_stabilizers(), 4);
let weight4 = code
.stabilizers()
.iter()
.filter(|s| s.support.len() == 4)
.count();
let weight2 = code
.stabilizers()
.iter()
.filter(|s| s.support.len() == 2)
.count();
assert_eq!(weight4, 4); assert_eq!(weight2, 4); }
#[test]
fn css_stabilizers_commute() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
for sx in code.stabilizers().iter().filter(|s| s.kind == StabKind::X) {
for sz in code.stabilizers().iter().filter(|s| s.kind == StabKind::Z) {
let overlap = sx.support.iter().filter(|q| sz.support.contains(q)).count();
assert!(overlap.is_multiple_of(2), "overlap={overlap}");
}
}
}
#[test]
fn logical_operators_anticommute() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
let overlap = code
.logical_x_support()
.iter()
.filter(|q| code.logical_z_support().contains(q))
.count();
assert_eq!(overlap, 1);
assert_eq!(code.logical_x_support().len(), 3);
assert_eq!(code.logical_z_support().len(), 3);
}
#[test]
fn invalid_distance_rejected() {
assert!(SurfaceCode::new(0).is_err()); assert!(SurfaceCode::new(2).is_err()); assert!(SurfaceCode::new(4).is_err()); assert!(SurfaceCode::new(3).is_ok()); }
#[test]
fn zero_error_gives_empty_syndrome_and_no_correction() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
let err = PauliError::identity(code.num_data_qubits());
let syn = code.syndrome(&err).expect("syndrome for identity error");
assert!(syn.is_trivial());
let corr = code.decode(&syn).expect("decode trivial syndrome");
assert_eq!(corr.weight(), 0);
}
#[test]
fn single_x_on_bulk_qubit_fires_two_z_stabilizers() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
let err = PauliError::single_x(code.num_data_qubits(), 4)
.expect("valid single-X error on qubit 4");
let syn = code
.syndrome(&err)
.expect("syndrome for single-X error on qubit 4");
assert_eq!(syn.weight(), 2, "central X error should fire two defects");
for (i, &fired) in syn.bits.iter().enumerate() {
if fired {
assert_eq!(code.stabilizers()[i].kind, StabKind::Z);
}
}
let residual = code
.residual_after_correction(&err)
.expect("residual after correcting X error on qubit 4");
assert_eq!(
code.logical_error(&residual)
.expect("logical error check on residual after X correction"),
(false, false)
);
}
#[test]
fn single_z_on_bulk_qubit_fires_two_x_stabilizers() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
let err = PauliError::single_z(code.num_data_qubits(), 4)
.expect("valid single-Z error on qubit 4");
let syn = code
.syndrome(&err)
.expect("syndrome for single-Z error on qubit 4");
assert_eq!(syn.weight(), 2);
for (i, &fired) in syn.bits.iter().enumerate() {
if fired {
assert_eq!(code.stabilizers()[i].kind, StabKind::X);
}
}
let residual = code
.residual_after_correction(&err)
.expect("residual after correcting Z error on qubit 4");
assert_eq!(
code.logical_error(&residual)
.expect("logical error check on residual after Z correction"),
(false, false)
);
}
#[test]
fn every_weight_one_error_is_corrected() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
let n = code.num_data_qubits();
for q in 0..n {
let ex = PauliError::single_x(n, q).expect("valid single-X error on qubit q");
let rx = code
.residual_after_correction(&ex)
.expect("residual after correcting single-X error");
assert_eq!(
code.logical_error(&rx)
.expect("logical error check on single-X residual"),
(false, false),
"X error on qubit {q} not corrected"
);
let ez = PauliError::single_z(n, q).expect("valid single-Z error on qubit q");
let rz = code
.residual_after_correction(&ez)
.expect("residual after correcting single-Z error");
assert_eq!(
code.logical_error(&rz)
.expect("logical error check on single-Z residual"),
(false, false),
"Z error on qubit {q} not corrected"
);
let mut ey =
PauliError::single_x(n, q).expect("valid single-X component of Y error on qubit q");
ey.z[q] = true;
let ry = code
.residual_after_correction(&ey)
.expect("residual after correcting single-Y error");
assert_eq!(
code.logical_error(&ry)
.expect("logical error check on single-Y residual"),
(false, false),
"Y error on qubit {q} not corrected"
);
}
}
#[test]
fn corrected_residual_has_trivial_syndrome() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
let n = code.num_data_qubits();
for q in 0..n {
let err = PauliError::single_x(n, q).expect("valid single-X error on qubit q");
let residual = code
.residual_after_correction(&err)
.expect("residual after correcting error on qubit q");
let syn = code
.syndrome(&residual)
.expect("syndrome for residual error");
assert!(syn.is_trivial(), "residual syndrome non-trivial for q={q}");
}
}
#[test]
fn boundary_x_error_fires_single_defect() {
let code = SurfaceCode::new(3).expect("valid distance-3 surface code");
let err = PauliError::single_x(code.num_data_qubits(), 0)
.expect("valid single-X error on corner qubit 0");
let syn = code
.syndrome(&err)
.expect("syndrome for boundary X error on qubit 0");
assert_eq!(syn.weight(), 1);
let residual = code
.residual_after_correction(&err)
.expect("residual after correcting boundary X error");
assert_eq!(
code.logical_error(&residual)
.expect("logical error check on boundary residual"),
(false, false)
);
}
#[test]
fn distance_five_layout_counts() {
let code = SurfaceCode::new(5).expect("valid distance-5 surface code");
assert_eq!(code.num_data_qubits(), 25);
assert_eq!(code.num_stabilizers(), 24);
assert_eq!(code.num_x_stabilizers(), 12);
assert_eq!(code.num_z_stabilizers(), 12);
}
}