use pounce_common::types::Number;
use rand::distributions::{Distribution, Standard};
use rand_chacha::ChaCha8Rng;
use rand_chacha::rand_core::SeedableRng;
pub struct Sampler {
rng: ChaCha8Rng,
sobol_seed: u32,
sobol_index: u32,
sobol: bool,
}
impl Sampler {
pub fn new(seed: u64, sobol: bool) -> Self {
Self {
rng: ChaCha8Rng::seed_from_u64(seed),
sobol_seed: (seed ^ (seed >> 32)) as u32,
sobol_index: 0,
sobol,
}
}
pub fn uniform(&mut self) -> f64 {
Standard.sample(&mut self.rng)
}
pub fn standard_normal(&mut self) -> f64 {
let u1: f64 = 1.0 - self.uniform();
let u2: f64 = self.uniform();
(-2.0 * u1.ln()).sqrt() * (std::f64::consts::TAU * u2).cos()
}
pub fn sample(
&mut self,
x0: &[Number],
lo: &[Number],
hi: &[Number],
has_box: bool,
jitter: f64,
) -> Vec<Number> {
let n = x0.len();
if has_box {
let idx = self.sobol_index;
self.sobol_index += 1;
(0..n)
.map(|j| {
let u = if self.sobol {
let (block, dim) = (j as u32 / NUM_DIMS, j as u32 % NUM_DIMS);
sobol_burley::sample(idx, dim, block_seed(self.sobol_seed, block)) as f64
} else {
self.uniform()
};
lo[j] + (hi[j] - lo[j]) * u
})
.collect()
} else {
(0..n)
.map(|j| x0[j] + jitter * self.standard_normal())
.collect()
}
}
pub fn perturb(&mut self, anchor: &[Number], scale: &[Number]) -> Vec<Number> {
anchor
.iter()
.enumerate()
.map(|(j, &a)| {
let s = if scale.len() == 1 { scale[0] } else { scale[j] };
a + s * self.standard_normal()
})
.collect()
}
}
const NUM_DIMS: u32 = sobol_burley::NUM_DIMENSIONS;
fn block_seed(seed: u32, block: u32) -> u32 {
if block == 0 {
return seed;
}
let mut z = ((u64::from(seed) << 32) | u64::from(block)).wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
(z ^ (z >> 31)) as u32
}
pub fn clip(x: &mut [Number], lo: &[Number], hi: &[Number], has_box: bool) {
if !has_box {
return;
}
for j in 0..x.len() {
x[j] = x[j].clamp(lo[j], hi[j]);
}
}
#[cfg(test)]
mod tests {
use super::{NUM_DIMS, Sampler, block_seed};
#[test]
fn sobol_sampling_extends_past_256_dimensions() {
let n = 3 * NUM_DIMS as usize + 7;
let (lo, hi) = (vec![-1.0; n], vec![1.0; n]);
let mut s = Sampler::new(5, true);
for _ in 0..4 {
let x = s.sample(&vec![0.0; n], &lo, &hi, true, 1.0);
assert_eq!(x.len(), n);
assert!(x.iter().all(|v| (-1.0..=1.0).contains(v)));
let b = NUM_DIMS as usize;
assert_ne!(x[..b], x[b..2 * b], "block 1 repeats block 0");
}
assert_eq!(block_seed(42, 0), 42);
assert_ne!(block_seed(42, 1), block_seed(42, 2));
}
#[test]
fn the_first_256_dimensions_are_unchanged() {
let n = NUM_DIMS as usize;
let (lo, hi) = (vec![0.0; n], vec![1.0; n]);
let mut s = Sampler::new(9, true);
let x = s.sample(&vec![0.0; n], &lo, &hi, true, 1.0);
let seed = (9u64 ^ (9u64 >> 32)) as u32;
for (j, v) in x.iter().enumerate() {
assert_eq!(*v, sobol_burley::sample(0, j as u32, seed) as f64);
}
}
}