use rug::ops::Pow;
use rug::Integer;
use std::fmt;
use super::error::CodingError;
use super::krawtchouk::krawtchouk;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WeightEnumerator {
n: usize,
q: u64,
coeffs: Vec<Integer>,
}
impl WeightEnumerator {
pub fn new(q: u64, coeffs: Vec<Integer>) -> Result<Self, CodingError> {
if coeffs.is_empty() {
return Err(CodingError::InvalidLength { n: 0 });
}
if q < 2 {
return Err(CodingError::InvalidAlphabet {
q: q.to_string(),
reason: "an alphabet needs at least two symbols".to_string(),
});
}
if coeffs[0] != 1 {
return Err(CodingError::MalformedDistribution {
reason: format!(
"A_0 = {} but a linear code contains the zero word exactly once",
coeffs[0]
),
});
}
if let Some(i) = coeffs.iter().position(|c| *c < 0) {
return Err(CodingError::MalformedDistribution {
reason: format!("A_{i} = {} is negative", coeffs[i]),
});
}
let n = coeffs.len() - 1;
Ok(Self { n, q, coeffs })
}
pub fn length(&self) -> usize {
self.n
}
pub fn alphabet_size(&self) -> u64 {
self.q
}
pub fn coefficients(&self) -> &[Integer] {
&self.coeffs
}
pub fn coefficient(&self, i: usize) -> Integer {
self.coeffs
.get(i)
.cloned()
.unwrap_or_else(|| Integer::from(0))
}
pub fn size(&self) -> Integer {
self.coeffs.iter().fold(Integer::from(0), |a, c| a + c)
}
pub fn minimum_distance(&self) -> Option<usize> {
self.coeffs
.iter()
.skip(1)
.position(|c| *c > 0)
.map(|i| i + 1)
}
pub fn evaluate(&self, x: &Integer, y: &Integer) -> Integer {
let mut total = Integer::from(0);
for (i, ai) in self.coeffs.iter().enumerate() {
if *ai == 0 {
continue;
}
let mut term = ai.clone();
term *= x.clone().pow((self.n - i) as u32);
term *= y.clone().pow(i as u32);
total += term;
}
total
}
pub fn macwilliams(&self) -> Result<WeightEnumerator, CodingError> {
let size = self.size();
if size == 0 {
return Err(CodingError::MalformedDistribution {
reason: "the code is empty, so there is nothing to divide by".to_string(),
});
}
let mut out = Vec::with_capacity(self.n + 1);
for j in 0..=self.n {
let mut acc = Integer::from(0);
for (i, ai) in self.coeffs.iter().enumerate() {
if *ai == 0 {
continue;
}
acc += ai * krawtchouk(j, i as i64, self.n, self.q);
}
let (quot, rem) = acc.clone().div_rem(size.clone());
if rem != 0 {
return Err(CodingError::MalformedDistribution {
reason: format!(
"the MacWilliams sum at j = {j} is {acc}, which is not divisible \
by |C| = {size}; for a linear code over GF({}) it always is, so \
this input is not one",
self.q
),
});
}
if quot < 0 {
return Err(CodingError::MalformedDistribution {
reason: format!("the transform gives B_{j} = {quot} < 0"),
});
}
out.push(quot);
}
WeightEnumerator::new(self.q, out)
}
pub fn is_dual_feasible(&self) -> bool {
(0..=self.n).all(|k| {
let mut acc = Integer::from(0);
for (i, ai) in self.coeffs.iter().enumerate() {
if *ai == 0 {
continue;
}
acc += ai * krawtchouk(k, i as i64, self.n, self.q);
}
acc >= 0
})
}
}
impl fmt::Display for WeightEnumerator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for (i, ai) in self.coeffs.iter().enumerate() {
if *ai == 0 {
continue;
}
if !first {
write!(f, " + ")?;
}
first = false;
let xdeg = self.n - i;
let mut wrote = false;
if *ai != 1 || (xdeg == 0 && i == 0) {
write!(f, "{ai}")?;
wrote = true;
}
if xdeg > 0 {
if wrote {
write!(f, "*")?;
}
write!(f, "x")?;
if xdeg > 1 {
write!(f, "^{xdeg}")?;
}
wrote = true;
}
if i > 0 {
if wrote {
write!(f, "*")?;
}
write!(f, "y")?;
if i > 1 {
write!(f, "^{i}")?;
}
}
}
if first {
write!(f, "0")?;
}
Ok(())
}
}