use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
pub fn lhs(n: usize, d: usize, seed: u64) -> Vec<Vec<f64>> {
let mut rng = StdRng::seed_from_u64(seed);
let mut perms: Vec<Vec<usize>> = (0..d)
.map(|_| {
let mut p: Vec<usize> = (0..n).collect();
for i in (1..n).rev() {
let j = rng.gen_range(0..=i);
p.swap(i, j);
}
p
})
.collect();
let inv_n = 1.0 / (n as f64);
let mut samples = Vec::with_capacity(n);
for i in 0..n {
let mut x = Vec::with_capacity(d);
for dim in 0..d {
let u: f64 = rng.gen_range(0.0..1.0);
let stratum = perms[dim][i] as f64;
x.push((stratum + u) * inv_n);
}
samples.push(x);
}
let _ = &mut perms;
samples
}
pub fn halton(n: usize, d: usize, skip: usize) -> Vec<Vec<f64>> {
let primes = first_primes(d.max(1));
(0..n)
.map(|i| {
primes
.iter()
.take(d)
.map(|&b| van_der_corput(i + skip + 1, b))
.collect()
})
.collect()
}
pub fn sobol(n: usize, d: usize, skip: usize) -> Vec<Vec<f64>> {
halton(n, d, skip)
}
fn van_der_corput(mut i: usize, base: usize) -> f64 {
let mut f = 1.0 / base as f64;
let mut r = 0.0;
while i > 0 {
r += f * (i % base) as f64;
i /= base;
f /= base as f64;
}
r
}
fn first_primes(n: usize) -> Vec<usize> {
let table = [
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,
97, 101, 103, 107, 109, 113,
];
table.iter().copied().take(n.max(1)).collect()
}
#[allow(dead_code)]
const SOBOL_DIRECTION_NUMBERS: &[(u32, u32, &[u32])] = &[
(1, 0, &[]),
(2, 1, &[1]),
(3, 1, &[1, 3]),
(3, 2, &[1, 3, 1]),
(4, 1, &[1, 1, 1]),
(4, 4, &[1, 1, 3, 3]),
(5, 2, &[1, 3, 5, 13]),
(5, 4, &[1, 1, 5, 5]),
(5, 7, &[1, 3, 5, 5]),
(5, 11, &[1, 1, 7, 11]),
(5, 13, &[1, 1, 5, 7]),
(5, 14, &[1, 3, 7, 11]),
(6, 1, &[1, 3, 3, 5, 1]),
(6, 13, &[1, 1, 3, 5, 5]),
(6, 16, &[1, 1, 7, 13, 25]),
(6, 19, &[1, 1, 1, 7, 9]),
];
#[allow(dead_code)]
fn sobol_directions(d: usize) -> Vec<[u64; 32]> {
let mut dir: Vec<[u64; 32]> = Vec::with_capacity(d);
{
let mut col = [0u64; 32];
for i in 0..32 {
col[i] = 1u64 << (31 - i);
}
dir.push(col);
}
for k in 1..d {
let (deg, a, m_init) = SOBOL_DIRECTION_NUMBERS[k];
let mut m: Vec<u64> = m_init.iter().map(|&v| v as u64).collect();
while m.len() < 32 {
let i = m.len();
let mut v = m[i - deg as usize];
v ^= v << deg;
for j in 1..(deg as usize) {
let bit = (a >> (deg as usize - 1 - j)) & 1;
if bit == 1 {
v ^= (1u64 << j) * m[i - j];
}
}
m.push(v);
}
let mut col = [0u64; 32];
for i in 0..32 {
col[i] = m[i] << (31 - i);
}
dir.push(col);
}
dir
}
#[allow(dead_code)]
fn lowest_zero_bit(i: u64) -> usize {
let mut c = 0usize;
let mut v = i;
while v & 1 != 0 {
v >>= 1;
c += 1;
}
c
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lhs_one_per_stratum() {
let n = 20;
let d = 3;
let pts = lhs(n, d, 42);
for dim in 0..d {
let mut bins = vec![0usize; n];
for p in &pts {
let b = ((p[dim] * n as f64).floor() as usize).min(n - 1);
bins[b] += 1;
}
for &b in &bins {
assert_eq!(b, 1, "LHS stratum violation at dim {dim}");
}
}
}
#[test]
fn halton_in_unit_cube() {
let pts = halton(64, 5, 100);
for p in &pts {
for &v in p {
assert!(v >= 0.0 && v < 1.0);
}
}
}
#[test]
fn sobol_in_unit_cube() {
let pts = sobol(64, 3, 0);
for p in &pts {
for &v in p {
assert!(v >= 0.0 && v <= 1.0);
}
}
}
}