1pub mod params;
2mod type_support;
3use core::{
4 fmt,
5 ops::{AddAssign, BitXorAssign},
6};
7use num_traits::{Bounded, One, PrimInt, Unsigned, Zero};
8pub use statrs;
9use statrs::distribution::Normal;
10use std::iter::repeat_n;
11
12#[derive(Clone)]
14pub struct Sobol<T: SobolType, R: Render<T> = UnitRender> {
15 pub dims: usize,
16 pub resolution: usize,
17 dir_vals: Vec<Vec<T::IT>>,
18 previous: Vec<T::IT>,
19 render: R,
20 pub count: T::IT,
21 pub max_len: T::IT,
22}
23
24#[derive(Debug, Clone, Copy, thiserror::Error)]
25pub enum SobolError {
26 #[error(
27 "Sobol sequence supports a maximum of {max_dims} dimensions, but was configured for {dims}."
28 )]
29 MaxDim { dims: usize, max_dims: usize },
30 #[error("Render supports a {render_dims} dimensions, but Sobol was configured for {dims}.")]
31 RenderDim { dims: usize, render_dims: usize },
32}
33
34impl<T: SobolType> Sobol<T, UnitRender>
35where
36 UnitRender: Render<T>,
37{
38 pub fn new<P, Param: SobolParams<P>>(dims: usize, params: &Param) -> Result<Self, SobolError>
40 where
41 T::IT: LossyFrom<P>,
42 {
43 Self::new_with_resolution::<P, Param>(dims, params, None, UnitRender)
44 }
45}
46impl<T: SobolType, R: Render<T>> Sobol<T, R> {
47 pub fn new_with_resolution<P, Param: SobolParams<P>>(
52 dims: usize,
53 params: &Param,
54 resolution: Option<usize>,
55 render: R,
56 ) -> Result<Self, SobolError>
57 where
58 T::IT: LossyFrom<P>,
59 {
60 let res = resolution
61 .filter(|res| *res <= T::MAX_RESOLUTION)
62 .unwrap_or(T::MAX_RESOLUTION);
63 let max_dims = params.max_dims();
64 if dims > params.max_dims() {
65 return Err(SobolError::MaxDim { dims, max_dims });
66 }
67 if let Some(render_dims) = render.support_dims() {
68 if dims != render_dims {
69 return Err(SobolError::RenderDim { dims, render_dims });
70 }
71 }
72
73 let dir_values = Self::init_direction_vals::<P, Param>(dims, res, params);
74 let dir_values = (0..dir_values[0].len())
76 .map(|i| dir_values.iter().map(|inner| inner[i]).collect::<Vec<_>>())
77 .collect();
78 Ok(Sobol {
79 dims,
80 resolution: res,
81 dir_vals: dir_values,
82 count: T::IT::zero(),
83 max_len: T::IT::max_value() >> (T::IT::BITS - res),
84 previous: Vec::with_capacity(dims),
85 render,
86 })
87 }
88
89 pub fn init_direction_vals<P, Param: SobolParams<P>>(
91 dims: usize,
92 resolution: usize,
93 params: &Param,
94 ) -> Vec<Vec<T::IT>>
95 where
96 T::IT: LossyFrom<P>,
97 {
98 let bits = T::IT::BITS;
99
100 (1..=dims)
101 .map(|dim| match dim {
102 1 => (1..=resolution)
103 .map(|i| T::IT::one() << (bits - i))
104 .collect(),
105 _ => {
106 let p = params.get_dim(dim);
108 let s = if resolution >= p.s() {
109 p.s()
110 } else {
111 resolution
112 };
113
114 let mut dirs: Vec<T::IT> = vec![T::IT::zero(); resolution];
116 for i in 1..=s {
117 let m = T::IT::lossy_from(p.m(i - 1));
118 dirs[i - 1] = m << (bits - i);
119 }
120
121 for i in s + 1..=resolution {
123 dirs[i - 1] = dirs[i - s - 1] ^ (dirs[i - s - 1] >> s);
124
125 for k in 1..s {
126 let a = T::IT::lossy_from(p.coefficient(s - k - 1));
127 let dir = dirs[i - k - 1];
128 dirs[i - 1] ^= a * dir;
129 }
130 }
131
132 dirs
133 }
134 })
135 .collect()
136 }
137
138 #[inline]
140 pub fn rightmost_zero(n: T::IT) -> usize {
141 (n ^ T::IT::max_value()).trailing_zeros() as usize
142 }
143
144 #[inline]
146 pub fn update(&mut self) {
147 if self.previous.is_empty() {
148 self.previous.extend(repeat_n(T::IT::zero(), self.dims));
149 } else {
150 let a = self.count - T::IT::one();
151 let c = Self::rightmost_zero(a);
152 for (p, dir) in self.dir_vals[c].iter().zip(&mut self.previous) {
153 *dir = *p ^ *dir;
154 }
155 }
156 self.count += T::IT::one();
157 }
158 #[inline]
159 pub fn render_next(&self) -> impl Iterator<Item = T> {
160 self.previous
161 .iter()
162 .enumerate()
163 .map(|(dim, val)| self.render.render(dim, *val))
164 }
165}
166
167impl<T: SobolType, R: Render<T>> Iterator for Sobol<T, R> {
168 type Item = Vec<T>;
169 #[inline]
170 fn next(&mut self) -> Option<Self::Item> {
171 if self.count < self.max_len {
172 self.update();
173 Some(self.render_next().collect())
174 } else {
175 None
176 }
177 }
178 #[inline]
179 fn nth(&mut self, n: usize) -> Option<Self::Item> {
180 for _ in 0..n {
181 if self.count < self.max_len {
182 self.update();
183 } else {
184 break;
185 }
186 }
187 self.next()
188 }
189}
190
191#[derive(Debug, Clone, Copy)]
192pub struct UnitRender;
193#[derive(Debug, Clone, Copy)]
194pub struct GaussianRender(pub Normal);
195#[derive(Debug, Clone)]
196pub struct MultiDimGaussianRender(pub Vec<Normal>);
197pub trait Render<T: SobolType>: Clone {
198 fn render(&self, dim: usize, val: <T as SobolType>::IT) -> T;
201
202 fn support_dims(&self) -> Option<usize> {
203 None
204 }
205}
206
207pub trait SobolType: Sized + fmt::Display {
210 type IT: InternalType;
212
213 const MAX_RESOLUTION: usize = Self::IT::BITS;
217}
218
219pub trait InternalType: PrimInt + Unsigned + One + Zero + AddAssign + BitXorAssign + Copy {
221 const BITS: usize;
222}
223
224pub trait SobolParams<P> {
226 type Dimension: ParamDimension<P>;
227 fn get_dim(&self, dim: usize) -> &Self::Dimension;
229
230 fn max_dims(&self) -> usize;
232}
233
234pub trait ParamDimension<P> {
236 fn d(&self) -> u16;
238
239 fn s(&self) -> usize;
241
242 fn coefficient(&self, i: usize) -> P;
244
245 fn m(&self, i: usize) -> P;
247}
248
249pub trait LossyFrom<T>: Sized {
253 fn lossy_from(_: T) -> Self;
254}