pub(crate) fn radical_inverse(t: usize, base: usize) -> f64 {
debug_assert!(base >= 2, "Halton base must be a prime ≥ 2");
let mut result = 0.0;
let mut f = 1.0 / base as f64;
let mut i = t;
while i > 0 {
result += f * (i % base) as f64;
i /= base;
f /= base as f64;
}
result
}
pub(crate) fn halton_direction(t: usize, n: usize) -> Vec<f64> {
first_n_primes(n)
.into_iter()
.map(|p| radical_inverse(t, p))
.collect()
}
pub(crate) fn first_n_primes(n: usize) -> Vec<usize> {
let mut primes: Vec<usize> = Vec::with_capacity(n);
let mut candidate = 2;
while primes.len() < n {
if primes.iter().all(|&p| candidate % p != 0) {
primes.push(candidate);
}
candidate += 1;
}
primes
}
pub(crate) fn halton_seed(n: usize) -> usize {
*first_n_primes(n)
.last()
.expect("n ≥ 1 for a non-empty problem")
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f64, b: f64) {
assert!((a - b).abs() < 1e-12, "{a} != {b}");
}
#[test]
fn first_primes() {
assert_eq!(first_n_primes(4), vec![2, 3, 5, 7]);
assert_eq!(halton_seed(2), 3); assert_eq!(halton_seed(4), 7); }
#[test]
fn halton_table_1() {
let rows: [(usize, [f64; 4]); 8] = [
(0, [0.0, 0.0, 0.0, 0.0]),
(1, [1.0 / 2.0, 1.0 / 3.0, 1.0 / 5.0, 1.0 / 7.0]),
(2, [1.0 / 4.0, 2.0 / 3.0, 2.0 / 5.0, 2.0 / 7.0]),
(3, [3.0 / 4.0, 1.0 / 9.0, 3.0 / 5.0, 3.0 / 7.0]),
(4, [1.0 / 8.0, 4.0 / 9.0, 4.0 / 5.0, 4.0 / 7.0]),
(5, [5.0 / 8.0, 7.0 / 9.0, 1.0 / 25.0, 5.0 / 7.0]),
(6, [3.0 / 8.0, 2.0 / 9.0, 6.0 / 25.0, 6.0 / 7.0]),
(7, [7.0 / 8.0, 5.0 / 9.0, 11.0 / 25.0, 1.0 / 49.0]),
];
for (t, expected) in rows {
let u = halton_direction(t, 4);
for (got, want) in u.iter().zip(expected.iter()) {
approx(*got, *want);
}
}
}
#[test]
fn radical_inverse_footnote() {
approx(radical_inverse(5, 3), 7.0 / 9.0);
}
}