use super::metadata::IndexFn;
use super::strategy::StrategyName;
pub mod antidiagonal;
pub mod diagonal;
pub mod extrema;
pub mod halton;
pub mod lex;
pub mod lhs;
pub mod prng;
pub mod reverse_lex;
pub mod shells;
pub mod shuffle;
pub mod sobol;
pub type MultiIndex = Vec<u64>;
#[derive(Debug, Clone, PartialEq)]
pub struct Tuple {
pub bindings: Vec<(String, TupleValue)>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TupleValue {
U64(u64),
I64(i64),
F64(f64),
Str(String),
Bool(bool),
}
impl Tuple {
pub fn new() -> Self {
Self { bindings: Vec::new() }
}
pub fn with<K: Into<String>>(mut self, key: K, value: TupleValue) -> Self {
self.bindings.push((key.into(), value));
self
}
}
impl Default for Tuple {
fn default() -> Self {
Self::new()
}
}
pub struct EvaluatedInput {
pub tuples: Vec<Tuple>,
pub cardinality: u64,
pub index_fn: IndexFn,
}
pub trait Strategy {
fn name(&self) -> StrategyName;
fn accepts_input(&self, idx: Option<&IndexFn>) -> bool;
fn has_closed_form_for(&self, idx: &IndexFn) -> bool;
fn apply(&self, input: &EvaluatedInput, truncation: Option<u64>) -> Vec<Tuple>;
}
pub fn for_name(name: StrategyName) -> Box<dyn Strategy + Send + Sync> {
match name {
StrategyName::Lex => Box::new(lex::Lex),
StrategyName::ReverseLex => Box::new(reverse_lex::ReverseLex),
StrategyName::Shuffle => Box::new(shuffle::Shuffle),
StrategyName::Halton => Box::new(halton::Halton),
StrategyName::Sobol => Box::new(sobol::Sobol),
StrategyName::Lhs => Box::new(lhs::Lhs),
StrategyName::Extrema => Box::new(extrema::Extrema),
StrategyName::Shells => Box::new(shells::Shells),
StrategyName::Diagonal => Box::new(diagonal::Diagonal),
StrategyName::Antidiagonal => Box::new(antidiagonal::Antidiagonal),
}
}
pub fn multi_index_to_flat(idx: &IndexFn, mi: &MultiIndex) -> Option<usize> {
match idx {
IndexFn::Lattice { axis_sizes } => {
if mi.len() != axis_sizes.len() {
return None;
}
let mut flat: u64 = 0;
let mut stride: u64 = 1;
for i in (0..axis_sizes.len()).rev() {
let pos = mi[i];
let size = axis_sizes[i];
if pos >= size {
return None;
}
flat = flat.checked_add(pos.checked_mul(stride)?)?;
stride = stride.checked_mul(size)?;
}
Some(flat as usize)
}
IndexFn::Lockstep { length } => {
if mi.len() != 1 || mi[0] >= *length {
return None;
}
Some(mi[0] as usize)
}
IndexFn::Modular { axis_sizes } => {
let max = axis_sizes.iter().copied().max().unwrap_or(0);
if mi.len() != 1 || mi[0] >= max {
return None;
}
Some(mi[0] as usize)
}
IndexFn::Concatenation { segment_sizes } => {
let total: u64 = segment_sizes.iter().copied().sum();
if mi.len() != 1 || mi[0] >= total {
return None;
}
Some(mi[0] as usize)
}
IndexFn::Continuous { .. } | IndexFn::Hybrid { .. } => None,
}
}
pub fn index_fn_supports_lookup(idx: &IndexFn) -> bool {
!matches!(idx, IndexFn::Continuous { .. } | IndexFn::Hybrid { .. })
}
pub(crate) fn index_fn_size(idx: &IndexFn) -> u64 {
match idx {
IndexFn::Lattice { axis_sizes } => {
axis_sizes.iter().copied().fold(1u64, |a, b| a.saturating_mul(b))
}
IndexFn::Lockstep { length } => *length,
IndexFn::Modular { axis_sizes } => {
axis_sizes.iter().copied().max().unwrap_or(0)
}
IndexFn::Concatenation { segment_sizes } => {
segment_sizes.iter().copied().fold(0u64, |a, b| a.saturating_add(b))
}
IndexFn::Continuous { .. } | IndexFn::Hybrid { .. } => 0,
}
}
pub(crate) fn index_fn_dim(idx: &IndexFn) -> usize {
match idx {
IndexFn::Lattice { axis_sizes } => axis_sizes.len(),
IndexFn::Continuous { intervals, .. } => intervals.len(),
IndexFn::Hybrid {
discrete_axes,
continuous_axes,
..
} => discrete_axes.len() + continuous_axes.len(),
IndexFn::Lockstep { .. } | IndexFn::Modular { .. } => 1,
IndexFn::Concatenation { segment_sizes } => segment_sizes.len(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn for_name_dispatches_to_correct_strategy() {
assert_eq!(for_name(StrategyName::Lex).name(), StrategyName::Lex);
assert_eq!(for_name(StrategyName::Halton).name(), StrategyName::Halton);
assert_eq!(for_name(StrategyName::Extrema).name(), StrategyName::Extrema);
}
#[test]
fn index_fn_size_lattice() {
let idx = IndexFn::Lattice { axis_sizes: vec![3, 4, 5] };
assert_eq!(index_fn_size(&idx), 60);
}
#[test]
fn index_fn_size_concatenation() {
let idx = IndexFn::Concatenation { segment_sizes: vec![10, 20, 30] };
assert_eq!(index_fn_size(&idx), 60);
}
#[test]
fn index_fn_dim_classifies_correctly() {
assert_eq!(index_fn_dim(&IndexFn::Lattice { axis_sizes: vec![3, 4] }), 2);
assert_eq!(index_fn_dim(&IndexFn::Lockstep { length: 10 }), 1);
assert_eq!(index_fn_dim(&IndexFn::Concatenation { segment_sizes: vec![1, 2, 3] }), 3);
}
}