use core::fmt;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SamplerAlgorithm {
SplitMix64V1,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SamplerState {
pub algorithm: SamplerAlgorithm,
pub state: u64,
pub words_generated: u64,
pub seed: u64,
pub stream: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SamplerReceipt {
pub state: SamplerState,
pub max_words: Option<u64>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SeededSampler {
state: SamplerState,
max_words: Option<u64>,
}
impl SeededSampler {
pub fn new(seed: u64) -> Self {
Self::from_parts(seed, 0, seed, None)
}
pub fn with_max_words(seed: u64, max_words: u64) -> Self {
Self::from_parts(seed, 0, seed, Some(max_words))
}
fn from_parts(seed: u64, stream: u64, state: u64, max_words: Option<u64>) -> Self {
Self {
state: SamplerState {
algorithm: SamplerAlgorithm::SplitMix64V1,
state,
words_generated: 0,
seed,
stream,
},
max_words,
}
}
pub fn replay(receipt: SamplerReceipt) -> Self {
Self {
state: receipt.state,
max_words: receipt.max_words,
}
}
pub fn receipt(&self) -> SamplerReceipt {
SamplerReceipt {
state: self.state,
max_words: self.max_words,
}
}
pub fn fork(&self, label: u64) -> Self {
let state = mix(self.state.seed
^ label.wrapping_mul(0xd2b7_4407_b1ce_6e93)
^ 0xa076_1d64_78bd_642f);
Self::from_parts(self.state.seed, label, state, self.max_words)
}
pub fn try_next_u64(&mut self) -> Result<u64, DesignError> {
if self
.max_words
.is_some_and(|limit| self.state.words_generated >= limit)
{
return Err(DesignError::WorkLimit {
required: self.state.words_generated.saturating_add(1),
limit: self.max_words.unwrap_or(0),
});
}
self.state.state = self.state.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
self.state.words_generated += 1;
Ok(mix(self.state.state))
}
pub(crate) fn next_u64(&mut self) -> u64 {
self.try_next_u64()
.expect("unbounded or preflighted sampler")
}
pub fn unit_interval(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 * (1.0 / ((1_u64 << 53) as f64))
}
pub(crate) fn index_modulo(&mut self, length: usize) -> usize {
(self.next_u64() % length as u64) as usize
}
pub(crate) fn index_multiply_high(&mut self, length: usize) -> usize {
((u128::from(self.next_u64()) * length as u128) >> 64) as usize
}
}
fn mix(mut value: u64) -> u64 {
value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
value ^ (value >> 31)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Scramble {
None,
DigitalShift,
}
#[derive(Clone, Debug, PartialEq)]
pub struct UntestedRegion {
pub label: String,
pub reason: String,
}
#[derive(Clone, Debug, PartialEq)]
pub struct CoverageEvidence {
pub sequence_identity: String,
pub boundary_injections: Vec<usize>,
pub duplicates: Vec<(usize, usize)>,
pub stratum_occupancy: Vec<Vec<usize>>,
pub sampler: Option<SamplerReceipt>,
pub untested_regions: Vec<UntestedRegion>,
pub work: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SampleDesign {
pub points: Vec<Vec<f64>>,
pub coverage: CoverageEvidence,
}
#[derive(Clone, Debug, PartialEq)]
pub struct LatinHypercubePlan {
pub dimensions: usize,
pub points: usize,
pub seed: u64,
pub max_work: u64,
pub untested_regions: Vec<UntestedRegion>,
}
impl LatinHypercubePlan {
pub fn generate(&self) -> Result<SampleDesign, DesignError> {
validate_shape(self.dimensions, self.points, 64)?;
let required = (self.dimensions as u64)
.checked_mul(self.points.saturating_sub(1) as u64)
.ok_or(DesignError::Overflow)?;
if required > self.max_work {
return Err(DesignError::WorkLimit {
required,
limit: self.max_work,
});
}
let mut sampler = SeededSampler::with_max_words(self.seed, self.max_work);
let mut points = vec![vec![0.0; self.dimensions]; self.points];
let occupancy = vec![vec![1; self.points]; self.dimensions];
for (dimension, _) in occupancy.iter().enumerate() {
let mut permutation = (0..self.points).collect::<Vec<_>>();
for end in (1..self.points).rev() {
let chosen = sampler.index_modulo(end + 1);
permutation.swap(end, chosen);
}
for (row, stratum) in permutation.into_iter().enumerate() {
points[row][dimension] = (stratum as f64 + 0.5) / self.points as f64;
}
}
Ok(design(
points,
format!("latin-hypercube/centered-v1;seed={}", self.seed),
vec![],
occupancy,
Some(sampler.receipt()),
self.untested_regions.clone(),
required,
))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SobolPlan {
pub dimensions: usize,
pub points: usize,
pub skip: u64,
pub scramble: Scramble,
pub seed: u64,
pub max_work: u64,
pub untested_regions: Vec<UntestedRegion>,
}
impl SobolPlan {
pub fn generate(&self) -> Result<SampleDesign, DesignError> {
validate_shape(self.dimensions, self.points, 4)?;
let end = self
.skip
.checked_add(self.points as u64)
.ok_or(DesignError::Overflow)?;
if end > u32::MAX as u64 {
return Err(DesignError::UnsupportedPoint { point: end });
}
let required = (self.dimensions as u64)
.checked_mul(self.points as u64)
.ok_or(DesignError::Overflow)?;
if required > self.max_work {
return Err(DesignError::WorkLimit {
required,
limit: self.max_work,
});
}
let mut sampler = SeededSampler::new(self.seed);
let shifts = (0..self.dimensions)
.map(|_| {
if self.scramble == Scramble::DigitalShift {
sampler.next_u64()
} else {
0
}
})
.collect::<Vec<_>>();
let mut points = Vec::with_capacity(self.points);
for index in self.skip..end {
let gray = index ^ (index >> 1);
let mut row = Vec::with_capacity(self.dimensions);
for (dimension, shift) in shifts.iter().copied().enumerate() {
let mut bits = 0_u64;
for bit in 0..32 {
if gray & (1_u64 << bit) != 0 {
bits ^= direction(dimension, bit);
}
}
row.push(((bits ^ shift) >> 11) as f64 * (1.0 / ((1_u64 << 53) as f64)));
}
points.push(row);
}
let receipt = (self.scramble == Scramble::DigitalShift).then(|| sampler.receipt());
Ok(design(
points,
format!(
"sobol/joe-kuo-reviewed-4d-v1;skip={};scramble={:?};seed={}",
self.skip, self.scramble, self.seed
),
vec![],
vec![],
receipt,
self.untested_regions.clone(),
required,
))
}
}
fn direction(dimension: usize, bit: usize) -> u64 {
if dimension == 0 {
return 1_u64 << (63 - bit);
}
let (s, a, initial): (usize, u32, &[u32]) = match dimension {
1 => (1, 0, &[1]),
2 => (2, 1, &[1, 3]),
3 => (3, 1, &[1, 3, 1]),
_ => unreachable!(),
};
let mut values = [0_u64; 32];
for index in 0..s {
values[index] = (initial[index] as u64) << (63 - index);
}
for index in s..=bit {
let mut value = values[index - s] ^ (values[index - s] >> s);
for k in 1..s {
if ((a >> (s - 1 - k)) & 1) != 0 {
value ^= values[index - k];
}
}
values[index] = value;
}
values[bit]
}
#[derive(Clone, Debug, PartialEq)]
pub struct SweepPlan {
pub inject_lower_boundary: bool,
pub inject_upper_boundary: bool,
pub untested_regions: Vec<UntestedRegion>,
}
impl SweepPlan {
pub fn apply(&self, mut design: SampleDesign) -> SampleDesign {
let dimensions = design.points.first().map_or(0, Vec::len);
let mut boundaries = Vec::new();
if self.inject_lower_boundary {
design.points.insert(0, vec![0.0; dimensions]);
boundaries.push(0);
}
if self.inject_upper_boundary {
boundaries.push(design.points.len());
design.points.push(vec![1.0; dimensions]);
}
design.coverage.boundary_injections = boundaries;
design.coverage.duplicates = duplicates(&design.points);
design
.coverage
.untested_regions
.extend(self.untested_regions.clone());
design
.coverage
.sequence_identity
.push_str(";sweep-boundaries-v1");
design
}
}
fn validate_shape(
dimensions: usize,
points: usize,
max_dimensions: usize,
) -> Result<(), DesignError> {
if dimensions == 0 || dimensions > max_dimensions {
return Err(DesignError::UnsupportedDimension {
requested: dimensions,
maximum: max_dimensions,
});
}
if points == 0 {
return Err(DesignError::InvalidPointCount);
}
Ok(())
}
fn design(
points: Vec<Vec<f64>>,
identity: String,
boundaries: Vec<usize>,
occupancy: Vec<Vec<usize>>,
sampler: Option<SamplerReceipt>,
untested_regions: Vec<UntestedRegion>,
work: u64,
) -> SampleDesign {
let duplicates = duplicates(&points);
SampleDesign {
points,
coverage: CoverageEvidence {
sequence_identity: identity,
boundary_injections: boundaries,
duplicates,
stratum_occupancy: occupancy,
sampler,
untested_regions,
work,
},
}
}
fn duplicates(points: &[Vec<f64>]) -> Vec<(usize, usize)> {
let mut result = Vec::new();
for later in 0..points.len() {
if let Some(earlier) = (0..later).find(|&earlier| points[earlier] == points[later]) {
result.push((later, earlier));
}
}
result
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DesignError {
UnsupportedDimension {
requested: usize,
maximum: usize,
},
InvalidPointCount,
UnsupportedPoint {
point: u64,
},
WorkLimit {
required: u64,
limit: u64,
},
Overflow,
}
impl fmt::Display for DesignError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for DesignError {}