pub mod code;
pub mod css;
pub mod matrix_group;
pub mod pauli;
pub mod symplectic;
#[cfg(test)]
mod proptests;
#[cfg(test)]
mod tests;
pub use code::{Distance, StabilizerCode, MAX_DISTANCE_SEARCH_DIM};
pub use css::CssCode;
pub use matrix_group::{MatrixGroup, MatrixGroupKind, MAX_MATRIX_ENUMERATION};
pub use pauli::{PauliOperator, StabilizerGroup};
pub use symplectic::{
is_symplectic, symplectic_complement, symplectic_form, symplectic_gram_matrix,
symplectic_gram_schmidt, HyperbolicBasis,
};
use crate::ffield::FiniteFieldError;
use std::fmt;
pub const MAX_QUBITS: usize = 1024;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StabilizerError {
FieldNotBinary {
field: String,
},
ShapeMismatch {
op: &'static str,
expected: String,
got: String,
},
QubitCountMismatch {
left: usize,
right: usize,
},
NotCommuting {
i: usize,
j: usize,
},
CssConditionViolated {
row: usize,
col: usize,
},
ContainsMinusIdentity {
combination: Vec<usize>,
},
NoLogicalQubits {
n: usize,
},
DistanceSearchTooLarge {
dim: usize,
cap: usize,
},
TooManyQubits {
n: usize,
max: usize,
},
MalformedPauli {
reason: String,
},
EnumerationTooLarge {
candidates: String,
cap: u64,
},
FieldMismatch {
lhs: String,
rhs: String,
},
InconsistentResult {
reason: String,
},
FiniteField(FiniteFieldError),
}
impl From<FiniteFieldError> for StabilizerError {
fn from(e: FiniteFieldError) -> Self {
StabilizerError::FiniteField(e)
}
}
impl fmt::Display for StabilizerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StabilizerError::FieldNotBinary { field } => write!(
f,
"the Pauli and stabilizer surface is defined over GF(2) only; got {field}. \
Qudit stabilizer codes are a different theory, not a generalisation applied here"
),
StabilizerError::ShapeMismatch { op, expected, got } => {
write!(f, "{op} needs {expected}, but got {got}")
}
StabilizerError::QubitCountMismatch { left, right } => write!(
f,
"Pauli operators are on {left} and {right} qubits; counts are not padded \
with identities automatically"
),
StabilizerError::NotCommuting { i, j } => write!(
f,
"generators {i} and {j} anticommute, so they generate no abelian group \
and stabilize no non-zero state"
),
StabilizerError::CssConditionViolated { row, col } => write!(
f,
"the CSS condition H_X · H_Zᵀ = 0 fails at entry ({row}, {col}): the X-type \
stabilizer from row {row} of H_X anticommutes with the Z-type stabilizer \
from row {col} of H_Z"
),
StabilizerError::ContainsMinusIdentity { combination } => write!(
f,
"the product of generators {combination:?} is −I, so the +1 eigenspace \
is {{0}} and the code encodes nothing"
),
StabilizerError::NoLogicalQubits { n } => write!(
f,
"this [[{n}, 0]] code has no logical qubits, so it has no minimum distance: \
N(S) \\ S is empty"
),
StabilizerError::DistanceSearchTooLarge { dim, cap } => write!(
f,
"an exhaustive distance search would enumerate 2^{dim} centralizer elements, \
past the cap of 2^{cap}"
),
StabilizerError::TooManyQubits { n, max } => {
write!(f, "{n} qubits exceeds this module's limit of {max}")
}
StabilizerError::MalformedPauli { reason } => {
write!(f, "malformed Pauli operator: {reason}")
}
StabilizerError::EnumerationTooLarge { candidates, cap } => write!(
f,
"listing this group would filter {candidates} candidate matrices, past the \
cap of {cap}"
),
StabilizerError::FieldMismatch { lhs, rhs } => {
write!(f, "operands live over different fields: {lhs} and {rhs}")
}
StabilizerError::InconsistentResult { reason } => write!(
f,
"a result failed its own consistency check and was withheld: {reason}. \
This is a bug in alkahest's stabilizer layer, not in the input"
),
StabilizerError::FiniteField(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for StabilizerError {}
impl crate::errors::AlkahestError for StabilizerError {
fn code(&self) -> &'static str {
match self {
StabilizerError::FieldNotBinary { .. } => "E-STAB-001",
StabilizerError::ShapeMismatch { .. } => "E-STAB-002",
StabilizerError::QubitCountMismatch { .. } => "E-STAB-003",
StabilizerError::NotCommuting { .. } => "E-STAB-004",
StabilizerError::CssConditionViolated { .. } => "E-STAB-005",
StabilizerError::ContainsMinusIdentity { .. } => "E-STAB-006",
StabilizerError::NoLogicalQubits { .. } => "E-STAB-007",
StabilizerError::DistanceSearchTooLarge { .. } => "E-STAB-008",
StabilizerError::TooManyQubits { .. } => "E-STAB-009",
StabilizerError::MalformedPauli { .. } => "E-STAB-010",
StabilizerError::EnumerationTooLarge { .. } => "E-STAB-011",
StabilizerError::FieldMismatch { .. } => "E-STAB-012",
StabilizerError::InconsistentResult { .. } => "E-STAB-013",
StabilizerError::FiniteField(e) => crate::errors::AlkahestError::code(e),
}
}
fn remediation(&self) -> Option<&'static str> {
crate::errors::codes::REGISTRY
.iter()
.find(|spec| spec.code == crate::errors::AlkahestError::code(self))
.and_then(|spec| spec.remediation)
}
}
use crate::ffield::{FiniteField, GfMatrix};
use std::sync::OnceLock;
pub(crate) fn gf2() -> &'static FiniteField {
static GF2: OnceLock<FiniteField> = OnceLock::new();
GF2.get_or_init(|| FiniteField::prime(2).expect("2 is prime"))
}
pub(crate) fn require_gf2(m: &GfMatrix) -> Result<(), StabilizerError> {
if m.field().characteristic() == 2 && m.field().degree() == 1 {
Ok(())
} else {
Err(StabilizerError::FieldNotBinary {
field: format!("{}", m.field()),
})
}
}
pub(crate) fn check_qubits(n: usize) -> Result<(), StabilizerError> {
if n > MAX_QUBITS {
return Err(StabilizerError::TooManyQubits { n, max: MAX_QUBITS });
}
Ok(())
}
pub(crate) fn matrix_rows(m: &GfMatrix) -> Result<Vec<Vec<u8>>, StabilizerError> {
require_gf2(m)?;
let (r, c) = m.shape();
let flat = m.to_u64().ok_or_else(|| StabilizerError::FieldNotBinary {
field: format!("{}", m.field()),
})?;
Ok((0..r)
.map(|i| (0..c).map(|j| (flat[i * c + j] & 1) as u8).collect())
.collect())
}
pub(crate) fn matrix_cols(m: &GfMatrix) -> Result<Vec<Vec<u8>>, StabilizerError> {
require_gf2(m)?;
let (r, c) = m.shape();
let flat = m.to_u64().ok_or_else(|| StabilizerError::FieldNotBinary {
field: format!("{}", m.field()),
})?;
Ok((0..c)
.map(|j| (0..r).map(|i| (flat[i * c + j] & 1) as u8).collect())
.collect())
}
pub(crate) fn matrix_from_rows(
rows: &[Vec<u8>],
ncols: usize,
) -> Result<GfMatrix, StabilizerError> {
let flat: Vec<u64> = rows
.iter()
.flat_map(|r| r.iter().map(|&b| u64::from(b & 1)))
.collect();
for r in rows {
if r.len() != ncols {
return Err(StabilizerError::ShapeMismatch {
op: "assemble a matrix from rows",
expected: format!("every row of length {ncols}"),
got: format!("a row of length {}", r.len()),
});
}
}
Ok(GfMatrix::from_u64(gf2(), rows.len(), ncols, &flat)?)
}
pub(crate) fn bit_rank(rows: &[Vec<u8>], ncols: usize) -> Result<usize, StabilizerError> {
if rows.is_empty() || ncols == 0 {
return Ok(0);
}
Ok(matrix_from_rows(rows, ncols)?.rank())
}
pub(crate) fn bit_add(u: &[u8], v: &[u8]) -> Vec<u8> {
u.iter().zip(v).map(|(a, b)| (a ^ b) & 1).collect()
}