use ndarray::Array2;
use super::Constructor;
use crate::error::{Error, Result};
use crate::oa::{OA, OAParams};
#[derive(Debug, Clone)]
pub struct HadamardSylvester {
n: usize,
_m: u32,
}
impl HadamardSylvester {
pub fn new(n: usize) -> Result<Self> {
if n == 0 || (n & (n - 1)) != 0 {
return Err(Error::invalid_params(format!(
"HadamardSylvester requires n to be a power of 2, got {n}"
)));
}
if n < 4 {
return Err(Error::invalid_params(
"HadamardSylvester requires n >= 4 for a valid OA",
));
}
let m = n.trailing_zeros();
Ok(Self { n, _m: m })
}
pub fn for_factors(factors: usize) -> Result<Self> {
if factors == 0 {
return Err(Error::invalid_params("factors must be at least 1"));
}
let mut n = 4;
while n - 1 < factors {
n *= 2;
if n > 1 << 24 {
return Err(Error::invalid_params(format!(
"Cannot create Hadamard OA for {factors} factors (would require too many runs)"
)));
}
}
Self::new(n)
}
fn build_hadamard_matrix(&self) -> Array2<i8> {
let n = self.n;
let mut h = Array2::from_elem((n, n), 1i8);
let mut size = 1;
while size < n {
for i in 0..size {
for j in 0..size {
h[[i, j + size]] = h[[i, j]];
}
}
for i in 0..size {
for j in 0..size {
h[[i + size, j]] = h[[i, j]];
}
}
for i in 0..size {
for j in 0..size {
h[[i + size, j + size]] = -h[[i, j]];
}
}
size *= 2;
}
h
}
#[must_use]
pub fn order(&self) -> usize {
self.n
}
#[must_use]
pub fn runs(&self) -> usize {
self.n
}
#[must_use]
pub fn max_factors(&self) -> usize {
self.n - 1
}
pub fn construct(&self, factors: usize) -> Result<OA> {
let max = self.max_factors();
if factors > max {
return Err(Error::TooManyFactors {
factors,
max,
algorithm: "HadamardSylvester",
});
}
if factors == 0 {
return Err(Error::invalid_params("factors must be at least 1"));
}
let h = self.build_hadamard_matrix();
let n = self.n;
let mut data = Array2::zeros((n, factors));
for i in 0..n {
for j in 0..factors {
data[[i, j]] = if h[[i, j + 1]] == 1 { 0 } else { 1 };
}
}
let strength = 2.min(factors as u32);
let params = OAParams::new(n, factors, 2, strength)?;
Ok(OA::new(data, params))
}
}
impl Constructor for HadamardSylvester {
fn name(&self) -> &'static str {
"HadamardSylvester"
}
fn family(&self) -> &'static str {
"OA(2^m, 2^m-1, 2, 2), m ≥ 2"
}
fn levels(&self) -> u32 {
2
}
fn strength(&self) -> u32 {
2
}
fn runs(&self) -> usize {
self.n
}
fn max_factors(&self) -> usize {
self.n - 1
}
fn construct(&self, factors: usize) -> Result<OA> {
HadamardSylvester::construct(self, factors)
}
}
#[derive(Debug, Clone)]
pub struct HadamardPaley {
p: u32,
n: usize,
}
impl HadamardPaley {
pub fn new(p: u32) -> Result<Self> {
if !crate::utils::is_prime(p) {
return Err(Error::invalid_params(format!(
"HadamardPaley requires a prime, but {p} is not prime"
)));
}
if p % 4 != 3 {
return Err(Error::invalid_params(format!(
"HadamardPaley requires p ≡ 3 (mod 4), but {p} ≡ {} (mod 4)",
p % 4
)));
}
let n = (p + 1) as usize;
Ok(Self { p, n })
}
pub fn for_factors(factors: usize) -> Result<Self> {
if factors == 0 {
return Err(Error::invalid_params("factors must be at least 1"));
}
let mut candidate = factors as u32;
let rem = candidate % 4;
if rem != 3 {
candidate += (3 + 4 - rem) % 4;
if candidate < factors as u32 {
candidate += 4;
}
}
while candidate < (1 << 24) {
if crate::utils::is_prime(candidate) && candidate % 4 == 3 {
return Self::new(candidate);
}
candidate += 4; }
Err(Error::invalid_params(format!(
"Cannot find suitable prime for {factors} factors"
)))
}
fn legendre(&self, a: u32) -> i8 {
let a = a % self.p;
if a == 0 {
return 0;
}
let exp = (self.p - 1) / 2;
let result = crate::utils::mod_pow(u64::from(a), u64::from(exp), u64::from(self.p));
if result == 1 {
1
} else {
-1 }
}
fn build_hadamard_matrix(&self) -> Array2<i8> {
let n = self.n;
let p = self.p;
let mut h = Array2::from_elem((n, n), 1i8);
for i in 1..n {
h[[i, 0]] = -1;
}
for i in 1..n {
for j in 1..n {
let fi = (i - 1) as u32; let fj = (j - 1) as u32;
if fi == fj {
h[[i, j]] = 1;
} else {
let diff = (fj + p - fi) % p;
h[[i, j]] = self.legendre(diff);
}
}
}
for i in 0..n {
if h[[i, 0]] == -1 {
for j in 0..n {
h[[i, j]] = -h[[i, j]];
}
}
}
for j in 0..n {
if h[[0, j]] == -1 {
for i in 0..n {
h[[i, j]] = -h[[i, j]];
}
}
}
h
}
#[must_use]
pub fn prime(&self) -> u32 {
self.p
}
#[must_use]
pub fn order(&self) -> usize {
self.n
}
#[must_use]
pub fn runs(&self) -> usize {
self.n
}
#[must_use]
pub fn max_factors(&self) -> usize {
self.n - 1
}
pub fn construct(&self, factors: usize) -> Result<OA> {
let max = self.max_factors();
if factors > max {
return Err(Error::TooManyFactors {
factors,
max,
algorithm: "HadamardPaley",
});
}
if factors == 0 {
return Err(Error::invalid_params("factors must be at least 1"));
}
let h = self.build_hadamard_matrix();
let n = self.n;
let mut data = Array2::zeros((n, factors));
for i in 0..n {
for j in 0..factors {
data[[i, j]] = if h[[i, j + 1]] == 1 { 0 } else { 1 };
}
}
let strength = 2.min(factors as u32);
let params = OAParams::new(n, factors, 2, strength)?;
Ok(OA::new(data, params))
}
}
impl Constructor for HadamardPaley {
fn name(&self) -> &'static str {
"HadamardPaley"
}
fn family(&self) -> &'static str {
"OA(p+1, p, 2, 2), p ≡ 3 (mod 4) prime"
}
fn levels(&self) -> u32 {
2
}
fn strength(&self) -> u32 {
2
}
fn runs(&self) -> usize {
self.n
}
fn max_factors(&self) -> usize {
self.n - 1
}
fn construct(&self, factors: usize) -> Result<OA> {
HadamardPaley::construct(self, factors)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oa::verify_strength;
#[test]
fn test_hadamard_sylvester_creation() {
assert!(HadamardSylvester::new(4).is_ok());
assert!(HadamardSylvester::new(8).is_ok());
assert!(HadamardSylvester::new(16).is_ok());
assert!(HadamardSylvester::new(32).is_ok());
assert!(HadamardSylvester::new(64).is_ok());
}
#[test]
fn test_hadamard_sylvester_invalid() {
assert!(HadamardSylvester::new(0).is_err());
assert!(HadamardSylvester::new(1).is_err());
assert!(HadamardSylvester::new(2).is_err()); assert!(HadamardSylvester::new(3).is_err()); assert!(HadamardSylvester::new(5).is_err());
assert!(HadamardSylvester::new(6).is_err());
assert!(HadamardSylvester::new(7).is_err());
assert!(HadamardSylvester::new(12).is_err());
}
#[test]
fn test_hadamard_for_factors() {
let h = HadamardSylvester::for_factors(3).unwrap();
assert_eq!(h.order(), 4);
let h = HadamardSylvester::for_factors(5).unwrap();
assert_eq!(h.order(), 8);
let h = HadamardSylvester::for_factors(7).unwrap();
assert_eq!(h.order(), 8);
let h = HadamardSylvester::for_factors(8).unwrap();
assert_eq!(h.order(), 16); }
#[test]
fn test_hadamard_sylvester_oa4() {
let h = HadamardSylvester::new(4).unwrap();
let oa = h.construct(3).unwrap();
assert_eq!(oa.runs(), 4);
assert_eq!(oa.factors(), 3);
assert_eq!(oa.levels(), 2);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(4,3,2,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_hadamard_sylvester_oa8() {
let h = HadamardSylvester::new(8).unwrap();
let oa = h.construct(7).unwrap();
assert_eq!(oa.runs(), 8);
assert_eq!(oa.factors(), 7);
assert_eq!(oa.levels(), 2);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(8,7,2,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_hadamard_sylvester_oa16() {
let h = HadamardSylvester::new(16).unwrap();
let oa = h.construct(15).unwrap();
assert_eq!(oa.runs(), 16);
assert_eq!(oa.factors(), 15);
assert_eq!(oa.levels(), 2);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(16,15,2,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_hadamard_sylvester_oa32() {
let h = HadamardSylvester::new(32).unwrap();
let oa = h.construct(31).unwrap();
assert_eq!(oa.runs(), 32);
assert_eq!(oa.factors(), 31);
assert_eq!(oa.levels(), 2);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(32,31,2,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_hadamard_sylvester_fewer_factors() {
let h = HadamardSylvester::new(8).unwrap();
for k in 1..=7 {
let oa = h.construct(k).unwrap();
assert_eq!(oa.factors(), k);
if k >= 2 {
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"OA(8,{},2,2) should be valid: {:?}",
k, result.issues
);
}
}
}
#[test]
fn test_hadamard_sylvester_too_many_factors() {
let h = HadamardSylvester::new(8).unwrap();
assert!(h.construct(8).is_err());
assert!(h.construct(10).is_err());
}
#[test]
fn test_hadamard_sylvester_single_factor() {
let h = HadamardSylvester::new(4).unwrap();
let oa = h.construct(1).unwrap();
assert_eq!(oa.factors(), 1);
assert_eq!(oa.runs(), 4);
let mut counts = [0usize; 2];
for row in 0..4 {
counts[oa.get(row, 0) as usize] += 1;
}
assert_eq!(counts, [2, 2]);
}
#[test]
fn test_hadamard_matrix_structure() {
let h = HadamardSylvester::new(4).unwrap();
let mat = h.build_hadamard_matrix();
for j in 0..4 {
assert_eq!(mat[[0, j]], 1, "First row should be all 1s");
}
for i in 0..4 {
assert_eq!(mat[[i, 0]], 1, "First column should be all 1s");
}
for i in 0..4 {
for j in 0..4 {
assert!(
mat[[i, j]] == 1 || mat[[i, j]] == -1,
"All entries should be ±1"
);
}
}
}
#[test]
fn test_hadamard_matrix_orthogonality() {
let h = HadamardSylvester::new(8).unwrap();
let mat = h.build_hadamard_matrix();
let n = 8i32;
for i in 0..8 {
for j in 0..8 {
let mut dot_product: i32 = 0;
for k in 0..8 {
dot_product += mat[[i, k]] as i32 * mat[[j, k]] as i32;
}
if i == j {
assert_eq!(dot_product, n, "Diagonal should be n");
} else {
assert_eq!(dot_product, 0, "Off-diagonal should be 0");
}
}
}
}
#[test]
fn test_hadamard_balance() {
let h = HadamardSylvester::new(8).unwrap();
let mat = h.build_hadamard_matrix();
for j in 1..8 {
let mut plus_count = 0;
let mut minus_count = 0;
for i in 0..8 {
if mat[[i, j]] == 1 {
plus_count += 1;
} else {
minus_count += 1;
}
}
assert_eq!(plus_count, 4, "Column {} should have 4 ones", j);
assert_eq!(minus_count, 4, "Column {} should have 4 minus-ones", j);
}
}
#[test]
fn test_hadamard_paley_creation() {
assert!(HadamardPaley::new(3).is_ok());
assert!(HadamardPaley::new(7).is_ok());
assert!(HadamardPaley::new(11).is_ok());
assert!(HadamardPaley::new(19).is_ok());
assert!(HadamardPaley::new(23).is_ok());
assert!(HadamardPaley::new(31).is_ok());
assert!(HadamardPaley::new(43).is_ok());
}
#[test]
fn test_hadamard_paley_invalid() {
assert!(HadamardPaley::new(9).is_err()); assert!(HadamardPaley::new(15).is_err()); assert!(HadamardPaley::new(21).is_err());
assert!(HadamardPaley::new(5).is_err()); assert!(HadamardPaley::new(13).is_err()); assert!(HadamardPaley::new(17).is_err()); assert!(HadamardPaley::new(29).is_err());
assert!(HadamardPaley::new(2).is_err());
}
#[test]
fn test_hadamard_paley_for_factors() {
let h = HadamardPaley::for_factors(3).unwrap();
assert_eq!(h.prime(), 3);
let h = HadamardPaley::for_factors(5).unwrap();
assert_eq!(h.prime(), 7);
let h = HadamardPaley::for_factors(10).unwrap();
assert_eq!(h.prime(), 11);
let h = HadamardPaley::for_factors(12).unwrap();
assert_eq!(h.prime(), 19); }
#[test]
fn test_hadamard_paley_oa4() {
let h = HadamardPaley::new(3).unwrap();
let oa = h.construct(3).unwrap();
assert_eq!(oa.runs(), 4);
assert_eq!(oa.factors(), 3);
assert_eq!(oa.levels(), 2);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"Paley OA(4,3,2,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_hadamard_paley_oa8() {
let h = HadamardPaley::new(7).unwrap();
let oa = h.construct(7).unwrap();
assert_eq!(oa.runs(), 8);
assert_eq!(oa.factors(), 7);
assert_eq!(oa.levels(), 2);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"Paley OA(8,7,2,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_hadamard_paley_oa12() {
let h = HadamardPaley::new(11).unwrap();
let oa = h.construct(11).unwrap();
assert_eq!(oa.runs(), 12);
assert_eq!(oa.factors(), 11);
assert_eq!(oa.levels(), 2);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"Paley OA(12,11,2,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_hadamard_paley_oa20() {
let h = HadamardPaley::new(19).unwrap();
let oa = h.construct(19).unwrap();
assert_eq!(oa.runs(), 20);
assert_eq!(oa.factors(), 19);
assert_eq!(oa.levels(), 2);
assert_eq!(oa.strength(), 2);
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"Paley OA(20,19,2,2) should be valid: {:?}",
result.issues
);
}
#[test]
fn test_hadamard_paley_fewer_factors() {
let h = HadamardPaley::new(11).unwrap();
for k in 1..=11 {
let oa = h.construct(k).unwrap();
assert_eq!(oa.factors(), k);
if k >= 2 {
let result = verify_strength(&oa, 2).unwrap();
assert!(
result.is_valid,
"Paley OA(12,{},2,2) should be valid: {:?}",
k, result.issues
);
}
}
}
#[test]
fn test_hadamard_paley_too_many_factors() {
let h = HadamardPaley::new(7).unwrap();
assert!(h.construct(8).is_err());
assert!(h.construct(10).is_err());
}
#[test]
fn test_hadamard_paley_matrix_orthogonality() {
let h = HadamardPaley::new(7).unwrap();
let mat = h.build_hadamard_matrix();
let n = 8i32;
for i in 0..8 {
for j in 0..8 {
let mut dot_product: i32 = 0;
for k in 0..8 {
dot_product += mat[[i, k]] as i32 * mat[[j, k]] as i32;
}
if i == j {
assert_eq!(dot_product, n, "Diagonal should be n");
} else {
assert_eq!(dot_product, 0, "Off-diagonal should be 0");
}
}
}
}
#[test]
fn test_hadamard_paley_balance() {
let h = HadamardPaley::new(7).unwrap();
let mat = h.build_hadamard_matrix();
for j in 1..8 {
let mut plus_count = 0;
let mut minus_count = 0;
for i in 0..8 {
if mat[[i, j]] == 1 {
plus_count += 1;
} else {
minus_count += 1;
}
}
assert_eq!(plus_count, 4, "Column {} should have 4 ones", j);
assert_eq!(minus_count, 4, "Column {} should have 4 minus-ones", j);
}
}
#[test]
fn test_legendre_symbol() {
let h = HadamardPaley::new(7).unwrap();
assert_eq!(h.legendre(0), 0);
assert_eq!(h.legendre(1), 1); assert_eq!(h.legendre(2), 1); assert_eq!(h.legendre(3), -1); assert_eq!(h.legendre(4), 1); assert_eq!(h.legendre(5), -1); assert_eq!(h.legendre(6), -1); }
}