use ndarray::Array2;
use super::Constructor;
use crate::error::{Error, Result};
use crate::gf::DynamicGf;
use crate::oa::{OA, OAParams};
use crate::utils::is_prime_power;
#[derive(Debug, Clone)]
pub struct RaoHamming {
q: u32,
m: u32,
field: DynamicGf,
}
impl RaoHamming {
pub fn new(q: u32, m: u32) -> Result<Self> {
if !is_prime_power(q) {
return Err(Error::LevelsNotPrimePower {
levels: q,
algorithm: "RaoHamming",
});
}
if m < 2 {
return Err(Error::invalid_params("RaoHamming requires m >= 2"));
}
let field = DynamicGf::new(q)?;
Ok(Self { q, m, field })
}
#[must_use]
pub fn levels(&self) -> u32 {
self.q
}
#[must_use]
pub fn runs(&self) -> usize {
self.q.pow(self.m) as usize
}
#[must_use]
pub fn max_factors(&self) -> usize {
let q = self.q as usize;
let m = self.m as u32;
(q.pow(m) - 1) / (q - 1)
}
fn generate_columns(&self, factors: usize) -> Vec<Vec<u32>> {
let q = self.q;
let m = self.m as usize;
let mut columns = Vec::with_capacity(factors);
for i in 1..q.pow(self.m) {
let mut vec = Vec::with_capacity(m);
let mut temp = i;
for _ in 0..m {
vec.push(temp % q);
temp /= q;
}
vec.reverse();
for &val in &vec {
if val != 0 {
if val == 1 {
columns.push(vec);
}
break;
}
}
if columns.len() == factors {
break;
}
}
columns
}
}
impl Constructor for RaoHamming {
fn name(&self) -> &'static str {
"RaoHamming"
}
fn family(&self) -> &'static str {
"OA(q^m, k, q, 2), k ≤ (q^m-1)/(q-1)"
}
fn levels(&self) -> u32 {
self.q
}
fn strength(&self) -> u32 {
2
}
fn runs(&self) -> usize {
self.q.pow(self.m) as usize
}
fn max_factors(&self) -> usize {
self.max_factors()
}
fn construct(&self, factors: usize) -> Result<OA> {
let max = self.max_factors();
if factors > max {
return Err(Error::TooManyFactors {
factors,
max,
algorithm: "RaoHamming",
});
}
if factors == 0 {
return Err(Error::invalid_params("factors must be at least 1"));
}
let q = self.q;
let m = self.m as usize;
let runs = self.runs();
let mut data = Array2::zeros((runs, factors));
let cols = self.generate_columns(factors);
for row_idx in 0..runs {
let mut r_vec = Vec::with_capacity(m);
let mut temp = row_idx as u32;
for _ in 0..m {
r_vec.push(temp % q);
temp /= q;
}
for (col_idx, col_vec) in cols.iter().enumerate() {
let mut sum = self.field.zero();
for i in 0..m {
let a = self.field.element(r_vec[i]);
let b = self.field.element(col_vec[m - 1 - i]); sum = sum.add(a.mul(b));
}
data[[row_idx, col_idx]] = sum.to_u32();
}
}
let strength = 2.min(factors as u32);
let params = OAParams::new(runs, factors, q, strength)?;
Ok(OA::new(data, params))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oa::verify_strength;
#[test]
fn test_rao_hamming_creation() {
let rh = RaoHamming::new(3, 2).unwrap();
assert_eq!(rh.levels(), 3);
assert_eq!(rh.runs(), 9);
assert_eq!(rh.max_factors(), 4); }
#[test]
fn test_rao_hamming_l9() {
let rh = RaoHamming::new(3, 2).unwrap();
let oa = rh.construct(4).unwrap();
assert_eq!(oa.runs(), 9);
assert_eq!(oa.factors(), 4);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"RaoHamming L9 should be valid: {:?}",
result.issues
);
}
#[test]
fn test_rao_hamming_l27() {
let rh = RaoHamming::new(3, 3).unwrap();
let oa = rh.construct(13).unwrap();
assert_eq!(oa.runs(), 27);
assert_eq!(oa.factors(), 13);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"RaoHamming L27 should be valid: {:?}",
result.issues
);
}
#[test]
fn test_rao_hamming_l8() {
let rh = RaoHamming::new(2, 3).unwrap();
let oa = rh.construct(7).unwrap();
assert_eq!(oa.runs(), 8);
assert_eq!(oa.factors(), 7);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"RaoHamming L8 should be valid: {:?}",
result.issues
);
}
#[test]
fn test_rao_hamming_too_many_factors() {
let rh = RaoHamming::new(3, 2).unwrap();
assert!(rh.construct(5).is_err());
}
}