use nalgebra::DMatrix;
use rand::{Rng, RngExt};
pub fn latin_hypercube<R: Rng + ?Sized>(n: usize, d: usize, rng: &mut R) -> DMatrix<f64> {
assert!(n > 0 && d > 0, "latin_hypercube requires n > 0 and d > 0");
let mut samples = DMatrix::<f64>::zeros(n, d);
for j in 0..d {
let mut perm: Vec<usize> = (0..n).collect();
for i in (1..n).rev() {
let swap_idx = rng.random_range(0..=i);
perm.swap(i, swap_idx);
}
for i in 0..n {
let strata_start = perm[i] as f64 / n as f64;
let strata_end = (perm[i] + 1) as f64 / n as f64;
samples[(i, j)] = strata_start + (strata_end - strata_start) * rng.random::<f64>();
}
}
samples
}
pub fn rejection_sampling<R, P, T, D>(
target: T,
mut proposal: P,
proposal_density: D,
m: f64,
n: usize,
rng: &mut R,
) -> Vec<f64>
where
R: Rng + ?Sized,
P: FnMut(&mut R) -> f64,
T: Fn(f64) -> f64,
D: Fn(f64) -> f64,
{
let mut accepted = Vec::with_capacity(n);
while accepted.len() < n {
let x = proposal(rng);
let u = rng.random::<f64>();
let acceptance_prob = target(x) / (m * proposal_density(x));
if u < acceptance_prob {
accepted.push(x);
}
}
accepted
}
pub struct HaltonSequence {
dimension: usize,
bases: Vec<u32>,
index: u64,
}
impl HaltonSequence {
pub fn new(dimension: usize) -> Self {
assert!(
dimension > 0 && dimension <= 16,
"HaltonSequence dimension must be between 1 and 16"
);
let primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53];
let bases: Vec<u32> = primes.iter().take(dimension).copied().collect();
Self {
dimension,
bases,
index: 0,
}
}
pub fn generate(&mut self, n: usize) -> DMatrix<f64> {
let mut samples = DMatrix::<f64>::zeros(n, self.dimension);
for (i, point) in self.by_ref().take(n).enumerate() {
for (j, value) in point.into_iter().enumerate() {
samples[(i, j)] = value;
}
}
samples
}
}
impl Iterator for HaltonSequence {
type Item = Vec<f64>;
fn next(&mut self) -> Option<Self::Item> {
let point = self
.bases
.iter()
.map(|&base| van_der_corput(self.index, base))
.collect();
self.index += 1;
Some(point)
}
}
fn van_der_corput(mut n: u64, base: u32) -> f64 {
let mut vdc = 0.0;
let mut denom = 1.0;
while n > 0 {
denom *= base as f64;
let remainder = n % base as u64;
n /= base as u64;
vdc += remainder as f64 / denom;
}
vdc
}
pub fn sobol_1d(n: usize) -> Vec<f64> {
let mut sequence = Vec::with_capacity(n);
for i in 0..n {
sequence.push(van_der_corput(i as u64, 2));
}
sequence
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_latin_hypercube() {
let mut rng = rand::rng();
let samples = latin_hypercube(50, 3, &mut rng);
assert_eq!(samples.nrows(), 50);
assert_eq!(samples.ncols(), 3);
for i in 0..50 {
for j in 0..3 {
assert!(samples[(i, j)] >= 0.0 && samples[(i, j)] <= 1.0);
}
}
}
#[test]
fn test_halton_sequence() {
let mut halton = HaltonSequence::new(2);
let samples = halton.generate(10);
assert_eq!(samples.nrows(), 10);
assert_eq!(samples.ncols(), 2);
for i in 0..10 {
for j in 0..2 {
assert!(samples[(i, j)] >= 0.0 && samples[(i, j)] <= 1.0);
}
}
}
#[test]
fn test_van_der_corput() {
let expected = [0.0, 0.5, 0.25, 0.75, 0.125];
for (i, &exp) in expected.iter().enumerate() {
let val = van_der_corput(i as u64, 2);
assert!((val - exp).abs() < 1e-10);
}
}
#[test]
fn test_rejection_sampling() {
let mut rng = rand::rng();
let target = |x: f64| {
if (0.0..=1.0).contains(&x) {
(-x * x / 2.0).exp()
} else {
0.0
}
};
let proposal = |rng: &mut rand::rngs::ThreadRng| rng.random::<f64>();
let proposal_density = |_x: f64| 1.0;
let m = 1.5;
let samples = rejection_sampling(target, proposal, proposal_density, m, 100, &mut rng);
assert_eq!(samples.len(), 100);
for &s in &samples {
assert!((0.0..=1.0).contains(&s));
}
}
#[test]
fn test_latin_hypercube_stratification() {
let mut rng = rand::rng();
let n = 100;
let samples = latin_hypercube(n, 1, &mut rng);
let mut counts = vec![0; n];
for i in 0..n {
let stratum = (samples[(i, 0)] * n as f64).floor() as usize;
let stratum = stratum.min(n - 1);
counts[stratum] += 1;
}
for (i, &c) in counts.iter().enumerate() {
assert_eq!(c, 1, "stratum {} has {} samples, expected 1", i, c);
}
}
#[test]
fn test_latin_hypercube_single_sample() {
let mut rng = rand::rng();
let samples = latin_hypercube(1, 2, &mut rng);
assert_eq!(samples.nrows(), 1);
assert_eq!(samples.ncols(), 2);
assert!(samples[(0, 0)] >= 0.0 && samples[(0, 0)] <= 1.0);
assert!(samples[(0, 1)] >= 0.0 && samples[(0, 1)] <= 1.0);
}
#[test]
fn test_halton_iterator_matches_generate() {
let from_iter: Vec<Vec<f64>> = HaltonSequence::new(2).take(5).collect();
let generated = HaltonSequence::new(2).generate(5);
for (i, point) in from_iter.iter().enumerate() {
assert_eq!(point.len(), 2);
for (j, &value) in point.iter().enumerate() {
assert_eq!(value, generated[(i, j)]);
}
}
}
#[test]
fn test_halton_sequence_deterministic() {
let mut h1 = HaltonSequence::new(1);
let mut h2 = HaltonSequence::new(1);
let s1 = h1.generate(10);
let s2 = h2.generate(10);
for i in 0..10 {
assert!((s1[(i, 0)] - s2[(i, 0)]).abs() < 1e-15);
}
}
#[test]
fn test_halton_sequence_coverage() {
let mut halton = HaltonSequence::new(2);
let samples = halton.generate(100);
let mut has_low = false;
let mut has_high = false;
for i in 0..100 {
if samples[(i, 0)] < 0.1 {
has_low = true;
}
if samples[(i, 0)] > 0.9 {
has_high = true;
}
}
assert!(has_low, "Halton sequence missing low values");
assert!(has_high, "Halton sequence missing high values");
}
#[test]
fn test_sobol_1d() {
let seq = sobol_1d(8);
assert_eq!(seq.len(), 8);
assert!((seq[0] - 0.0).abs() < 1e-15);
assert!((seq[1] - 0.5).abs() < 1e-15);
for &v in &seq {
assert!((0.0..=1.0).contains(&v));
}
}
#[test]
fn test_van_der_corput_base3() {
let expected = [0.0, 1.0 / 3.0, 2.0 / 3.0, 1.0 / 9.0];
for (i, &exp) in expected.iter().enumerate() {
let val = van_der_corput(i as u64, 3);
assert!(
(val - exp).abs() < 1e-10,
"vdc({}, 3) = {} expected {}",
i,
val,
exp
);
}
}
}