use rug::ops::Pow;
use rug::Integer;
use crate::ffield::{FieldElement, FiniteField, GfMatrix};
use super::error::{wrap_ff, CodingError};
use super::weight::WeightEnumerator;
pub const MAX_ENUMERATED_CODEWORDS: u128 = 1 << 22;
pub const MAX_ENUMERATION_CELLS: u128 = 1 << 26;
#[derive(Clone)]
pub struct LinearCode {
field: FiniteField,
n: usize,
generator: GfMatrix,
parity: GfMatrix,
}
impl LinearCode {
pub fn from_generator(generator: &GfMatrix) -> Result<Self, CodingError> {
let n = generator.ncols();
if n == 0 {
return Err(CodingError::InvalidLength { n });
}
let g = canonical_row_basis(generator)?;
let parity = dual_basis(&g, n)?;
Ok(Self {
field: generator.field().clone(),
n,
generator: g,
parity,
})
}
pub fn from_parity_check(parity: &GfMatrix) -> Result<Self, CodingError> {
let n = parity.ncols();
if n == 0 {
return Err(CodingError::InvalidLength { n });
}
let h = canonical_row_basis(parity)?;
let generator = dual_basis(&h, n)?;
Ok(Self {
field: parity.field().clone(),
n,
generator,
parity: h,
})
}
pub fn field(&self) -> &FiniteField {
&self.field
}
pub fn length(&self) -> usize {
self.n
}
pub fn dimension(&self) -> usize {
self.generator.nrows()
}
pub fn redundancy(&self) -> usize {
self.parity.nrows()
}
pub fn size(&self) -> Integer {
let q = Integer::from(self.field.characteristic()).pow(self.field.degree() as u32);
q.pow(self.dimension() as u32)
}
pub fn generator(&self) -> &GfMatrix {
&self.generator
}
pub fn parity_check(&self) -> &GfMatrix {
&self.parity
}
pub fn dual(&self) -> Self {
Self {
field: self.field.clone(),
n: self.n,
generator: self.parity.clone(),
parity: self.generator.clone(),
}
}
pub fn is_self_orthogonal(&self) -> Result<bool, CodingError> {
let gt = self.generator.transpose();
let prod = self
.generator
.mul(>)
.map_err(|e| wrap_ff("multiply G by its transpose", e))?;
Ok(prod.is_zero())
}
pub fn is_self_dual(&self) -> Result<bool, CodingError> {
Ok(2 * self.dimension() == self.n && self.is_self_orthogonal()?)
}
pub fn contains(&self, word: &GfMatrix) -> Result<bool, CodingError> {
if word.nrows() != 1 || word.ncols() != self.n {
return Err(CodingError::InvalidLength { n: word.ncols() });
}
if self.parity.nrows() == 0 {
return Ok(true);
}
let s = self
.parity
.mul(&word.transpose())
.map_err(|e| wrap_ff("apply the parity check", e))?;
Ok(s.is_zero())
}
pub fn extend(&self) -> Result<Self, CodingError> {
let k = self.dimension();
let ones = {
let mut m = GfMatrix::zeros(&self.field, self.n, 1)
.map_err(|e| wrap_ff("allocate the all-ones column", e))?;
let one = self.field.one();
for i in 0..self.n {
m.set_entry(i, 0, &one)
.map_err(|e| wrap_ff("fill the all-ones column", e))?;
}
m
};
let sums = self
.generator
.mul(&ones)
.map_err(|e| wrap_ff("compute row sums", e))?
.neg();
let mut g = GfMatrix::zeros(&self.field, k, self.n + 1)
.map_err(|e| wrap_ff("allocate the extended generator", e))?;
for i in 0..k {
for j in 0..self.n {
let v = self
.generator
.entry(i, j)
.map_err(|e| wrap_ff("read the generator", e))?;
g.set_entry(i, j, &v)
.map_err(|e| wrap_ff("write the extended generator", e))?;
}
let v = sums.entry(i, 0).map_err(|e| wrap_ff("read a row sum", e))?;
g.set_entry(i, self.n, &v)
.map_err(|e| wrap_ff("write the overall parity symbol", e))?;
}
LinearCode::from_generator(&g)
}
pub fn weight_distribution(&self) -> Result<Vec<Integer>, CodingError> {
self.weight_distribution_with_cap(MAX_ENUMERATED_CODEWORDS)
}
pub fn weight_distribution_with_cap(&self, cap: u128) -> Result<Vec<Integer>, CodingError> {
let counts = self.enumerate_weights(cap)?;
Ok(counts.into_iter().map(Integer::from).collect())
}
pub fn weight_enumerator(&self) -> Result<WeightEnumerator, CodingError> {
let q = self.alphabet_size()?;
let q = u64::try_from(q).map_err(|_| CodingError::InvalidAlphabet {
q: q.to_string(),
reason: "the weight enumerator's Krawtchouk transform needs q to fit a u64".to_string(),
})?;
WeightEnumerator::new(q, self.weight_distribution()?)
}
pub fn minimum_distance(&self) -> Result<Option<usize>, CodingError> {
let counts = self.enumerate_weights(MAX_ENUMERATED_CODEWORDS)?;
Ok(counts.iter().skip(1).position(|c| *c > 0).map(|i| i + 1))
}
fn alphabet_size(&self) -> Result<u128, CodingError> {
self.field
.order()
.ok_or_else(|| CodingError::InvalidAlphabet {
q: format!("{}^{}", self.field.characteristic(), self.field.degree()),
reason: "the field order overflows a u128".to_string(),
})
}
fn enumerate_weights(&self, cap: u128) -> Result<Vec<u64>, CodingError> {
let k = self.dimension();
let n = self.n;
let deg = self.field.degree();
let p = self.field.characteristic();
let q = self.alphabet_size()?;
let codewords = q
.checked_pow(u32::try_from(k).unwrap_or(u32::MAX))
.unwrap_or(u128::MAX);
if codewords > cap {
return Err(CodingError::EnumerationTooLarge {
codewords: if codewords == u128::MAX {
format!("q^k with q = {q}, k = {k}")
} else {
codewords.to_string()
},
n,
limit: format!("at most {cap} codewords"),
});
}
let cells = codewords
.saturating_mul(n as u128)
.saturating_mul(deg as u128);
if cells > MAX_ENUMERATION_CELLS {
return Err(CodingError::EnumerationTooLarge {
codewords: codewords.to_string(),
n,
limit: format!(
"at most {MAX_ENUMERATION_CELLS} coordinate updates; this needs {cells}"
),
});
}
let table_cells = q
.saturating_mul(k as u128)
.saturating_mul(n as u128)
.saturating_mul(deg as u128);
if table_cells > MAX_ENUMERATION_CELLS {
return Err(CodingError::EnumerationTooLarge {
codewords: codewords.to_string(),
n,
limit: format!(
"the table of scalar multiples would need {table_cells} words, \
past {MAX_ENUMERATION_CELLS}"
),
});
}
let mut counts = vec![0u64; n + 1];
if k == 0 {
counts[0] = 1;
return Ok(counts);
}
let q_usize = q as usize;
let elems = field_elements(&self.field)?;
let mut multiples: Vec<Vec<u64>> = Vec::with_capacity(q_usize * k);
for c in elems.iter() {
let scaled = self
.generator
.scalar_mul(c)
.map_err(|e| wrap_ff("scale the generator", e))?;
let entries = scaled.to_elements();
for i in 0..k {
let mut row = Vec::with_capacity(n * deg);
for j in 0..n {
row.extend_from_slice(&entries[i * n + j].coefficients_padded(deg));
}
multiples.push(row);
}
}
let mut cur = vec![0u64; n * deg];
enumerate(0, k, q_usize, p, n, deg, &multiples, &mut cur, &mut counts);
Ok(counts)
}
}
#[allow(clippy::too_many_arguments)]
fn enumerate(
level: usize,
k: usize,
q: usize,
p: u64,
n: usize,
deg: usize,
multiples: &[Vec<u64>],
cur: &mut [u64],
counts: &mut [u64],
) {
if level == k {
let mut w = 0usize;
for j in 0..n {
if cur[j * deg..(j + 1) * deg].iter().any(|v| *v != 0) {
w += 1;
}
}
counts[w] += 1;
return;
}
enumerate(level + 1, k, q, p, n, deg, multiples, cur, counts);
for c in 1..q {
let add = &multiples[c * k + level];
for (t, v) in cur.iter_mut().zip(add.iter()) {
*t += *v;
if *t >= p {
*t -= p;
}
}
enumerate(level + 1, k, q, p, n, deg, multiples, cur, counts);
for (t, v) in cur.iter_mut().zip(add.iter()) {
if *t < *v {
*t += p;
}
*t -= *v;
}
}
}
pub(crate) fn field_elements(field: &FiniteField) -> Result<Vec<FieldElement>, CodingError> {
let p = field.characteristic();
let deg = field.degree();
let q = field.order().ok_or_else(|| CodingError::InvalidAlphabet {
q: format!("{p}^{deg}"),
reason: "the field order overflows a u128".to_string(),
})?;
if q > MAX_ENUMERATED_CODEWORDS {
return Err(CodingError::EnumerationTooLarge {
codewords: q.to_string(),
n: 0,
limit: format!("at most {MAX_ENUMERATED_CODEWORDS} field elements to enumerate"),
});
}
let q = q as usize;
if deg == 1 {
return Ok((0..p).map(|x| field.scalar(x)).collect());
}
let mut out = Vec::with_capacity(q);
for m in 0..q {
let mut coeffs = Vec::with_capacity(deg);
let mut rest = m as u128;
for _ in 0..deg {
coeffs.push((rest % p as u128) as u64);
rest /= p as u128;
}
out.push(
field
.element(&coeffs)
.map_err(|e| wrap_ff("build a field element", e))?,
);
}
Ok(out)
}
fn canonical_row_basis(m: &GfMatrix) -> Result<GfMatrix, CodingError> {
if m.nrows() == 0 {
return Ok(m.clone());
}
let rref = m.rref().map_err(|e| wrap_ff("rref", e))?;
if rref.rank == m.nrows() {
return Ok(rref.matrix);
}
let mut out = GfMatrix::zeros(m.field(), rref.rank, m.ncols())
.map_err(|e| wrap_ff("allocate the row basis", e))?;
for i in 0..rref.rank {
for j in 0..m.ncols() {
let v = rref
.matrix
.entry(i, j)
.map_err(|e| wrap_ff("read the rref", e))?;
out.set_entry(i, j, &v)
.map_err(|e| wrap_ff("write the row basis", e))?;
}
}
Ok(out)
}
fn dual_basis(m: &GfMatrix, n: usize) -> Result<GfMatrix, CodingError> {
if m.nrows() == 0 {
return GfMatrix::identity(m.field(), n).map_err(|e| wrap_ff("identity", e));
}
let ns = m.nullspace().map_err(|e| wrap_ff("nullspace", e))?;
canonical_row_basis(&ns.transpose())
}
impl LinearCode {
pub fn hamming(field: &FiniteField, r: u32) -> Result<Self, CodingError> {
if r < 2 {
return Err(CodingError::InvalidDistance {
d: 3,
n: r as usize,
});
}
let q = field.order().unwrap_or(u128::MAX);
let total = q
.checked_pow(r)
.ok_or_else(|| CodingError::EnumerationTooLarge {
codewords: format!("q^r with q = {q}, r = {r}"),
n: 0,
limit: format!("at most {MAX_ENUMERATED_CODEWORDS}"),
})?;
if total > MAX_ENUMERATED_CODEWORDS {
return Err(CodingError::EnumerationTooLarge {
codewords: total.to_string(),
n: 0,
limit: format!(
"at most {MAX_ENUMERATED_CODEWORDS} vectors to sieve for projective points"
),
});
}
let elems = field_elements(field)?;
let q = elems.len();
let mut columns: Vec<Vec<usize>> = Vec::new();
for m in 1..total {
let mut digits = vec![0usize; r as usize];
let mut rest = m;
for t in (0..r as usize).rev() {
digits[t] = (rest % q as u128) as usize;
rest /= q as u128;
}
let lead = digits.iter().position(|d| *d != 0).expect("m != 0");
if digits[lead] == 1 {
columns.push(digits);
}
}
let n = columns.len();
let mut h = GfMatrix::zeros(field, r as usize, n)
.map_err(|e| wrap_ff("allocate the Hamming parity check", e))?;
for (j, col) in columns.iter().enumerate() {
for (i, d) in col.iter().enumerate() {
h.set_entry(i, j, &elems[*d])
.map_err(|e| wrap_ff("fill the Hamming parity check", e))?;
}
}
LinearCode::from_parity_check(&h)
}
pub fn golay_binary() -> Result<Self, CodingError> {
let field = FiniteField::prime(2).map_err(|e| wrap_ff("build GF(2)", e))?;
const G: [u64; 12] = [1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1];
let n = 23;
let k = 12;
let mut gen = GfMatrix::zeros(&field, k, n)
.map_err(|e| wrap_ff("allocate the Golay generator", e))?;
let one = field.one();
for i in 0..k {
for (t, c) in G.iter().enumerate() {
if *c == 1 {
gen.set_entry(i, i + t, &one)
.map_err(|e| wrap_ff("fill the Golay generator", e))?;
}
}
}
LinearCode::from_generator(&gen)
}
pub fn repetition(field: &FiniteField, n: usize) -> Result<Self, CodingError> {
if n == 0 {
return Err(CodingError::InvalidLength { n });
}
let mut g =
GfMatrix::zeros(field, 1, n).map_err(|e| wrap_ff("allocate the repetition code", e))?;
let one = field.one();
for j in 0..n {
g.set_entry(0, j, &one)
.map_err(|e| wrap_ff("fill the repetition code", e))?;
}
LinearCode::from_generator(&g)
}
}
impl std::fmt::Debug for LinearCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"LinearCode[n={}, k={}] over {:?}",
self.n,
self.dimension(),
self.field
)
}
}