pounce_minima/
sampling.rs1use pounce_common::types::Number;
10use rand::distributions::{Distribution, Standard};
11use rand_chacha::ChaCha8Rng;
12use rand_chacha::rand_core::SeedableRng;
13
14pub struct Sampler {
16 rng: ChaCha8Rng,
17 sobol_seed: u32,
19 sobol_index: u32,
21 sobol: bool,
23}
24
25impl Sampler {
26 pub fn new(seed: u64, sobol: bool) -> Self {
27 Self {
28 rng: ChaCha8Rng::seed_from_u64(seed),
29 sobol_seed: (seed ^ (seed >> 32)) as u32,
31 sobol_index: 0,
32 sobol,
33 }
34 }
35
36 pub fn uniform(&mut self) -> f64 {
39 Standard.sample(&mut self.rng)
40 }
41
42 pub fn standard_normal(&mut self) -> f64 {
44 let u1: f64 = 1.0 - self.uniform();
46 let u2: f64 = self.uniform();
47 (-2.0 * u1.ln()).sqrt() * (std::f64::consts::TAU * u2).cos()
48 }
49
50 pub fn sample(
55 &mut self,
56 x0: &[Number],
57 lo: &[Number],
58 hi: &[Number],
59 has_box: bool,
60 jitter: f64,
61 ) -> Vec<Number> {
62 let n = x0.len();
63 if has_box {
64 let idx = self.sobol_index;
65 self.sobol_index += 1;
66 (0..n)
67 .map(|j| {
68 let u = if self.sobol {
69 let (block, dim) = (j as u32 / NUM_DIMS, j as u32 % NUM_DIMS);
77 sobol_burley::sample(idx, dim, block_seed(self.sobol_seed, block)) as f64
78 } else {
79 self.uniform()
80 };
81 lo[j] + (hi[j] - lo[j]) * u
82 })
83 .collect()
84 } else {
85 (0..n)
86 .map(|j| x0[j] + jitter * self.standard_normal())
87 .collect()
88 }
89 }
90
91 pub fn perturb(&mut self, anchor: &[Number], scale: &[Number]) -> Vec<Number> {
95 anchor
96 .iter()
97 .enumerate()
98 .map(|(j, &a)| {
99 let s = if scale.len() == 1 { scale[0] } else { scale[j] };
100 a + s * self.standard_normal()
101 })
102 .collect()
103 }
104}
105
106const NUM_DIMS: u32 = sobol_burley::NUM_DIMENSIONS;
108
109fn block_seed(seed: u32, block: u32) -> u32 {
113 if block == 0 {
114 return seed;
115 }
116 let mut z = ((u64::from(seed) << 32) | u64::from(block)).wrapping_add(0x9E37_79B9_7F4A_7C15);
117 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
118 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
119 (z ^ (z >> 31)) as u32
120}
121
122pub fn clip(x: &mut [Number], lo: &[Number], hi: &[Number], has_box: bool) {
124 if !has_box {
125 return;
126 }
127 for j in 0..x.len() {
128 x[j] = x[j].clamp(lo[j], hi[j]);
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::{NUM_DIMS, Sampler, block_seed};
135
136 #[test]
139 fn sobol_sampling_extends_past_256_dimensions() {
140 let n = 3 * NUM_DIMS as usize + 7;
141 let (lo, hi) = (vec![-1.0; n], vec![1.0; n]);
142 let mut s = Sampler::new(5, true);
143 for _ in 0..4 {
144 let x = s.sample(&vec![0.0; n], &lo, &hi, true, 1.0);
145 assert_eq!(x.len(), n);
146 assert!(x.iter().all(|v| (-1.0..=1.0).contains(v)));
147 let b = NUM_DIMS as usize;
148 assert_ne!(x[..b], x[b..2 * b], "block 1 repeats block 0");
149 }
150 assert_eq!(block_seed(42, 0), 42);
151 assert_ne!(block_seed(42, 1), block_seed(42, 2));
152 }
153
154 #[test]
156 fn the_first_256_dimensions_are_unchanged() {
157 let n = NUM_DIMS as usize;
158 let (lo, hi) = (vec![0.0; n], vec![1.0; n]);
159 let mut s = Sampler::new(9, true);
160 let x = s.sample(&vec![0.0; n], &lo, &hi, true, 1.0);
161 let seed = (9u64 ^ (9u64 >> 32)) as u32;
162 for (j, v) in x.iter().enumerate() {
163 assert_eq!(*v, sobol_burley::sample(0, j as u32, seed) as f64);
164 }
165 }
166}