use crate::error::QuadratureError;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
const BITS: u32 = 32;
pub struct SobolSequence {
dim: usize,
direction: Vec<u32>,
index: u64,
state: Vec<u32>,
}
impl SobolSequence {
pub fn new(dim: usize) -> Result<Self, QuadratureError> {
if dim == 0 {
return Err(QuadratureError::InvalidInput("dimension must be >= 1"));
}
if dim > MAX_DIM {
return Err(QuadratureError::InvalidInput(
"Sobol sequence supports at most 40 dimensions",
));
}
let b = BITS as usize;
let mut direction = vec![0u32; dim * b];
for (i, d) in direction.iter_mut().enumerate().take(b) {
#[allow(clippy::cast_possible_truncation)]
let i_u32 = i as u32;
*d = 1u32 << (BITS - 1 - i_u32);
}
for j in 1..dim {
let entry = &SOBOL_TABLE[j - 1];
let s = entry.degree;
let a = entry.coeffs;
for i in 0..s as usize {
#[allow(clippy::cast_possible_truncation)]
let i_u32 = i as u32;
direction[j * b + i] = entry.m[i] << (BITS - 1 - i_u32);
}
for i in s as usize..b {
let mut v = direction[j * b + i - s as usize] >> s;
v ^= direction[j * b + i - s as usize];
for k in 1..s as usize {
if (a >> (s as usize - 1 - k)) & 1 == 1 {
v ^= direction[j * b + i - k];
}
}
direction[j * b + i] = v;
}
}
Ok(Self {
dim,
direction,
index: 0,
state: vec![0u32; dim],
})
}
pub fn next_point(&mut self, point: &mut [f64]) {
assert!(point.len() >= self.dim);
self.index += 1;
let c = (self.index - 1).trailing_ones() as usize;
let b = BITS as usize;
#[allow(clippy::cast_precision_loss)]
let norm = 1.0 / (1u64 << BITS) as f64;
for (j, p) in point.iter_mut().enumerate().take(self.dim) {
self.state[j] ^= self.direction[j * b + c];
*p = f64::from(self.state[j]) * norm;
}
}
pub fn skip(&mut self, n: u64) {
let b = BITS as usize;
self.state.fill(0);
self.index = 0;
let gray = n ^ (n >> 1);
for bit in 0..BITS {
if (gray >> bit) & 1 == 1 {
for j in 0..self.dim {
self.state[j] ^= self.direction[j * b + bit as usize];
}
}
}
self.index = n;
}
#[must_use]
pub fn index(&self) -> u64 {
self.index
}
#[must_use]
pub fn dim(&self) -> usize {
self.dim
}
}
const MAX_DIM: usize = 40;
struct SobolEntry {
degree: u32,
coeffs: u32,
m: [u32; 8],
}
static SOBOL_TABLE: [SobolEntry; 39] = [
SobolEntry {
degree: 1,
coeffs: 0,
m: [1, 0, 0, 0, 0, 0, 0, 0],
},
SobolEntry {
degree: 1,
coeffs: 1,
m: [1, 0, 0, 0, 0, 0, 0, 0],
},
SobolEntry {
degree: 2,
coeffs: 1,
m: [1, 1, 0, 0, 0, 0, 0, 0],
},
SobolEntry {
degree: 3,
coeffs: 1,
m: [1, 3, 1, 0, 0, 0, 0, 0],
},
SobolEntry {
degree: 3,
coeffs: 2,
m: [1, 1, 1, 0, 0, 0, 0, 0],
},
SobolEntry {
degree: 4,
coeffs: 1,
m: [1, 1, 3, 3, 0, 0, 0, 0],
},
SobolEntry {
degree: 4,
coeffs: 4,
m: [1, 3, 5, 13, 0, 0, 0, 0],
},
SobolEntry {
degree: 5,
coeffs: 2,
m: [1, 1, 5, 5, 17, 0, 0, 0],
},
SobolEntry {
degree: 5,
coeffs: 4,
m: [1, 1, 5, 5, 5, 0, 0, 0],
},
SobolEntry {
degree: 5,
coeffs: 7,
m: [1, 1, 7, 11, 19, 0, 0, 0],
},
SobolEntry {
degree: 5,
coeffs: 11,
m: [1, 1, 5, 1, 1, 0, 0, 0],
},
SobolEntry {
degree: 5,
coeffs: 13,
m: [1, 1, 1, 3, 11, 0, 0, 0],
},
SobolEntry {
degree: 5,
coeffs: 14,
m: [1, 3, 5, 5, 31, 0, 0, 0],
},
SobolEntry {
degree: 6,
coeffs: 1,
m: [1, 3, 3, 9, 7, 49, 0, 0],
},
SobolEntry {
degree: 6,
coeffs: 13,
m: [1, 1, 1, 15, 21, 21, 0, 0],
},
SobolEntry {
degree: 6,
coeffs: 16,
m: [1, 3, 1, 13, 27, 49, 0, 0],
},
SobolEntry {
degree: 7,
coeffs: 19,
m: [1, 1, 1, 15, 7, 5, 127, 0],
},
SobolEntry {
degree: 7,
coeffs: 22,
m: [1, 3, 3, 5, 19, 33, 65, 0],
},
SobolEntry {
degree: 7,
coeffs: 25,
m: [1, 3, 7, 11, 29, 17, 85, 0],
},
SobolEntry {
degree: 7,
coeffs: 37,
m: [1, 1, 3, 7, 23, 55, 41, 0],
},
SobolEntry {
degree: 7,
coeffs: 41,
m: [1, 3, 5, 1, 15, 17, 63, 0],
},
SobolEntry {
degree: 7,
coeffs: 50,
m: [1, 1, 7, 9, 31, 29, 17, 0],
},
SobolEntry {
degree: 7,
coeffs: 55,
m: [1, 3, 7, 7, 21, 61, 119, 0],
},
SobolEntry {
degree: 7,
coeffs: 59,
m: [1, 1, 5, 3, 5, 49, 89, 0],
},
SobolEntry {
degree: 7,
coeffs: 62,
m: [1, 3, 1, 1, 11, 3, 117, 0],
},
SobolEntry {
degree: 8,
coeffs: 14,
m: [1, 3, 3, 3, 15, 17, 17, 193],
},
SobolEntry {
degree: 8,
coeffs: 52,
m: [1, 1, 3, 7, 31, 29, 67, 45],
},
SobolEntry {
degree: 8,
coeffs: 56,
m: [1, 3, 1, 7, 23, 59, 55, 165],
},
SobolEntry {
degree: 8,
coeffs: 67,
m: [1, 3, 5, 9, 21, 37, 7, 51],
},
SobolEntry {
degree: 8,
coeffs: 69,
m: [1, 1, 7, 5, 17, 13, 81, 251],
},
SobolEntry {
degree: 8,
coeffs: 70,
m: [1, 1, 3, 15, 29, 47, 49, 143],
},
SobolEntry {
degree: 8,
coeffs: 79,
m: [1, 3, 7, 3, 21, 39, 29, 45],
},
SobolEntry {
degree: 8,
coeffs: 81,
m: [1, 1, 5, 7, 29, 7, 37, 67],
},
SobolEntry {
degree: 8,
coeffs: 84,
m: [1, 1, 5, 5, 11, 57, 97, 175],
},
SobolEntry {
degree: 8,
coeffs: 87,
m: [1, 3, 3, 13, 29, 25, 29, 15],
},
SobolEntry {
degree: 8,
coeffs: 90,
m: [1, 1, 7, 7, 31, 37, 105, 189],
},
SobolEntry {
degree: 8,
coeffs: 97,
m: [1, 3, 5, 3, 25, 37, 5, 187],
},
SobolEntry {
degree: 8,
coeffs: 103,
m: [1, 3, 1, 7, 21, 43, 81, 37],
},
SobolEntry {
degree: 8,
coeffs: 115,
m: [1, 1, 3, 5, 1, 45, 47, 77],
},
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_point_dim1() {
let mut sob = SobolSequence::new(1).unwrap();
let mut pt = [0.0];
sob.next_point(&mut pt);
assert!((pt[0] - 0.5).abs() < 1e-10); }
#[test]
fn points_in_unit_cube() {
let mut sob = SobolSequence::new(5).unwrap();
let mut pt = vec![0.0; 5];
for _ in 0..100 {
sob.next_point(&mut pt);
for &x in &pt {
assert!(x >= 0.0 && x < 1.0, "x={x} out of [0,1)");
}
}
}
#[test]
fn skip_and_generate() {
let mut sob1 = SobolSequence::new(3).unwrap();
let mut pt1 = vec![0.0; 3];
for _ in 0..10 {
sob1.next_point(&mut pt1);
}
let mut sob2 = SobolSequence::new(3).unwrap();
sob2.skip(9);
let mut pt2 = vec![0.0; 3];
sob2.next_point(&mut pt2);
for j in 0..3 {
assert!(
(pt1[j] - pt2[j]).abs() < 1e-14,
"j={j}: {} vs {}",
pt1[j],
pt2[j]
);
}
}
#[test]
fn invalid_dim() {
assert!(SobolSequence::new(0).is_err());
assert!(SobolSequence::new(41).is_err());
}
#[test]
fn high_dim_points_in_unit_cube() {
let dim = 20;
let mut sob = SobolSequence::new(dim).unwrap();
let mut pt = vec![0.0; dim];
for i in 0..200 {
sob.next_point(&mut pt);
for (j, &x) in pt.iter().enumerate() {
assert!(
x >= 0.0 && x < 1.0,
"point {i}, dim {j}: x={x} out of [0,1)"
);
}
}
}
#[test]
fn high_dim_30_points_in_unit_cube() {
let dim = 30;
let mut sob = SobolSequence::new(dim).unwrap();
let mut pt = vec![0.0; dim];
for i in 0..100 {
sob.next_point(&mut pt);
for (j, &x) in pt.iter().enumerate() {
assert!(
x >= 0.0 && x < 1.0,
"point {i}, dim {j}: x={x} out of [0,1)"
);
}
}
}
#[test]
fn high_dim_constant_integral() {
let dim = 20;
let n = 1024;
let mut sob = SobolSequence::new(dim).unwrap();
let mut pt = vec![0.0; dim];
let mut sum = 0.0;
for _ in 0..n {
sob.next_point(&mut pt);
sum += 1.0; }
let estimate = sum / n as f64;
assert!(
(estimate - 1.0).abs() < 1e-14,
"estimate={estimate}, expected 1.0"
);
}
#[test]
fn max_dim_construction() {
let sob = SobolSequence::new(40);
assert!(sob.is_ok());
let mut sob = sob.unwrap();
assert_eq!(sob.dim(), 40);
let mut pt = vec![0.0; 40];
sob.next_point(&mut pt);
for (j, &x) in pt.iter().enumerate() {
assert!(x >= 0.0 && x < 1.0, "dim {j}: x={x} out of [0,1)");
}
}
}