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 Bose {
q: u32,
field: DynamicGf,
}
impl Bose {
#[must_use]
pub fn new(q: u32) -> Self {
Self::try_new(q).expect("q must be a prime power")
}
pub fn try_new(q: u32) -> Result<Self> {
if !is_prime_power(q) {
return Err(Error::LevelsNotPrimePower {
levels: q,
algorithm: "Bose",
});
}
let field = DynamicGf::new(q)?;
Ok(Self { q, field })
}
#[must_use]
pub fn levels(&self) -> u32 {
self.q
}
#[must_use]
pub fn runs(&self) -> usize {
(self.q * self.q) as usize
}
#[must_use]
pub fn max_factors(&self) -> usize {
(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: "Bose",
});
}
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));
for i in 0..q {
for j in 0..q {
let row = (i * q + j) as usize;
data[[row, 0]] = j;
let tables = self.field.tables();
for c in 1..factors {
let c_val = (c as u32) % self.q;
let term = tables.mul(c_val, j);
data[[row, c]] = tables.add(i, term);
}
}
}
let strength = 2.min(factors as u32);
let params = OAParams::new(runs, factors, q, strength)?;
Ok(OA::new(data, params))
}
}
impl Constructor for Bose {
fn name(&self) -> &'static str {
"Bose"
}
fn family(&self) -> &'static str {
"OA(q², k, q, 2), k ≤ q+1"
}
fn levels(&self) -> u32 {
self.q
}
fn strength(&self) -> u32 {
2
}
fn runs(&self) -> usize {
(self.q * self.q) as usize
}
fn max_factors(&self) -> usize {
(self.q + 1) as usize
}
fn construct(&self, factors: usize) -> Result<OA> {
Bose::construct(self, factors)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oa::verify_strength;
#[test]
fn test_bose_creation() {
let bose = Bose::new(3);
assert_eq!(bose.levels(), 3);
assert_eq!(bose.runs(), 9);
assert_eq!(bose.max_factors(), 4);
}
#[test]
fn test_bose_invalid() {
assert!(Bose::try_new(6).is_err());
assert!(Bose::try_new(10).is_err());
}
#[test]
fn test_bose_construct_l9() {
let bose = Bose::new(3);
let oa = bose.construct(4).unwrap();
assert_eq!(oa.runs(), 9);
assert_eq!(oa.factors(), 4);
assert_eq!(oa.levels(), 3);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(result.is_valid, "L9 should be valid: {:?}", result.issues);
}
#[test]
fn test_bose_construct_l4() {
let bose = Bose::new(2);
let oa = bose.construct(3).unwrap();
assert_eq!(oa.runs(), 4);
assert_eq!(oa.factors(), 3);
assert_eq!(oa.levels(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(result.is_valid, "L4 should be valid: {:?}", result.issues);
}
#[test]
fn test_bose_construct_l25() {
let bose = Bose::new(5);
let oa = bose.construct(6).unwrap();
assert_eq!(oa.runs(), 25);
assert_eq!(oa.factors(), 6);
assert_eq!(oa.levels(), 5);
let result = verify_strength(&oa, 2).unwrap();
assert!(result.is_valid, "L25 should be valid: {:?}", result.issues);
}
#[test]
fn test_bose_construct_l49() {
let bose = Bose::new(7);
let oa = bose.construct(8).unwrap();
assert_eq!(oa.runs(), 49);
assert_eq!(oa.factors(), 8);
assert_eq!(oa.levels(), 7);
let result = verify_strength(&oa, 2).unwrap();
assert!(result.is_valid, "L49 should be valid: {:?}", result.issues);
}
#[test]
fn test_bose_prime_power() {
let bose = Bose::new(4);
let oa = bose.construct(5).unwrap();
assert_eq!(oa.runs(), 16);
assert_eq!(oa.factors(), 5);
assert_eq!(oa.levels(), 4);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"L16(4-level) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_bose_too_many_factors() {
let bose = Bose::new(3);
assert!(bose.construct(5).is_err()); }
#[test]
fn test_bose_zero_factors() {
let bose = Bose::new(3);
assert!(bose.construct(0).is_err());
}
#[test]
fn test_bose_single_factor() {
let bose = Bose::new(3);
let oa = bose.construct(1).unwrap();
assert_eq!(oa.factors(), 1);
let mut counts = [0usize; 3];
for row in 0..9 {
counts[oa.get(row, 0) as usize] += 1;
}
assert_eq!(counts, [3, 3, 3]);
}
}