use ndarray::Array2;
use super::Constructor;
use crate::error::{Error, Result};
use crate::gf::DynamicGf;
use crate::oa::{OA, OAParams};
use crate::utils::factor_prime_power;
#[derive(Debug, Clone)]
pub struct AddelmanKempthorne {
q: u32,
_p: u32,
field: DynamicGf,
kay: u32,
b: Vec<u32>,
c: Vec<u32>,
k: Vec<u32>,
}
impl AddelmanKempthorne {
pub fn new(q: u32) -> Result<Self> {
let factorization = factor_prime_power(q).ok_or(Error::LevelsNotPrimePower {
levels: q,
algorithm: "AddelmanKempthorne",
})?;
let p = factorization.prime;
if p == 2 {
return Err(Error::invalid_params(
"AddelmanKempthorne requires an ODD prime power (3, 5, 7, 9, ...). \
Use BoseBush for powers of 2.",
));
}
let field = DynamicGf::new(q)?;
let (kay, b, c, k) = Self::compute_constants(&field, q, p)?;
Ok(Self {
q,
_p: p,
field,
kay,
b,
c,
k,
})
}
fn compute_constants(
field: &DynamicGf,
q: u32,
p: u32,
) -> Result<(u32, Vec<u32>, Vec<u32>, Vec<u32>)> {
let kay = Self::find_non_residue(field, q)?;
let four = if p == 3 { 1u32 } else { 4u32 };
let four_elem = field.element(four);
let kay_elem = field.element(kay);
let neg_one = field.element(q - 1);
let kay_minus_one = kay_elem.add(neg_one.clone());
let four_inv = if four == 1 {
field.element(1)
} else {
four_elem.inv()
};
let mut b = vec![0u32; q as usize];
let mut c = vec![0u32; q as usize];
let mut k_arr = vec![0u32; q as usize];
b[0] = 0;
c[0] = 0;
k_arr[0] = 0;
for m in 1..q {
let m_elem = field.element(m);
let k_m = kay_elem.mul(m_elem.clone()).to_u32();
k_arr[m as usize] = k_m;
let denom = kay_elem.mul(four_elem.clone()).mul(m_elem.clone());
let denom_inv = denom.inv();
let b_m = kay_minus_one.mul(denom_inv).to_u32();
b[m as usize] = b_m;
let m_sq = m_elem.clone().mul(m_elem);
let c_m = m_sq
.mul(kay_minus_one.clone())
.mul(four_inv.clone())
.to_u32();
c[m as usize] = c_m;
}
Ok((kay, b, c, k_arr))
}
fn find_non_residue(field: &DynamicGf, q: u32) -> Result<u32> {
let mut is_residue = vec![false; q as usize];
is_residue[0] = true;
for y in 1..q {
let y_elem = field.element(y);
let y_sq = y_elem.clone().mul(y_elem).to_u32();
is_residue[y_sq as usize] = true;
}
for x in 1..q {
if !is_residue[x as usize] {
return Ok(x);
}
}
Err(Error::invalid_params(
"Failed to find a quadratic non-residue in GF(q)",
))
}
#[must_use]
pub fn levels(&self) -> u32 {
self.q
}
#[must_use]
pub fn runs(&self) -> usize {
(2 * self.q * self.q) as usize
}
#[must_use]
pub fn max_factors(&self) -> usize {
(2 * self.q + 1) as usize
}
pub fn construct(&self, factors: usize) -> Result<OA> {
let max = self.max_factors();
if factors > max {
return Err(Error::TooManyFactors {
factors,
max,
algorithm: "AddelmanKempthorne",
});
}
if factors == 0 {
return Err(Error::invalid_params("factors must be at least 1"));
}
let q = self.q;
let runs = self.runs();
let mut data = Array2::zeros((runs, factors));
let kay_elem = self.field.element(self.kay);
for i in 0..q {
for j in 0..q {
let row = (i * q + j) as usize;
let i_elem = self.field.element(i);
let j_elem = self.field.element(j);
let i_sq = i_elem.mul(i_elem.clone());
for col in 0..factors {
let col_u32 = col as u32;
let value = if col_u32 == 0 {
j
} else if col_u32 <= q - 1 {
let m = col_u32;
let m_elem = self.field.element(m);
i_elem.add(m_elem.mul(j_elem.clone())).to_u32()
} else if col_u32 == q {
i
} else {
let m = col_u32 - q - 1;
let m_elem = self.field.element(m);
i_sq.add(m_elem.mul(i_elem.clone()))
.add(j_elem.clone())
.to_u32()
};
data[[row, col]] = value;
}
}
}
for i in 0..q {
for j in 0..q {
let row = (q * q + i * q + j) as usize;
let i_elem = self.field.element(i);
let j_elem = self.field.element(j);
let kay_i_sq = kay_elem.mul(i_elem.mul(i_elem.clone()));
for col in 0..factors {
let col_u32 = col as u32;
let value = if col_u32 == 0 {
j_elem.add(self.field.element(self.b[0])).to_u32()
} else if col_u32 <= q - 1 {
let m = col_u32 as usize;
let m_elem = self.field.element(m as u32);
let b_m = self.field.element(self.b[m]);
i_elem.add(m_elem.mul(j_elem.clone())).add(b_m).to_u32()
} else if col_u32 == q {
i_elem.add(self.field.element(self.b[0])).to_u32()
} else {
let m = (col_u32 - q - 1) as usize;
let k_m = self.field.element(self.k[m]);
let c_m = self.field.element(self.c[m]);
kay_i_sq
.add(k_m.mul(i_elem.clone()))
.add(j_elem.clone())
.add(c_m)
.to_u32()
};
data[[row, col]] = value;
}
}
}
let strength = 2.min(factors as u32);
let params = OAParams::new(runs, factors, q, strength)?;
Ok(OA::new(data, params))
}
}
impl Constructor for AddelmanKempthorne {
fn name(&self) -> &'static str {
"AddelmanKempthorne"
}
fn family(&self) -> &'static str {
"OA(2q², k, q, 2), q odd prime power, k ≤ 2q+1"
}
fn levels(&self) -> u32 {
self.q
}
fn strength(&self) -> u32 {
2
}
fn runs(&self) -> usize {
(2 * self.q * self.q) as usize
}
fn max_factors(&self) -> usize {
(2 * self.q + 1) as usize
}
fn construct(&self, factors: usize) -> Result<OA> {
AddelmanKempthorne::construct(self, factors)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oa::verify_strength;
#[test]
fn test_addelman_creation_q3() {
let ak = AddelmanKempthorne::new(3).unwrap();
assert_eq!(ak.levels(), 3);
assert_eq!(ak.runs(), 18);
assert_eq!(ak.max_factors(), 7);
}
#[test]
fn test_addelman_creation_q5() {
let ak = AddelmanKempthorne::new(5).unwrap();
assert_eq!(ak.levels(), 5);
assert_eq!(ak.runs(), 50);
assert_eq!(ak.max_factors(), 11);
}
#[test]
fn test_addelman_creation_q7() {
let ak = AddelmanKempthorne::new(7).unwrap();
assert_eq!(ak.levels(), 7);
assert_eq!(ak.runs(), 98);
assert_eq!(ak.max_factors(), 15);
}
#[test]
fn test_addelman_creation_q9() {
let ak = AddelmanKempthorne::new(9).unwrap();
assert_eq!(ak.levels(), 9);
assert_eq!(ak.runs(), 162);
assert_eq!(ak.max_factors(), 19);
}
#[test]
fn test_addelman_invalid_even() {
assert!(AddelmanKempthorne::new(2).is_err());
assert!(AddelmanKempthorne::new(4).is_err());
assert!(AddelmanKempthorne::new(8).is_err());
assert!(AddelmanKempthorne::new(16).is_err());
}
#[test]
fn test_addelman_invalid_non_prime_power() {
assert!(AddelmanKempthorne::new(6).is_err());
assert!(AddelmanKempthorne::new(10).is_err());
assert!(AddelmanKempthorne::new(12).is_err());
assert!(AddelmanKempthorne::new(15).is_err());
}
#[test]
fn test_addelman_q3_l18() {
let ak = AddelmanKempthorne::new(3).unwrap();
let oa = ak.construct(7).unwrap();
assert_eq!(oa.runs(), 18);
assert_eq!(oa.factors(), 7);
assert_eq!(oa.levels(), 3);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(result.is_valid, "L18 should be valid: {:?}", result.issues);
}
#[test]
fn test_addelman_q5() {
let ak = AddelmanKempthorne::new(5).unwrap();
let oa = ak.construct(11).unwrap();
assert_eq!(oa.runs(), 50);
assert_eq!(oa.factors(), 11);
assert_eq!(oa.levels(), 5);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(50,11,5,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_addelman_q7() {
let ak = AddelmanKempthorne::new(7).unwrap();
let oa = ak.construct(10).unwrap();
assert_eq!(oa.runs(), 98);
assert_eq!(oa.factors(), 10);
assert_eq!(oa.levels(), 7);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(98,10,7,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_addelman_q9() {
let ak = AddelmanKempthorne::new(9).unwrap();
let oa = ak.construct(10).unwrap();
assert_eq!(oa.runs(), 162);
assert_eq!(oa.factors(), 10);
assert_eq!(oa.levels(), 9);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(162,10,9,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_addelman_fewer_factors() {
let ak = AddelmanKempthorne::new(3).unwrap();
let oa = ak.construct(4).unwrap();
assert_eq!(oa.runs(), 18);
assert_eq!(oa.factors(), 4);
assert_eq!(oa.levels(), 3);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(18,4,3,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_addelman_too_many_factors() {
let ak = AddelmanKempthorne::new(3).unwrap();
assert!(ak.construct(8).is_err()); }
#[test]
fn test_addelman_zero_factors() {
let ak = AddelmanKempthorne::new(3).unwrap();
assert!(ak.construct(0).is_err());
}
#[test]
fn test_addelman_single_factor() {
let ak = AddelmanKempthorne::new(3).unwrap();
let oa = ak.construct(1).unwrap();
assert_eq!(oa.factors(), 1);
assert_eq!(oa.runs(), 18);
let mut counts = [0usize; 3];
for row in 0..18 {
counts[oa.get(row, 0) as usize] += 1;
}
assert_eq!(counts, [6, 6, 6]);
}
#[test]
fn test_addelman_balance() {
let ak = AddelmanKempthorne::new(3).unwrap();
let oa = ak.construct(7).unwrap();
let expected_count = 18 / 3;
for col in 0..7 {
let mut counts = [0usize; 3];
for row in 0..18 {
counts[oa.get(row, col) as usize] += 1;
}
assert_eq!(
counts,
[expected_count, expected_count, expected_count],
"Column {} should be balanced",
col
);
}
}
#[test]
fn test_find_non_residue() {
let field = DynamicGf::new(3).unwrap();
let nr = AddelmanKempthorne::find_non_residue(&field, 3).unwrap();
assert_eq!(nr, 2);
let field5 = DynamicGf::new(5).unwrap();
let nr5 = AddelmanKempthorne::find_non_residue(&field5, 5).unwrap();
assert!(nr5 == 2 || nr5 == 3);
}
}