use super::pauli::{PauliOperator, StabilizerGroup};
use super::symplectic::{symplectic_complement, symplectic_gram_schmidt};
use super::StabilizerError;
use std::fmt;
pub const MAX_DISTANCE_SEARCH_DIM: usize = 22;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Distance {
Exact(usize),
UpperBound(usize),
}
impl Distance {
pub fn value(&self) -> usize {
match self {
Distance::Exact(d) | Distance::UpperBound(d) => *d,
}
}
pub fn is_exact(&self) -> bool {
matches!(self, Distance::Exact(_))
}
}
impl fmt::Display for Distance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Distance::Exact(d) => write!(f, "{d}"),
Distance::UpperBound(d) => write!(f, "≤ {d}"),
}
}
}
#[derive(Clone)]
struct Packed {
xw: Vec<u64>,
zw: Vec<u64>,
}
impl Packed {
fn zeros(words: usize) -> Self {
Self {
xw: vec![0; words],
zw: vec![0; words],
}
}
fn pack(v: &[u8], n: usize) -> Self {
let words = n.div_ceil(64).max(1);
let mut p = Self::zeros(words);
for i in 0..n {
if v[i] & 1 == 1 {
p.xw[i / 64] |= 1u64 << (i % 64);
}
if v[n + i] & 1 == 1 {
p.zw[i / 64] |= 1u64 << (i % 64);
}
}
p
}
fn xor_assign(&mut self, other: &Self) {
for i in 0..self.xw.len() {
self.xw[i] ^= other.xw[i];
self.zw[i] ^= other.zw[i];
}
}
fn weight(&self) -> usize {
self.xw
.iter()
.zip(&self.zw)
.map(|(a, b)| (a | b).count_ones() as usize)
.sum()
}
}
pub(crate) fn min_weight_outside(
logicals: &[Vec<u8>],
stabs: &[Vec<u8>],
n: usize,
cap: usize,
) -> Result<usize, StabilizerError> {
let dim = logicals.len() + stabs.len();
if dim > cap {
return Err(StabilizerError::DistanceSearchTooLarge { dim, cap });
}
if logicals.is_empty() {
return Err(StabilizerError::NoLogicalQubits { n });
}
let basis: Vec<Packed> = logicals
.iter()
.chain(stabs.iter())
.map(|v| Packed::pack(v, n))
.collect();
let low_mask: u64 = if logicals.len() >= 64 {
u64::MAX
} else {
(1u64 << logicals.len()) - 1
};
let words = n.div_ceil(64).max(1);
let mut cur = Packed::zeros(words);
let mut gray: u64 = 0;
let mut best = usize::MAX;
let total: u64 = 1u64 << dim;
for i in 1..total {
let b = i.trailing_zeros() as usize;
cur.xor_assign(&basis[b]);
gray ^= 1u64 << b;
if gray & low_mask != 0 {
let w = cur.weight();
if w < best {
best = w;
}
}
}
if best == usize::MAX || best == 0 {
return Err(StabilizerError::InconsistentResult {
reason: "the distance search found no non-trivial logical operator, or found one \
of weight 0"
.to_string(),
});
}
Ok(best)
}
#[derive(Clone, Debug)]
pub struct StabilizerCode {
group: StabilizerGroup,
n: usize,
k: usize,
logical_x: Vec<PauliOperator>,
logical_z: Vec<PauliOperator>,
stabilizer_basis: Vec<Vec<u8>>,
}
impl StabilizerCode {
pub fn new(group: StabilizerGroup) -> Result<Self, StabilizerError> {
let n = group.qubits();
let k = n - group.rank();
let stab_rows: Vec<Vec<u8>> = group.generators().iter().map(|g| g.symplectic()).collect();
let centralizer = symplectic_complement(&stab_rows, n)?;
let hb = symplectic_gram_schmidt(¢ralizer, n)?;
if hb.pairs().len() != k {
return Err(StabilizerError::InconsistentResult {
reason: format!(
"symplectic Gram–Schmidt on the centralizer produced {} hyperbolic pairs, \
but n − rank(S) = {k}",
hb.pairs().len()
),
});
}
if hb.radical().len() != n - k {
return Err(StabilizerError::InconsistentResult {
reason: format!(
"the radical of the centralizer has dimension {}, but rank(S) = {}",
hb.radical().len(),
n - k
),
});
}
let mut logical_x = Vec::with_capacity(k);
let mut logical_z = Vec::with_capacity(k);
for (u, v) in hb.pairs() {
logical_x.push(PauliOperator::hermitian(&u[..n], &u[n..], false)?);
logical_z.push(PauliOperator::hermitian(&v[..n], &v[n..], false)?);
}
let code = Self {
group,
n,
k,
logical_x,
logical_z,
stabilizer_basis: hb.radical().to_vec(),
};
code.verify_logicals()?;
Ok(code)
}
pub fn from_generators(generators: Vec<PauliOperator>) -> Result<Self, StabilizerError> {
Self::new(StabilizerGroup::new(generators)?)
}
pub fn from_generators_on(
n: usize,
generators: Vec<PauliOperator>,
) -> Result<Self, StabilizerError> {
Self::new(StabilizerGroup::with_qubits(n, generators)?)
}
fn verify_logicals(&self) -> Result<(), StabilizerError> {
for (i, lx) in self.logical_x.iter().enumerate() {
for (g, gen) in self.group.generators().iter().enumerate() {
if gen.symplectic_product(lx)? != 0 {
return Err(StabilizerError::InconsistentResult {
reason: format!("logical X_{i} anticommutes with stabilizer generator {g}"),
});
}
}
}
for (i, lz) in self.logical_z.iter().enumerate() {
for (g, gen) in self.group.generators().iter().enumerate() {
if gen.symplectic_product(lz)? != 0 {
return Err(StabilizerError::InconsistentResult {
reason: format!("logical Z_{i} anticommutes with stabilizer generator {g}"),
});
}
}
}
for i in 0..self.k {
for j in 0..self.k {
let want = u8::from(i == j);
if self.logical_x[i].symplectic_product(&self.logical_z[j])? != want {
return Err(StabilizerError::InconsistentResult {
reason: format!("⟨X_{i}, Z_{j}⟩ is not {want}"),
});
}
if i != j
&& (self.logical_x[i].symplectic_product(&self.logical_x[j])? != 0
|| self.logical_z[i].symplectic_product(&self.logical_z[j])? != 0)
{
return Err(StabilizerError::InconsistentResult {
reason: format!("logical pairs {i} and {j} are not independent"),
});
}
}
}
Ok(())
}
pub fn n(&self) -> usize {
self.n
}
pub fn k(&self) -> usize {
self.k
}
pub fn stabilizer(&self) -> &StabilizerGroup {
&self.group
}
pub fn logical_x(&self) -> &[PauliOperator] {
&self.logical_x
}
pub fn logical_z(&self) -> &[PauliOperator] {
&self.logical_z
}
pub fn syndrome(&self, error: &PauliOperator) -> Result<Vec<u8>, StabilizerError> {
self.group.syndrome(error)
}
pub fn is_logical_error(&self, error: &PauliOperator) -> Result<bool, StabilizerError> {
if !self.group.centralizes(error)? {
return Ok(false);
}
let mut rows = self.stabilizer_basis.clone();
let r = super::bit_rank(&rows, 2 * self.n)?;
rows.push(error.symplectic());
Ok(super::bit_rank(&rows, 2 * self.n)? > r)
}
pub fn minimum_distance(&self) -> Result<Distance, StabilizerError> {
self.minimum_distance_with_cap(MAX_DISTANCE_SEARCH_DIM)
}
pub fn minimum_distance_with_cap(&self, cap: usize) -> Result<Distance, StabilizerError> {
if self.k == 0 {
return Err(StabilizerError::NoLogicalQubits { n: self.n });
}
let logicals: Vec<Vec<u8>> = self
.logical_x
.iter()
.chain(self.logical_z.iter())
.map(|p| p.symplectic())
.collect();
let d = min_weight_outside(&logicals, &self.stabilizer_basis, self.n, cap)?;
Ok(Distance::Exact(d))
}
pub fn distance_upper_bound(&self) -> Result<Distance, StabilizerError> {
if self.k == 0 {
return Err(StabilizerError::NoLogicalQubits { n: self.n });
}
let logicals: Vec<Vec<u8>> = self
.logical_x
.iter()
.chain(self.logical_z.iter())
.map(|p| p.symplectic())
.collect();
let best = if logicals.len() <= 20 {
min_weight_outside(&logicals, &[], self.n, logicals.len())?
} else {
logicals
.iter()
.map(|v| Packed::pack(v, self.n).weight())
.min()
.unwrap_or(0)
};
Ok(Distance::UpperBound(best))
}
}
impl fmt::Display for StabilizerCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[[{}, {}]]", self.n, self.k)
}
}