use rug::ops::Pow;
use rug::{Integer, Rational};
use crate::real::sos::lp::{Lp, LpStatus, Rel};
use super::error::CodingError;
use super::krawtchouk::krawtchouk;
pub const MAX_LP_LENGTH: usize = 96;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DelsarteBound {
n: usize,
d: usize,
q: u64,
optimum: Rational,
bound: Integer,
distribution: Vec<Rational>,
certificate: Vec<Rational>,
}
impl DelsarteBound {
pub fn length(&self) -> usize {
self.n
}
pub fn distance(&self) -> usize {
self.d
}
pub fn alphabet_size(&self) -> u64 {
self.q
}
pub fn optimum(&self) -> &Rational {
&self.optimum
}
pub fn bound(&self) -> &Integer {
&self.bound
}
pub fn distribution(&self) -> &[Rational] {
&self.distribution
}
pub fn certificate(&self) -> &[Rational] {
&self.certificate
}
pub fn verify_certificate(&self) -> bool {
if self.certificate.len() != self.n {
return false;
}
if self.certificate.iter().any(|y| *y < 0) {
return false;
}
for i in self.d..=self.n {
let mut acc = Rational::from(0);
for (idx, y) in self.certificate.iter().enumerate() {
if *y == 0 {
continue;
}
let kk = Rational::from(krawtchouk(idx + 1, i as i64, self.n, self.q));
acc += Rational::from(y * &kk);
}
if acc > -1 {
return false;
}
}
let mut value = Rational::from(1);
for (idx, y) in self.certificate.iter().enumerate() {
if *y == 0 {
continue;
}
let kk = Rational::from(krawtchouk(idx + 1, 0, self.n, self.q));
value += Rational::from(y * &kk);
}
value == self.optimum
}
pub fn verify_distribution(&self) -> bool {
if self.distribution.len() != self.n + 1 || self.distribution[0] != 1 {
return false;
}
if self.distribution.iter().any(|a| *a < 0) {
return false;
}
if self.distribution[1..self.d.min(self.n + 1)]
.iter()
.any(|a| *a != 0)
{
return false;
}
for k in 1..=self.n {
let mut acc = Rational::from(0);
for (i, ai) in self.distribution.iter().enumerate() {
if *ai == 0 {
continue;
}
let kk = Rational::from(krawtchouk(k, i as i64, self.n, self.q));
acc += Rational::from(ai * &kk);
}
if acc < 0 {
return false;
}
}
let total = self
.distribution
.iter()
.fold(Rational::from(0), |a, v| a + v.clone());
total == self.optimum
}
}
pub fn delsarte_lp_bound(n: usize, d: usize, q: u64) -> Result<DelsarteBound, CodingError> {
if n == 0 {
return Err(CodingError::InvalidLength { n });
}
if d == 0 || d > n {
return Err(CodingError::InvalidDistance { d, n });
}
if q < 2 {
return Err(CodingError::InvalidAlphabet {
q: q.to_string(),
reason: "an alphabet needs at least two symbols".to_string(),
});
}
if n > MAX_LP_LENGTH {
return Err(CodingError::LengthTooLarge {
n,
cap: MAX_LP_LENGTH,
});
}
let kvals: Vec<Vec<Rational>> = (0..=n)
.map(|k| {
(0..=n)
.map(|i| Rational::from(krawtchouk(k, i as i64, n, q)))
.collect()
})
.collect();
let free: Vec<usize> = (d..=n).collect();
let mut primal = Lp::new(free.len());
for (v, _) in free.iter().enumerate() {
primal.set_objective(v, Rational::from(-1));
}
for row in kvals.iter().take(n + 1).skip(1) {
let coeffs: Vec<Rational> = free.iter().map(|i| row[*i].clone()).collect();
primal.constrain(coeffs, Rel::Ge, -row[0].clone());
}
let primal_x = match primal.solve() {
LpStatus::Optimal(x) => x,
other => {
return Err(CodingError::LpFailure {
detail: format!(
"the primal programme for A_{q}({n}, {d}) returned {other:?}, but it is \
feasible (A = 0 works) and bounded (Σ A_i ≤ q^n − 1)"
),
})
}
};
let primal_value = Rational::from(1)
+ primal_x
.iter()
.fold(Rational::from(0), |a, v| a + v.clone());
let mut dual = Lp::new(n);
for (k, row) in kvals.iter().enumerate().take(n + 1).skip(1) {
dual.set_objective(k - 1, row[0].clone());
}
for i in d..=n {
let coeffs: Vec<Rational> = kvals
.iter()
.take(n + 1)
.skip(1)
.map(|row| row[i].clone())
.collect();
dual.constrain(coeffs, Rel::Le, Rational::from(-1));
}
let dual_y = match dual.solve() {
LpStatus::Optimal(y) => y,
other => {
return Err(CodingError::LpFailure {
detail: format!(
"the dual programme for A_{q}({n}, {d}) returned {other:?}; by LP duality \
it must have an optimum whenever the primal does"
),
})
}
};
let dual_value = Rational::from(1)
+ (1..=n).fold(Rational::from(0), |a, k| {
a + Rational::from(&dual_y[k - 1] * &kvals[k][0])
});
if primal_value != dual_value {
return Err(CodingError::LpFailure {
detail: format!(
"strong duality failed for A_{q}({n}, {d}): the primal optimum is \
{primal_value} and the dual optimum is {dual_value}"
),
});
}
let mut distribution = vec![Rational::from(0); n + 1];
distribution[0] = Rational::from(1);
for (v, i) in free.iter().enumerate() {
distribution[*i] = primal_x[v].clone();
}
let bound = dual_value
.numer()
.clone()
.div_rem_floor(dual_value.denom().clone())
.0;
let out = DelsarteBound {
n,
d,
q,
optimum: dual_value,
bound,
distribution,
certificate: dual_y,
};
if !out.verify_certificate() {
return Err(CodingError::LpFailure {
detail: format!(
"the dual certificate for A_{q}({n}, {d}) failed its own exact feasibility \
check; the bound it would have supported is not proved and is withheld"
),
});
}
if !out.verify_distribution() {
return Err(CodingError::LpFailure {
detail: format!(
"the primal vertex for A_{q}({n}, {d}) is not feasible for the programme it \
was supposed to optimise"
),
});
}
Ok(out)
}
pub fn singleton_bound(n: usize, d: usize, q: u64) -> Result<Integer, CodingError> {
check_ndq(n, d, q)?;
let e = n - d + 1;
check_power_size(e, q)?;
Ok(Integer::from(q).pow(e as u32))
}
pub fn hamming_bound(n: usize, d: usize, q: u64) -> Result<Integer, CodingError> {
check_ndq(n, d, q)?;
check_power_size(n, q)?;
let t = (d - 1) / 2;
let qm1 = Integer::from(q) - 1u32;
let mut term = Integer::from(1);
let mut volume = Integer::from(1);
for i in 0..t {
term *= &qm1;
term *= n - i;
term /= i + 1;
volume += &term;
}
let total = Integer::from(q).pow(n as u32);
Ok(total.div_rem_floor(volume).0)
}
fn check_power_size(e: usize, q: u64) -> Result<(), CodingError> {
let log2_q = 64 - u64::from(q.leading_zeros());
let bits = (e as u64).saturating_mul(log2_q);
crate::budget::clear_trip();
if let Err(trip) = crate::budget::preflight_bignum_bits(bits, 4) {
crate::budget::record_trip(trip);
return Err(CodingError::LengthTooLarge {
n: e,
cap: (crate::budget::MAX_INTEGER_BITS / log2_q.max(1)) as usize,
});
}
Ok(())
}
fn check_ndq(n: usize, d: usize, q: u64) -> Result<(), CodingError> {
if n == 0 {
return Err(CodingError::InvalidLength { n });
}
if d == 0 || d > n {
return Err(CodingError::InvalidDistance { d, n });
}
if q < 2 {
return Err(CodingError::InvalidAlphabet {
q: q.to_string(),
reason: "an alphabet needs at least two symbols".to_string(),
});
}
Ok(())
}