use crate::oa::{OAConstructor, OAResult, OA};
use crate::utils::{Integer, OarsError, OarsResult};
use ndarray::Array2;
use num::pow;
use oars_proc_macro::Checked;
use primes::is_prime;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
#[cfg(feature = "parallel")]
use crate::oa::ParOAConstructor;
#[cfg(feature = "parallel")]
use ndarray::{concatenate, Axis};
impl<T: Integer> BoseChecked<T> {
pub fn verify(self) -> OarsResult<Bose<T>> {
if self.dimensions < T::from(2).unwrap()
|| self.dimensions > self.prime_base + T::from(1).unwrap()
{
return Err(OarsError::InvalidParams("Invalid dimensions".into()));
}
if !is_prime(self.prime_base.to_u64().unwrap()) {
return Err(OarsError::InvalidParams("Base is not prime".into()));
}
Ok(Bose {
prime_base: self.prime_base,
dimensions: self.dimensions,
})
}
}
#[derive(Checked)]
pub struct Bose<T: Integer> {
pub prime_base: T,
pub dimensions: T,
}
impl<T: Integer> OAConstructor<T> for Bose<T> {
fn gen(&self) -> OAResult<T> {
let n = pow(self.prime_base, 2);
let mut points =
Array2::<T>::zeros((n.to_usize().unwrap(), self.dimensions.to_usize().unwrap()));
for i in 0..n.to_usize().unwrap() {
points[[i as usize, 0]] = T::from(i).unwrap() / self.prime_base;
points[[i as usize, 1]] = T::from(i).unwrap() % self.prime_base;
}
for i in 0..n.to_usize().unwrap() {
for j in 2..self.dimensions.to_usize().unwrap() {
points[[i, j]] = (points[[i, 0]]
+ T::from(j - 1).unwrap() * points[[i as usize, 1]])
% self.prime_base;
}
}
Ok(OA {
strength: T::from(2).unwrap(),
levels: self.prime_base,
factors: self.dimensions,
index: T::from(1).unwrap(),
points,
})
}
}
#[cfg(feature = "parallel")]
impl<T: Integer> ParOAConstructor<T> for Bose<T> {
fn gen_par(&self) -> OAResult<T> {
let n = pow(self.prime_base, 2);
let mut initial_points = Array2::<T>::zeros((n.to_usize().unwrap(), 2));
let mut points = Array2::<T>::zeros((
n.to_usize().unwrap(),
self.dimensions.to_usize().unwrap() - 2,
));
initial_points
.axis_iter_mut(Axis(1))
.into_par_iter()
.enumerate()
.for_each(|(col_idx, mut col)| {
col.axis_iter_mut(Axis(0))
.into_iter()
.enumerate()
.for_each(|(row_idx, mut row)| match col_idx {
0 => row[[row_idx; 0]] = T::from(row_idx).unwrap() / self.prime_base,
1 => row[[row_idx; 0]] = T::from(row_idx).unwrap() % self.prime_base,
_ => panic!("A column besides 0 or 1 was reached, which is impossible"),
})
});
points
.axis_iter_mut(Axis(1))
.into_par_iter()
.enumerate()
.for_each(|(col_idx, mut col)| {
col.axis_iter_mut(Axis(0))
.into_iter()
.enumerate()
.for_each(|(row_idx, mut row)| {
row[[row_idx; 0]] = (initial_points[[row_idx, 0]]
+ T::from(col_idx + 1).unwrap() * initial_points[[row_idx, 1]])
% self.prime_base;
})
});
let points: Array2<T> = concatenate(Axis(1), &[initial_points.view(), points.view()])?;
Ok(OA {
strength: T::from(2).unwrap(),
levels: self.prime_base,
factors: self.dimensions,
index: T::from(1).unwrap(),
points,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::arr2;
#[test]
fn bose_init_2() {
let bose = Bose {
prime_base: 2,
dimensions: 2,
};
let oa = bose.gen().unwrap();
let ground_truth = arr2(&[[0, 0], [0, 1], [1, 0], [1, 1]]);
assert!(oa.points == ground_truth);
}
#[test]
#[cfg(feature = "parallel")]
fn bose_par_init_2() {
let bose = Bose {
prime_base: 2,
dimensions: 2,
};
let oa = bose.gen_par().unwrap();
let ground_truth = arr2(&[[0, 0], [0, 1], [1, 0], [1, 1]]);
assert!(oa.points == ground_truth);
}
#[test]
fn bose_init_3() {
let bose = Bose {
prime_base: 3,
dimensions: 3,
};
let oa = bose.gen().unwrap();
let ground_truth = arr2(&[
[0, 0, 0],
[0, 1, 1],
[0, 2, 2],
[1, 0, 1],
[1, 1, 2],
[1, 2, 0],
[2, 0, 2],
[2, 1, 0],
[2, 2, 1],
]);
assert!(oa.points == ground_truth);
}
}