const PRIMES: [u64; 20] = [
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
];
fn radical_inverse(mut index: u64, base: u64) -> f64 {
let mut result = 0.0_f64;
let mut denom = 1.0_f64;
while index > 0 {
denom *= base as f64;
result += (index % base) as f64 / denom;
index /= base;
}
result
}
pub fn halton_point(index: usize, d: usize) -> Vec<f64> {
assert!(
d <= PRIMES.len(),
"Halton supports at most {} dimensions",
PRIMES.len()
);
(0..d)
.map(|j| radical_inverse(index as u64, PRIMES[j]))
.collect()
}
pub fn halton_sequence(n: usize, d: usize) -> Vec<Vec<f64>> {
(1..=n).map(|i| halton_point(i, d)).collect()
}
const SOBOL_BITS: usize = 52;
const JOE_KUO_D2_D8: [(u32, u32, &[u32]); 7] = [
(1, 0, &[1]),
(2, 1, &[1, 1]),
(3, 1, &[1, 1, 1]),
(3, 2, &[1, 3, 1]),
(4, 1, &[1, 1, 3, 3]),
(4, 4, &[1, 3, 5, 13]),
(5, 2, &[1, 1, 5, 5, 17]),
];
fn build_direction_numbers(dim: usize) -> Vec<[u64; SOBOL_BITS]> {
let mut v = Vec::with_capacity(dim);
{
let mut row = [0u64; SOBOL_BITS];
for (i, slot) in row.iter_mut().enumerate() {
*slot = 1u64 << (SOBOL_BITS - 1 - i);
}
v.push(row);
}
for &(s, a, m_init) in JOE_KUO_D2_D8.iter().take(dim.saturating_sub(1)) {
let s = s as usize;
let mut row = [0u64; SOBOL_BITS];
for i in 0..s {
row[i] = (m_init[i] as u64) << (SOBOL_BITS - 1 - i);
}
for i in s..SOBOL_BITS {
let mut val = row[i - s] >> s;
val ^= row[i - s];
for k in 1..s {
if (a >> (s - 1 - k)) & 1 == 1 {
val ^= row[i - k];
}
}
row[i] = val;
}
v.push(row);
}
v
}
fn rightmost_zero(n: u64) -> usize {
(!n).trailing_zeros() as usize
}
#[derive(Debug, Clone)]
pub struct SobolGenerator {
dim: usize,
index: u64,
x: Vec<u64>,
v: Vec<[u64; SOBOL_BITS]>,
}
impl SobolGenerator {
pub fn new(dim: usize) -> Self {
assert!(dim > 0, "dimension must be >= 1");
assert!(
dim <= 8,
"Sobol supports at most 8 dimensions (embedded table)"
);
let v = build_direction_numbers(dim);
Self {
dim,
index: 0,
x: vec![0u64; dim],
v,
}
}
#[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Vec<f64> {
if self.index == 0 {
self.index = 1;
return vec![0.0; self.dim];
}
let c = rightmost_zero(self.index - 1);
for j in 0..self.dim {
self.x[j] ^= self.v[j][c.min(SOBOL_BITS - 1)];
}
self.index += 1;
let scale = (1u64 << SOBOL_BITS) as f64;
self.x.iter().map(|&xi| xi as f64 / scale).collect()
}
pub fn skip(&mut self, n: u64) {
for _ in 0..n {
if self.index == 0 {
self.index = 1;
continue;
}
let c = rightmost_zero(self.index - 1);
for j in 0..self.dim {
self.x[j] ^= self.v[j][c.min(SOBOL_BITS - 1)];
}
self.index += 1;
}
}
pub fn index(&self) -> u64 {
self.index
}
}
pub fn sobol_sequence(n: usize, d: usize) -> Vec<Vec<f64>> {
let mut gen = SobolGenerator::new(d);
gen.skip(1); (0..n).map(|_| gen.next()).collect()
}
fn owen_scramble(mut x: u64, dim: u64, seed: u64) -> u64 {
for i in 0..SOBOL_BITS {
let bit_mask = 1u64 << (SOBOL_BITS - 1 - i);
let prefix = x >> (SOBOL_BITS - i);
let mut h = seed
.wrapping_mul(0x9E37_79B9_7F4A_7C15)
.wrapping_add(dim)
.wrapping_mul(0x517C_C1B7_2722_0A95)
.wrapping_add(prefix)
.wrapping_mul(0x6C62_272E_07BB_0142);
h ^= h >> 32;
h = h.wrapping_mul(0xBF58_476D_1CE4_E5B9);
h ^= h >> 31;
if h & 1 == 1 {
x ^= bit_mask;
}
}
x
}
pub fn sobol_scrambled(n: usize, d: usize, seed: u64) -> Vec<Vec<f64>> {
let mut gen = SobolGenerator::new(d);
gen.skip(1); let scale = (1u64 << SOBOL_BITS) as f64;
(0..n)
.map(|_| {
if gen.index == 0 {
gen.index = 1;
} else {
let c = rightmost_zero(gen.index - 1);
for j in 0..gen.dim {
gen.x[j] ^= gen.v[j][c.min(SOBOL_BITS - 1)];
}
gen.index += 1;
}
(0..d)
.map(|j| {
let scrambled = owen_scramble(gen.x[j], j as u64, seed);
scrambled as f64 / scale
})
.collect()
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn halton_base2_known_values() {
let pts = halton_sequence(4, 1);
let expected = [0.5, 0.25, 0.75, 0.125];
for (p, &e) in pts.iter().zip(&expected) {
assert!((p[0] - e).abs() < 1e-12, "got {} expected {}", p[0], e);
}
}
#[test]
fn halton_base3_known_values() {
let pts = halton_sequence(4, 2);
let expected_d1 = [1.0 / 3.0, 2.0 / 3.0, 1.0 / 9.0, 4.0 / 9.0];
for (p, &e) in pts.iter().zip(&expected_d1) {
assert!((p[1] - e).abs() < 1e-12, "got {} expected {}", p[1], e);
}
}
#[test]
fn halton_in_unit_cube() {
let pts = halton_sequence(1000, 5);
for p in &pts {
assert_eq!(p.len(), 5);
for &x in p {
assert!((0.0..1.0).contains(&x), "out of range: {x}");
}
}
}
#[test]
#[should_panic(expected = "at most 20 dimensions")]
fn halton_rejects_high_dim() {
halton_point(1, 21);
}
#[test]
fn sobol_1d_known_values() {
let pts = sobol_sequence(4, 1);
let expected = [0.5, 0.75, 0.25, 0.375];
for (p, &e) in pts.iter().zip(&expected) {
assert!((p[0] - e).abs() < 1e-12, "got {} expected {}", p[0], e);
}
}
#[test]
fn sobol_2d_first_point() {
let pts = sobol_sequence(1, 2);
assert!((pts[0][0] - 0.5).abs() < 1e-12);
assert!((pts[0][1] - 0.5).abs() < 1e-12);
}
#[test]
fn sobol_in_unit_cube() {
let pts = sobol_sequence(1024, 4);
for p in &pts {
assert_eq!(p.len(), 4);
for &x in p {
assert!((0.0..1.0).contains(&x), "out of range: {x}");
}
}
}
#[test]
fn sobol_generator_skip_consistent() {
let mut gen1 = SobolGenerator::new(3);
let all: Vec<_> = (0..10).map(|_| gen1.next()).collect();
let mut gen2 = SobolGenerator::new(3);
gen2.skip(5);
let last5: Vec<_> = (0..5).map(|_| gen2.next()).collect();
for (a, b) in all[5..].iter().zip(&last5) {
assert_eq!(a, b);
}
}
#[test]
#[should_panic(expected = "at most 8 dimensions")]
fn sobol_rejects_high_dim() {
SobolGenerator::new(9);
}
#[test]
#[should_panic(expected = "dimension must be >= 1")]
fn sobol_rejects_zero_dim() {
SobolGenerator::new(0);
}
#[test]
fn scrambled_in_unit_cube() {
let pts = sobol_scrambled(512, 3, 12345);
for p in &pts {
assert_eq!(p.len(), 3);
for &x in p {
assert!((0.0..1.0).contains(&x), "out of range: {x}");
}
}
}
#[test]
fn scrambled_different_seeds_differ() {
let a = sobol_scrambled(16, 2, 1);
let b = sobol_scrambled(16, 2, 2);
let diffs = a.iter().zip(&b).filter(|(pa, pb)| pa != pb).count();
assert!(
diffs > 0,
"different seeds should produce different sequences"
);
}
#[test]
fn scrambled_same_seed_reproducible() {
let a = sobol_scrambled(32, 2, 42);
let b = sobol_scrambled(32, 2, 42);
assert_eq!(a, b);
}
#[test]
fn qmc_integrates_x_squared() {
let exact = 1.0 / 3.0;
let n = 4096;
let pts = sobol_sequence(n, 1);
let qmc_est: f64 = pts.iter().map(|p| p[0] * p[0]).sum::<f64>() / n as f64;
let qmc_err = (qmc_est - exact).abs();
assert!(qmc_err < 5e-4, "QMC integration error too large: {qmc_err}");
}
#[test]
fn halton_integrates_x_squared() {
let exact = 1.0 / 3.0;
let n = 4096;
let pts = halton_sequence(n, 1);
let est: f64 = pts.iter().map(|p| p[0] * p[0]).sum::<f64>() / n as f64;
let err = (est - exact).abs();
assert!(err < 5e-4, "Halton integration error too large: {err}");
}
#[test]
fn rightmost_zero_cases() {
assert_eq!(rightmost_zero(0b0), 0); assert_eq!(rightmost_zero(0b1), 1); assert_eq!(rightmost_zero(0b11), 2);
assert_eq!(rightmost_zero(0b101), 1);
assert_eq!(rightmost_zero(0b111), 3);
}
#[test]
fn radical_inverse_base2() {
assert!((radical_inverse(1, 2) - 0.5).abs() < 1e-15);
assert!((radical_inverse(2, 2) - 0.25).abs() < 1e-15);
assert!((radical_inverse(3, 2) - 0.75).abs() < 1e-15);
assert!((radical_inverse(5, 2) - 0.625).abs() < 1e-15);
}
#[test]
fn radical_inverse_base3() {
assert!((radical_inverse(1, 3) - 1.0 / 3.0).abs() < 1e-15);
assert!((radical_inverse(2, 3) - 2.0 / 3.0).abs() < 1e-15);
assert!((radical_inverse(3, 3) - 1.0 / 9.0).abs() < 1e-15);
}
}