use super::{
bit_add, bit_rank, check_qubits, gf2, matrix_cols, matrix_from_rows, matrix_rows,
StabilizerError,
};
use crate::ffield::{FiniteField, GfMatrix};
pub fn symplectic_form(u: &[u8], v: &[u8]) -> Result<u8, StabilizerError> {
if u.len() != v.len() || u.len() % 2 != 0 {
return Err(StabilizerError::ShapeMismatch {
op: "symplectic form",
expected: "two vectors of the same even length 2n".to_string(),
got: format!("lengths {} and {}", u.len(), v.len()),
});
}
let n = u.len() / 2;
let mut acc = 0u8;
for i in 0..n {
acc ^= (u[i] & v[n + i]) ^ (u[n + i] & v[i]);
}
Ok(acc & 1)
}
#[inline]
pub(crate) fn form_unchecked(u: &[u8], v: &[u8]) -> u8 {
let n = u.len() / 2;
let mut acc = 0u8;
for i in 0..n {
acc ^= (u[i] & v[n + i]) ^ (u[n + i] & v[i]);
}
acc & 1
}
pub fn symplectic_gram_matrix(n: usize) -> Result<GfMatrix, StabilizerError> {
symplectic_gram_matrix_over(gf2(), n)
}
pub fn symplectic_gram_matrix_over(
field: &FiniteField,
n: usize,
) -> Result<GfMatrix, StabilizerError> {
check_qubits(n)?;
let d = 2 * n;
let minus_one = field.characteristic() - 1;
let mut entries = vec![0u64; d * d];
for i in 0..n {
entries[i * d + (n + i)] = 1;
entries[(n + i) * d + i] = minus_one;
}
Ok(GfMatrix::from_u64(field, d, d, &entries)?)
}
pub fn is_symplectic(m: &GfMatrix) -> Result<bool, StabilizerError> {
let (r, c) = m.shape();
if r != c || r % 2 != 0 {
return Err(StabilizerError::ShapeMismatch {
op: "Sp(2n, q) membership",
expected: "a square matrix of even size 2n".to_string(),
got: format!("{r}x{c}"),
});
}
if r == 0 {
return Ok(true);
}
let omega = symplectic_gram_matrix_over(m.field(), r / 2)?;
let lhs = m.transpose().mul(&omega)?.mul(m)?;
Ok(lhs.equals(&omega))
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HyperbolicBasis {
n: usize,
pairs: Vec<(Vec<u8>, Vec<u8>)>,
radical: Vec<Vec<u8>>,
}
impl HyperbolicBasis {
pub fn qubits(&self) -> usize {
self.n
}
pub fn pairs(&self) -> &[(Vec<u8>, Vec<u8>)] {
&self.pairs
}
pub fn radical(&self) -> &[Vec<u8>] {
&self.radical
}
pub fn dimension(&self) -> usize {
2 * self.pairs.len() + self.radical.len()
}
pub fn vectors(&self) -> Vec<Vec<u8>> {
let mut out = Vec::with_capacity(self.dimension());
for (u, v) in &self.pairs {
out.push(u.clone());
out.push(v.clone());
}
out.extend(self.radical.iter().cloned());
out
}
}
pub(crate) fn independent_rows(
rows: &[Vec<u8>],
width: usize,
) -> Result<Vec<Vec<u8>>, StabilizerError> {
if rows.is_empty() || width == 0 {
return Ok(Vec::new());
}
let m = matrix_from_rows(rows, width)?;
let rref = m.rref()?;
let all = matrix_rows(&rref.matrix)?;
Ok(all.into_iter().take(rref.rank).collect())
}
pub fn symplectic_gram_schmidt(
vectors: &[Vec<u8>],
n: usize,
) -> Result<HyperbolicBasis, StabilizerError> {
check_qubits(n)?;
let width = 2 * n;
for v in vectors {
if v.len() != width {
return Err(StabilizerError::ShapeMismatch {
op: "symplectic Gram–Schmidt",
expected: format!("vectors of length 2n = {width}"),
got: format!("a vector of length {}", v.len()),
});
}
}
let mut basis = independent_rows(vectors, width)?;
let mut pairs: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
let mut radical: Vec<Vec<u8>> = Vec::new();
while !basis.is_empty() {
let u = basis.remove(0);
let partner = basis.iter().position(|w| form_unchecked(&u, w) == 1);
match partner {
None => radical.push(u),
Some(idx) => {
let v = basis.remove(idx);
for w in basis.iter_mut() {
let a = form_unchecked(w, &v);
let b = form_unchecked(w, &u);
if a == 1 {
*w = bit_add(w, &u);
}
if b == 1 {
*w = bit_add(w, &v);
}
}
pairs.push((u, v));
}
}
}
let out = HyperbolicBasis { n, pairs, radical };
verify_hyperbolic(&out, vectors, width)?;
Ok(out)
}
fn verify_hyperbolic(
b: &HyperbolicBasis,
original: &[Vec<u8>],
width: usize,
) -> Result<(), StabilizerError> {
let all = b.vectors();
let m = b.pairs.len();
for i in 0..m {
for j in 0..m {
let want_uv = u8::from(i == j);
if form_unchecked(&b.pairs[i].0, &b.pairs[j].1) != want_uv {
return Err(StabilizerError::InconsistentResult {
reason: format!("⟨u_{i}, v_{j}⟩ is not {want_uv}"),
});
}
if i != j
&& (form_unchecked(&b.pairs[i].0, &b.pairs[j].0) != 0
|| form_unchecked(&b.pairs[i].1, &b.pairs[j].1) != 0)
{
return Err(StabilizerError::InconsistentResult {
reason: format!("hyperbolic pairs {i} and {j} are not orthogonal"),
});
}
}
}
for (r, rv) in b.radical.iter().enumerate() {
for w in &all {
if form_unchecked(rv, w) != 0 {
return Err(StabilizerError::InconsistentResult {
reason: format!("radical vector {r} is not orthogonal to the whole span"),
});
}
}
}
let span_rank = bit_rank(original, width)?;
if span_rank != b.dimension() {
return Err(StabilizerError::InconsistentResult {
reason: format!(
"the hyperbolic basis has dimension {} but the input spans dimension {span_rank}",
b.dimension()
),
});
}
let mut joint = all.clone();
joint.extend(original.iter().cloned());
if bit_rank(&joint, width)? != span_rank {
return Err(StabilizerError::InconsistentResult {
reason: "the hyperbolic basis does not span the same subspace as the input".to_string(),
});
}
Ok(())
}
pub fn symplectic_complement(
vectors: &[Vec<u8>],
n: usize,
) -> Result<Vec<Vec<u8>>, StabilizerError> {
check_qubits(n)?;
let width = 2 * n;
for v in vectors {
if v.len() != width {
return Err(StabilizerError::ShapeMismatch {
op: "symplectic complement",
expected: format!("vectors of length 2n = {width}"),
got: format!("a vector of length {}", v.len()),
});
}
}
if width == 0 {
return Ok(Vec::new());
}
if vectors.is_empty() {
return Ok((0..width)
.map(|i| {
let mut e = vec![0u8; width];
e[i] = 1;
e
})
.collect());
}
let swapped: Vec<Vec<u8>> = vectors
.iter()
.map(|a| {
let mut s = vec![0u8; width];
s[..n].copy_from_slice(&a[n..]);
s[n..].copy_from_slice(&a[..n]);
s
})
.collect();
let m = matrix_from_rows(&swapped, width)?;
let ns = m.nullspace()?;
matrix_cols(&ns)
}