1use std::f64::consts::PI;
6
7use super::types::Xoshiro256;
8
9pub fn box_muller(rng: &mut Xoshiro256) -> (f64, f64) {
11 let u1 = rng.next_f64().max(1e-300);
12 let u2 = rng.next_f64();
13 let r = (-2.0 * u1.ln()).sqrt();
14 let theta = 2.0 * PI * u2;
15 (r * theta.cos(), r * theta.sin())
16}
17pub fn sample_gamma(rng: &mut Xoshiro256, shape: f64, scale: f64) -> f64 {
19 if shape < 1.0 {
20 let u = rng.next_f64().max(1e-300);
21 return sample_gamma(rng, shape + 1.0, scale) * u.powf(1.0 / shape);
22 }
23 let d = shape - 1.0 / 3.0;
24 let c = 1.0 / (9.0 * d).sqrt();
25 loop {
26 let (z, _) = box_muller(rng);
27 let v_raw = 1.0 + c * z;
28 if v_raw <= 0.0 {
29 continue;
30 }
31 let v = v_raw.powi(3);
32 let u = rng.next_f64().max(1e-300);
33 if u < 1.0 - 0.0331 * (z * z) * (z * z) {
34 return d * v * scale;
35 }
36 if u.ln() < 0.5 * z * z + d * (1.0 - v + v.ln()) {
37 return d * v * scale;
38 }
39 }
40}
41pub fn weighted_choice_index(probs: &[f64], u: f64) -> usize {
43 let mut cumsum = 0.0;
44 for (i, &p) in probs.iter().enumerate() {
45 cumsum += p;
46 if u < cumsum {
47 return i;
48 }
49 }
50 probs.len() - 1
51}
52pub fn halton_1d(mut i: usize, base: usize) -> f64 {
54 let mut f = 1.0f64;
55 let mut r = 0.0f64;
56 while i > 0 {
57 f /= base as f64;
58 r += f * (i % base) as f64;
59 i /= base;
60 }
61 r
62}
63pub fn first_n_primes(n: usize) -> Vec<usize> {
65 let mut primes = Vec::with_capacity(n);
66 let mut candidate = 2usize;
67 while primes.len() < n {
68 if primes.iter().all(|&p| !candidate.is_multiple_of(p)) {
69 primes.push(candidate);
70 }
71 candidate += 1;
72 }
73 primes
74}
75pub fn shuffle_fisher_yates<T>(slice: &mut [T], rng: &mut Xoshiro256) {
77 let n = slice.len();
78 for i in (1..n).rev() {
79 let j = (rng.next_u64() as usize) % (i + 1);
80 slice.swap(i, j);
81 }
82}
83pub fn sample_without_replacement(rng: &mut Xoshiro256, n: usize, k: usize) -> Vec<usize> {
85 let k = k.min(n);
86 let mut pool: Vec<usize> = (0..n).collect();
87 shuffle_fisher_yates(&mut pool, rng);
88 pool.truncate(k);
89 pool
90}
91pub fn weighted_choice<T: Clone>(rng: &mut Xoshiro256, choices: &[(T, f64)]) -> Option<T> {
93 if choices.is_empty() {
94 return None;
95 }
96 let total: f64 = choices.iter().map(|(_, w)| w).sum();
97 let u = rng.next_f64() * total;
98 let mut cumsum = 0.0;
99 for (val, w) in choices {
100 cumsum += w;
101 if u <= cumsum {
102 return Some(val.clone());
103 }
104 }
105 choices.last().map(|c| c.0.clone())
106}
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 use crate::random::DirichletDist;
112
113 use crate::random::ExponentialDist;
114
115 use crate::random::MultinomialDist;
116 use crate::random::NormalDist;
117
118 use crate::random::Pcg64;
119 use crate::random::PoissonDist;
120 use crate::random::RandomSampler;
121
122 use crate::random::SplitMix64;
123
124 use crate::random::UniformDist;
125
126 use crate::random::Xoshiro256;
127
128 #[test]
129 fn splitmix64_deterministic() {
130 let mut a = SplitMix64::new(42);
131 let mut b = SplitMix64::new(42);
132 assert_eq!(a.next_u64(), b.next_u64());
133 }
134 #[test]
135 fn splitmix64_different_seeds() {
136 let mut a = SplitMix64::new(1);
137 let mut b = SplitMix64::new(2);
138 assert_ne!(a.next_u64(), b.next_u64());
139 }
140 #[test]
141 fn splitmix64_f64_range() {
142 let mut rng = SplitMix64::new(123);
143 for _ in 0..1000 {
144 let v = rng.next_f64();
145 assert!((0.0..1.0).contains(&v));
146 }
147 }
148 #[test]
149 fn pcg64_deterministic() {
150 let mut a = Pcg64::new(99, 1);
151 let mut b = Pcg64::new(99, 1);
152 for _ in 0..10 {
153 assert_eq!(a.next_u64(), b.next_u64());
154 }
155 }
156 #[test]
157 fn pcg64_f64_range() {
158 let mut rng = Pcg64::new(42, 1);
159 for _ in 0..1000 {
160 let v = rng.next_f64();
161 assert!((0.0..1.0).contains(&v), "v={v}");
162 }
163 }
164 #[test]
165 fn pcg64_range() {
166 let mut rng = Pcg64::new(7, 3);
167 for _ in 0..1000 {
168 let v = rng.next_f64_range(2.0, 5.0);
169 assert!((2.0..5.0).contains(&v), "v={v}");
170 }
171 }
172 #[test]
173 fn pcg64_different_streams() {
174 let mut a = Pcg64::new(0, 0);
175 let mut b = Pcg64::new(0, 1);
176 let va: Vec<u64> = (0..10).map(|_| a.next_u64()).collect();
177 let vb: Vec<u64> = (0..10).map(|_| b.next_u64()).collect();
178 assert_ne!(va, vb);
179 }
180 #[test]
181 fn xoshiro256_deterministic() {
182 let mut a = Xoshiro256::new(55);
183 let mut b = Xoshiro256::new(55);
184 for _ in 0..20 {
185 assert_eq!(a.next_u64(), b.next_u64());
186 }
187 }
188 #[test]
189 fn xoshiro256_f64_range() {
190 let mut rng = Xoshiro256::new(1);
191 for _ in 0..1000 {
192 let v = rng.next_f64();
193 assert!((0.0..1.0).contains(&v));
194 }
195 }
196 #[test]
197 fn xoshiro256_jump_changes_state() {
198 let mut a = Xoshiro256::new(10);
199 let mut b = Xoshiro256::new(10);
200 b.jump();
201 assert_ne!(a.next_u64(), b.next_u64());
202 }
203 #[test]
204 fn xoshiro256_long_jump_changes_state() {
205 let mut a = Xoshiro256::new(10);
206 let mut b = Xoshiro256::new(10);
207 b.long_jump();
208 assert_ne!(a.next_u64(), b.next_u64());
209 }
210 #[test]
211 fn uniform_dist_range() {
212 let dist = UniformDist::new(-3.0, 7.0);
213 let mut rng = Xoshiro256::new(1);
214 for _ in 0..1000 {
215 let v = dist.sample(&mut rng);
216 assert!((-3.0..7.0).contains(&v), "v={v}");
217 }
218 }
219 #[test]
220 fn uniform_dist_sample_n_count() {
221 let dist = UniformDist::new(0.0, 1.0);
222 let mut rng = Xoshiro256::new(2);
223 let samples = dist.sample_n(&mut rng, 50);
224 assert_eq!(samples.len(), 50);
225 }
226 #[test]
227 fn normal_dist_mean_approx() {
228 let mut dist = NormalDist::new(5.0, 1.0);
229 let mut rng = Xoshiro256::new(3);
230 let samples = dist.sample_n(&mut rng, 10000);
231 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
232 assert!((mean - 5.0).abs() < 0.1, "mean={mean}");
233 }
234 #[test]
235 fn normal_dist_std_approx() {
236 let mut dist = NormalDist::new(0.0, 2.0);
237 let mut rng = Xoshiro256::new(4);
238 let samples = dist.sample_n(&mut rng, 10000);
239 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
240 let var = samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / samples.len() as f64;
241 assert!((var.sqrt() - 2.0).abs() < 0.1, "std={}", var.sqrt());
242 }
243 #[test]
244 fn poisson_small_lambda_mean() {
245 let dist = PoissonDist::new(3.0);
246 let mut rng = Xoshiro256::new(5);
247 let samples: Vec<u64> = (0..10000).map(|_| dist.sample(&mut rng)).collect();
248 let mean = samples.iter().sum::<u64>() as f64 / samples.len() as f64;
249 assert!((mean - 3.0).abs() < 0.2, "mean={mean}");
250 }
251 #[test]
252 fn poisson_large_lambda_mean() {
253 let dist = PoissonDist::new(100.0);
254 let mut rng = Xoshiro256::new(6);
255 let samples: Vec<u64> = (0..5000).map(|_| dist.sample(&mut rng)).collect();
256 let mean = samples.iter().sum::<u64>() as f64 / samples.len() as f64;
257 assert!((mean - 100.0).abs() < 2.0, "mean={mean}");
258 }
259 #[test]
260 fn exponential_dist_mean() {
261 let dist = ExponentialDist::new(2.0);
262 let mut rng = Xoshiro256::new(7);
263 let samples = dist.sample_n(&mut rng, 10000);
264 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
265 assert!((mean - 0.5).abs() < 0.05, "mean={mean}");
266 }
267 #[test]
268 fn exponential_dist_all_positive() {
269 let dist = ExponentialDist::new(1.0);
270 let mut rng = Xoshiro256::new(8);
271 let samples = dist.sample_n(&mut rng, 1000);
272 assert!(samples.iter().all(|&v| v > 0.0));
273 }
274 #[test]
275 fn dirichlet_sums_to_one() {
276 let dist = DirichletDist::new(vec![1.0, 2.0, 3.0]);
277 let mut rng = Xoshiro256::new(9);
278 for _ in 0..100 {
279 let s = dist.sample(&mut rng);
280 let sum: f64 = s.iter().sum();
281 assert!((sum - 1.0).abs() < 1e-10, "sum={sum}");
282 }
283 }
284 #[test]
285 fn dirichlet_all_positive() {
286 let dist = DirichletDist::new(vec![0.5, 0.5, 0.5]);
287 let mut rng = Xoshiro256::new(10);
288 for _ in 0..100 {
289 let s = dist.sample(&mut rng);
290 assert!(s.iter().all(|&v| v > 0.0));
291 }
292 }
293 #[test]
294 fn dirichlet_uniform_mean() {
295 let dist = DirichletDist::new(vec![1.0, 1.0, 1.0]);
296 let mut rng = Xoshiro256::new(11);
297 let n = 5000usize;
298 let samples = dist.sample_n(&mut rng, n);
299 let mean0 = samples.iter().map(|s| s[0]).sum::<f64>() / n as f64;
300 assert!((mean0 - 1.0 / 3.0).abs() < 0.02, "mean0={mean0}");
301 }
302 #[test]
303 fn multinomial_counts_sum_to_n() {
304 let dist = MultinomialDist::new(vec![0.3, 0.4, 0.3]);
305 let mut rng = Xoshiro256::new(12);
306 let counts = dist.sample(&mut rng, 100);
307 assert_eq!(counts.iter().sum::<usize>(), 100);
308 }
309 #[test]
310 fn multinomial_distribution_approx() {
311 let probs = vec![0.5, 0.3, 0.2];
312 let dist = MultinomialDist::new(probs.clone());
313 let mut rng = Xoshiro256::new(13);
314 let n = 10000usize;
315 let counts = dist.sample(&mut rng, n);
316 let freq: Vec<f64> = counts.iter().map(|&c| c as f64 / n as f64).collect();
317 for (f, p) in freq.iter().zip(probs.iter()) {
318 assert!((f - p).abs() < 0.02, "freq={f}, prob={p}");
319 }
320 }
321 #[test]
322 fn stratified_sampling_covers_interval() {
323 let mut rng = Xoshiro256::new(14);
324 let samples = RandomSampler::stratified_1d(&mut rng, 10);
325 assert_eq!(samples.len(), 10);
326 for (i, &s) in samples.iter().enumerate() {
327 let lo = i as f64 / 10.0;
328 let hi = (i + 1) as f64 / 10.0;
329 assert!(s >= lo && s < hi, "sample[{i}]={s} outside [{lo},{hi})");
330 }
331 }
332 #[test]
333 fn latin_hypercube_dimensions() {
334 let mut rng = Xoshiro256::new(15);
335 let samples = RandomSampler::latin_hypercube(&mut rng, 20, 3);
336 assert_eq!(samples.len(), 20);
337 assert_eq!(samples[0].len(), 3);
338 }
339 #[test]
340 fn latin_hypercube_range() {
341 let mut rng = Xoshiro256::new(16);
342 let samples = RandomSampler::latin_hypercube(&mut rng, 10, 2);
343 for row in &samples {
344 for &v in row {
345 assert!((0.0..1.0).contains(&v), "v={v}");
346 }
347 }
348 }
349 #[test]
350 fn halton_sequence_dimensions() {
351 let samples = RandomSampler::halton(100, 3, 0);
352 assert_eq!(samples.len(), 100);
353 assert_eq!(samples[0].len(), 3);
354 }
355 #[test]
356 fn halton_1d_base2_known_values() {
357 assert!((halton_1d(1, 2) - 0.5).abs() < 1e-10);
358 assert!((halton_1d(2, 2) - 0.25).abs() < 1e-10);
359 assert!((halton_1d(3, 2) - 0.75).abs() < 1e-10);
360 }
361 #[test]
362 fn sobol_dimensions() {
363 let samples = RandomSampler::sobol(50, 4);
364 assert_eq!(samples.len(), 50);
365 assert_eq!(samples[0].len(), 4);
366 }
367 #[test]
368 fn sobol_range() {
369 let samples = RandomSampler::sobol(100, 2);
370 for row in &samples {
371 for &v in row {
372 assert!((0.0..=1.0).contains(&v), "v={v}");
373 }
374 }
375 }
376 #[test]
377 fn fisher_yates_shuffle_is_permutation() {
378 let mut rng = Xoshiro256::new(20);
379 let mut v: Vec<usize> = (0..20).collect();
380 let original = v.clone();
381 shuffle_fisher_yates(&mut v, &mut rng);
382 let mut sorted = v.clone();
383 sorted.sort_unstable();
384 assert_eq!(sorted, original);
385 }
386 #[test]
387 fn sample_without_replacement_count() {
388 let mut rng = Xoshiro256::new(21);
389 let s = sample_without_replacement(&mut rng, 100, 10);
390 assert_eq!(s.len(), 10);
391 }
392 #[test]
393 fn sample_without_replacement_unique() {
394 let mut rng = Xoshiro256::new(22);
395 let mut s = sample_without_replacement(&mut rng, 50, 20);
396 s.sort_unstable();
397 s.dedup();
398 assert_eq!(s.len(), 20);
399 }
400 #[test]
401 fn sample_without_replacement_k_gt_n() {
402 let mut rng = Xoshiro256::new(23);
403 let s = sample_without_replacement(&mut rng, 5, 10);
404 assert_eq!(s.len(), 5);
405 }
406 #[test]
407 fn weighted_choice_selects_valid() {
408 let mut rng = Xoshiro256::new(24);
409 let choices = vec![("a", 1.0), ("b", 2.0), ("c", 3.0)];
410 for _ in 0..100 {
411 let v = weighted_choice(&mut rng, &choices).unwrap();
412 assert!(matches!(v, "a" | "b" | "c"));
413 }
414 }
415 #[test]
416 fn weighted_choice_empty_none() {
417 let mut rng = Xoshiro256::new(25);
418 let choices: Vec<(i32, f64)> = vec![];
419 assert!(weighted_choice(&mut rng, &choices).is_none());
420 }
421 #[test]
422 fn box_muller_produces_two_values() {
423 let mut rng = Xoshiro256::new(26);
424 let (z0, z1) = box_muller(&mut rng);
425 assert!(z0.is_finite());
426 assert!(z1.is_finite());
427 }
428 #[test]
429 fn first_n_primes_correct() {
430 let p = first_n_primes(5);
431 assert_eq!(p, vec![2, 3, 5, 7, 11]);
432 }
433 #[test]
434 fn gamma_sample_positive() {
435 let mut rng = Xoshiro256::new(27);
436 for _ in 0..100 {
437 let v = sample_gamma(&mut rng, 2.0, 1.0);
438 assert!(v > 0.0, "gamma sample should be positive");
439 }
440 }
441 #[test]
442 fn poisson_sample_n_count() {
443 let dist = PoissonDist::new(5.0);
444 let mut rng = Xoshiro256::new(28);
445 let s = dist.sample_n(&mut rng, 30);
446 assert_eq!(s.len(), 30);
447 }
448 #[test]
449 fn weighted_choice_index_boundary() {
450 let probs = vec![0.5, 0.3, 0.2];
451 assert_eq!(weighted_choice_index(&probs, 0.0), 0);
452 assert_eq!(weighted_choice_index(&probs, 0.6), 1);
453 assert_eq!(weighted_choice_index(&probs, 0.99), 2);
454 }
455}
456pub const EULER_MASCHERONI: f64 = 0.577_215_664_901_532_9;
458pub fn sample_truncated_normal(rng: &mut Xoshiro256, mu: f64, sigma: f64, a: f64, b: f64) -> f64 {
461 loop {
462 let u1 = rng.next_f64().max(1e-300);
463 let u2 = rng.next_f64();
464 let r = (-2.0 * u1.ln()).sqrt();
465 let theta = 2.0 * PI * u2;
466 let x = mu + sigma * r * theta.cos();
467 if x >= a && x <= b {
468 return x;
469 }
470 }
471}
472pub fn scrambled_halton_1d(mut i: usize, base: usize, perm: &[usize]) -> f64 {
474 let mut f = 1.0f64;
475 let mut r = 0.0f64;
476 while i > 0 {
477 f /= base as f64;
478 let digit = i % base;
479 let scrambled_digit = if digit < perm.len() {
480 perm[digit]
481 } else {
482 digit
483 };
484 r += f * scrambled_digit as f64;
485 i /= base;
486 }
487 r
488}
489pub fn log_gamma(z: f64) -> f64 {
493 if z <= 0.0 {
494 return f64::INFINITY;
495 }
496 if z < 0.5 {
497 return PI.ln() - (PI * z).sin().ln() - log_gamma(1.0 - z);
498 }
499 pub(super) const G: f64 = 7.0;
500 pub(super) const C: [f64; 9] = [
501 0.999_999_999_999_809_9,
502 676.5203681218851,
503 -1259.1392167224028,
504 771.323_428_777_653_1,
505 -176.615_029_162_140_6,
506 12.507343278686905,
507 -0.13857109526572012,
508 9.984_369_578_019_572e-6,
509 1.5056327351493116e-7,
510 ];
511 let zz = z - 1.0;
512 let mut x = C[0];
513 for (i, &c) in C[1..].iter().enumerate() {
514 x += c / (zz + (i + 1) as f64);
515 }
516 let t = zz + G + 0.5;
517 0.5 * (2.0 * PI).ln() + (zz + 0.5) * t.ln() - t + x.ln()
518}
519pub fn gamma_fn(z: f64) -> f64 {
521 log_gamma(z).exp()
522}
523pub fn normal_cdf(x: f64) -> f64 {
527 let t = 1.0 / (1.0 + 0.2316419 * x.abs());
528 let poly = t
529 * (0.319381530
530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));
531 let p = 1.0 - ((-0.5 * x * x).exp() / (2.0 * PI).sqrt()) * poly;
532 if x >= 0.0 { p } else { 1.0 - p }
533}
534pub fn normal_ppf(p: f64) -> f64 {
538 let p = p.clamp(1e-15, 1.0 - 1e-15);
539 pub(super) const A: [f64; 6] = [
540 -3.969683028665376e+01,
541 2.209460984245205e+02,
542 -2.759285104469687e+02,
543 1.383_577_518_672_69e2,
544 -3.066479806614716e+01,
545 2.506628277459239,
546 ];
547 pub(super) const B: [f64; 5] = [
548 -5.447609879822406e+01,
549 1.615858368580409e+02,
550 -1.556989798598866e+02,
551 6.680131188771972e+01,
552 -1.328068155288572e+01,
553 ];
554 pub(super) const C: [f64; 6] = [
555 -7.784894002430293e-03,
556 -3.223964580411365e-01,
557 -2.400758277161838,
558 -2.549732539343734,
559 4.374664141464968,
560 2.938163982698783,
561 ];
562 pub(super) const D: [f64; 4] = [
563 7.784695709041462e-03,
564 3.224671290700398e-01,
565 2.445134137142996,
566 3.754408661907416,
567 ];
568 pub(super) const P_LOW: f64 = 0.02425;
569 pub(super) const P_HIGH: f64 = 1.0 - P_LOW;
570 if p < P_LOW {
571 let q = (-2.0 * p.ln()).sqrt();
572 (C[5] + q * (C[4] + q * (C[3] + q * (C[2] + q * (C[1] + q * C[0])))))
573 / (1.0 + q * (D[3] + q * (D[2] + q * (D[1] + q * D[0]))))
574 } else if p <= P_HIGH {
575 let q = p - 0.5;
576 let r = q * q;
577 (q * (A[5] + r * (A[4] + r * (A[3] + r * (A[2] + r * (A[1] + r * A[0]))))))
578 / (1.0 + r * (B[4] + r * (B[3] + r * (B[2] + r * (B[1] + r * B[0])))))
579 } else {
580 let q = (-(2.0 * (1.0 - p).ln())).sqrt();
581 -(C[5] + q * (C[4] + q * (C[3] + q * (C[2] + q * (C[1] + q * C[0])))))
582 / (1.0 + q * (D[3] + q * (D[2] + q * (D[1] + q * D[0]))))
583 }
584}
585pub fn student_t_cdf(x: f64, nu: f64) -> f64 {
589 if nu > 100.0 {
590 return normal_cdf(x);
591 }
592 let t_sq = x * x;
593 let z = nu / (nu + t_sq);
594 let ib = regularized_incomplete_beta(nu / 2.0, 0.5, z);
595 if x >= 0.0 { 1.0 - 0.5 * ib } else { 0.5 * ib }
596}
597pub fn regularized_incomplete_beta(a: f64, b: f64, x: f64) -> f64 {
601 if x <= 0.0 {
602 return 0.0;
603 }
604 if x >= 1.0 {
605 return 1.0;
606 }
607 if x > (a + 1.0) / (a + b + 2.0) {
608 return 1.0 - regularized_incomplete_beta(b, a, 1.0 - x);
609 }
610 let ln_beta = log_gamma(a) + log_gamma(b) - log_gamma(a + b);
611 let front = (a * x.ln() + b * (1.0 - x).ln() - ln_beta - a.ln()).exp();
612 let mut cf = 1.0f64;
613 for m in (1..=20).rev() {
614 let mf = m as f64;
615 let d1 = mf * (b - mf) * x / ((a + 2.0 * mf - 1.0) * (a + 2.0 * mf));
616 let d2 = -(a + mf) * (a + b + mf) * x / ((a + 2.0 * mf) * (a + 2.0 * mf + 1.0));
617 cf = 1.0 + d1 / (1.0 + d2 / cf);
618 }
619 front / (a * cf.max(1e-300))
620}
621#[cfg(test)]
622mod extended_tests {
623
624 use crate::bayesian_inference::log_gamma;
625 use crate::normal_cdf;
626 use crate::random::AntitheticVariates;
627 use crate::random::BetaDist;
628 use crate::random::BrownianBridge;
629 use crate::random::BrownianMotion;
630 use crate::random::ChiSquaredDist;
631 use crate::random::ClaytonCopula;
632
633 use crate::random::EULER_MASCHERONI;
634
635 use crate::random::FDist;
636 use crate::random::FractionalBrownianMotion;
637 use crate::random::FrankCopula;
638 use crate::random::FrechetDist;
639 use crate::random::GEVDist;
640 use crate::random::GOEMatrix;
641 use crate::random::GUEMatrix;
642 use crate::random::GammaDist;
643 use crate::random::GaussianCopula;
644 use crate::random::GumbelCopula;
645 use crate::random::GumbelDist;
646 use crate::random::LevyProcess;
647
648 use crate::random::OrnsteinUhlenbeck;
649
650 use crate::random::RejectionSampler;
651 use crate::random::ScrambledHalton;
652 use crate::random::SobolSequence;
653
654 use crate::random::StratifiedMonteCarlo;
655 use crate::random::StudentTDist;
656 use crate::random::TCopula;
657
658 use crate::random::WeibullDist;
659 use crate::random::WishartMatrix;
660 use crate::random::Xoshiro256;
661 use crate::random::ZigguratNormal;
662 use crate::random::functions::PI;
663 use crate::random::normal_ppf;
664 use crate::random::sample_truncated_normal;
665 #[test]
666 fn gamma_dist_positive_samples() {
667 let dist = GammaDist::new(2.0, 1.0);
668 let mut rng = Xoshiro256::new(100);
669 for _ in 0..200 {
670 assert!(dist.sample(&mut rng) > 0.0);
671 }
672 }
673 #[test]
674 fn gamma_dist_mean_approx() {
675 let dist = GammaDist::new(3.0, 2.0);
676 let mut rng = Xoshiro256::new(101);
677 let samples = dist.sample_n(&mut rng, 10000);
678 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
679 assert!((mean - 6.0).abs() < 0.3, "mean={mean}");
680 }
681 #[test]
682 fn gamma_dist_variance_approx() {
683 let alpha = 4.0;
684 let beta = 2.0;
685 let dist = GammaDist::new(alpha, beta);
686 let mut rng = Xoshiro256::new(102);
687 let samples = dist.sample_n(&mut rng, 10000);
688 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
689 let var = samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / samples.len() as f64;
690 let expected_var = alpha * beta * beta;
691 assert!(
692 (var - expected_var).abs() / expected_var < 0.1,
693 "var={var}, expected={expected_var}"
694 );
695 }
696 #[test]
697 fn beta_dist_in_range() {
698 let dist = BetaDist::new(2.0, 3.0);
699 let mut rng = Xoshiro256::new(110);
700 for _ in 0..200 {
701 let v = dist.sample(&mut rng);
702 assert!(v > 0.0 && v < 1.0, "v={v}");
703 }
704 }
705 #[test]
706 fn beta_dist_mean_approx() {
707 let dist = BetaDist::new(2.0, 3.0);
708 let mut rng = Xoshiro256::new(111);
709 let samples = dist.sample_n(&mut rng, 10000);
710 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
711 let expected = 2.0 / 5.0;
712 assert!((mean - expected).abs() < 0.02, "mean={mean}");
713 }
714 #[test]
715 fn beta_log_pdf_finite() {
716 let dist = BetaDist::new(2.0, 2.0);
717 let lp = dist.log_pdf(0.5);
718 assert!(lp.is_finite(), "log_pdf={lp}");
719 }
720 #[test]
721 fn student_t_mean_approx() {
722 let dist = StudentTDist::new(10.0);
723 let mut rng = Xoshiro256::new(120);
724 let samples = dist.sample_n(&mut rng, 10000);
725 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
726 assert!(mean.abs() < 0.1, "mean={mean}");
727 }
728 #[test]
729 fn student_t_variance_approx() {
730 let nu = 10.0;
731 let dist = StudentTDist::new(nu);
732 let mut rng = Xoshiro256::new(121);
733 let samples = dist.sample_n(&mut rng, 20000);
734 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
735 let var = samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / samples.len() as f64;
736 let expected_var = nu / (nu - 2.0);
737 assert!(
738 (var - expected_var).abs() / expected_var < 0.15,
739 "var={var}"
740 );
741 }
742 #[test]
743 fn student_t_log_pdf_finite() {
744 let dist = StudentTDist::new(5.0);
745 let lp = dist.log_pdf(0.0);
746 assert!(lp.is_finite());
747 }
748 #[test]
749 fn f_dist_positive_samples() {
750 let dist = FDist::new(5.0, 10.0);
751 let mut rng = Xoshiro256::new(130);
752 for _ in 0..200 {
753 assert!(dist.sample(&mut rng) > 0.0);
754 }
755 }
756 #[test]
757 fn f_dist_mean_approx() {
758 let dist = FDist::new(2.0, 10.0);
759 let mut rng = Xoshiro256::new(131);
760 let samples = dist.sample_n(&mut rng, 10000);
761 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
762 let expected = 10.0 / (10.0 - 2.0);
763 assert!((mean - expected).abs() / expected < 0.2, "mean={mean}");
764 }
765 #[test]
766 fn chi2_positive_samples() {
767 let dist = ChiSquaredDist::new(5.0);
768 let mut rng = Xoshiro256::new(140);
769 for _ in 0..200 {
770 assert!(dist.sample(&mut rng) > 0.0);
771 }
772 }
773 #[test]
774 fn chi2_mean_approx() {
775 let k = 4.0;
776 let dist = ChiSquaredDist::new(k);
777 let mut rng = Xoshiro256::new(141);
778 let samples = dist.sample_n(&mut rng, 10000);
779 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
780 assert!((mean - k).abs() / k < 0.1, "mean={mean}, expected={k}");
781 }
782 #[test]
783 fn weibull_positive_samples() {
784 let dist = WeibullDist::new(2.0, 1.0);
785 let mut rng = Xoshiro256::new(150);
786 for _ in 0..200 {
787 assert!(dist.sample(&mut rng) > 0.0);
788 }
789 }
790 #[test]
791 fn weibull_pdf_nonneg() {
792 let dist = WeibullDist::new(2.0, 1.0);
793 for i in 1..10 {
794 assert!(dist.pdf(i as f64 * 0.1) >= 0.0);
795 }
796 }
797 #[test]
798 fn gumbel_samples_finite() {
799 let dist = GumbelDist::new(0.0, 1.0);
800 let mut rng = Xoshiro256::new(160);
801 for _ in 0..200 {
802 let v = dist.sample(&mut rng);
803 assert!(v.is_finite(), "v={v}");
804 }
805 }
806 #[test]
807 fn gumbel_mean_approx() {
808 let mu = 2.0;
809 let beta = 1.0;
810 let dist = GumbelDist::new(mu, beta);
811 let mut rng = Xoshiro256::new(161);
812 let samples = dist.sample_n(&mut rng, 10000);
813 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
814 let expected = mu + beta * EULER_MASCHERONI;
815 assert!(
816 (mean - expected).abs() < 0.1,
817 "mean={mean}, expected={expected}"
818 );
819 }
820 #[test]
821 fn frechet_samples_finite() {
822 let dist = FrechetDist::new(2.0, 1.0, 0.0);
823 let mut rng = Xoshiro256::new(170);
824 for _ in 0..200 {
825 let v = dist.sample(&mut rng);
826 assert!(v.is_finite() && v > 0.0, "v={v}");
827 }
828 }
829 #[test]
830 fn frechet_pdf_nonneg() {
831 let dist = FrechetDist::new(2.0, 1.0, 0.0);
832 for i in 1..10 {
833 assert!(dist.pdf(i as f64 * 0.5) >= 0.0);
834 }
835 }
836 #[test]
837 fn gev_gumbel_samples_finite() {
838 let dist = GEVDist::new(0.0, 1.0, 0.0);
839 let mut rng = Xoshiro256::new(180);
840 for _ in 0..200 {
841 let v = dist.sample(&mut rng);
842 assert!(v.is_finite());
843 }
844 }
845 #[test]
846 fn gev_frechet_samples_finite() {
847 let dist = GEVDist::new(0.0, 1.0, 0.5);
848 let mut rng = Xoshiro256::new(181);
849 for _ in 0..200 {
850 let v = dist.sample(&mut rng);
851 assert!(v.is_finite());
852 }
853 }
854 #[test]
855 fn ziggurat_normal_samples_finite() {
856 let sampler = ZigguratNormal::new(256);
857 let mut rng = Xoshiro256::new(190);
858 for _ in 0..200 {
859 assert!(sampler.sample(&mut rng).is_finite());
860 }
861 }
862 #[test]
863 fn brownian_path_length() {
864 let bm = BrownianMotion::new(1.0, 100, 0.01);
865 let mut rng = Xoshiro256::new(200);
866 let path = bm.generate_path(&mut rng);
867 assert_eq!(path.len(), 101);
868 assert_eq!(path[0], 0.0);
869 }
870 #[test]
871 fn brownian_path_3d_finite() {
872 let bm = BrownianMotion::new(1.0, 50, 0.01);
873 let mut rng = Xoshiro256::new(201);
874 let path = bm.generate_path_3d(&mut rng);
875 for p in &path {
876 assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
877 }
878 }
879 #[test]
880 fn brownian_msd_formula() {
881 let bm = BrownianMotion::new(1.0, 1, 0.01);
882 let msd = bm.theoretical_msd_3d(1);
883 assert!((msd - 0.06).abs() < 1e-10, "msd={msd}");
884 }
885 #[test]
886 fn brownian_bridge_endpoints() {
887 let bb = BrownianBridge::new(0.0, 1.0, 1.0, 100, 1.0);
888 let mut rng = Xoshiro256::new(210);
889 let path = bb.generate_path(&mut rng);
890 assert_eq!(path.len(), 101);
891 assert!((path[0] - 0.0).abs() < 1e-10);
892 }
893 #[test]
894 fn brownian_bridge_variance_midpoint() {
895 let t = 0.5;
896 let bb = BrownianBridge::new(0.0, 0.0, 1.0, 10, 1.0);
897 let var = bb.variance_at(t);
898 assert!((var - 0.25).abs() < 1e-10, "var={var}");
899 }
900 #[test]
901 fn ou_path_length() {
902 let ou = OrnsteinUhlenbeck::new(1.0, 0.0, 1.0, 100, 0.01);
903 let mut rng = Xoshiro256::new(220);
904 let path = ou.generate_path(&mut rng, 5.0);
905 assert_eq!(path.len(), 101);
906 }
907 #[test]
908 fn ou_stationary_variance() {
909 let theta = 2.0;
910 let sigma = 1.0;
911 let ou = OrnsteinUhlenbeck::new(theta, 0.0, sigma, 5000, 0.005);
912 let mut rng = Xoshiro256::new(221);
913 let path = ou.generate_path(&mut rng, 0.0);
914 let tail = &path[2000..];
915 let mean = tail.iter().sum::<f64>() / tail.len() as f64;
916 let var = tail.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / tail.len() as f64;
917 let expected_var = sigma * sigma / (2.0 * theta);
918 assert!(
919 (var - expected_var).abs() / expected_var < 0.3,
920 "var={var}, expected={expected_var}"
921 );
922 }
923 #[test]
924 fn ou_mean_reversion() {
925 let ou = OrnsteinUhlenbeck::new(1.0, 2.0, 0.5, 10, 0.1);
926 let mean_t = ou.mean_at(10.0, 5.0);
927 assert!((mean_t - 2.0).abs() < 1.0);
928 }
929 #[test]
930 fn fbm_path_length() {
931 let fbm = FractionalBrownianMotion::new(0.7, 100, 0.01);
932 let mut rng = Xoshiro256::new(230);
933 let path = fbm.generate_path(&mut rng);
934 assert_eq!(path.len(), 101);
935 assert_eq!(path[0], 0.0);
936 }
937 #[test]
938 fn fbm_hurst_variance_scaling() {
939 let fbm = FractionalBrownianMotion::new(0.8, 1, 1.0);
940 let var = fbm.theoretical_variance(1.0);
941 assert!((var - 1.0).abs() < 1e-10);
942 }
943 #[test]
944 fn goe_symmetric() {
945 let mut rng = Xoshiro256::new(240);
946 let m = GOEMatrix::new(5, &mut rng);
947 for i in 0..5 {
948 for j in 0..5 {
949 assert!(
950 (m.get(i, j) - m.get(j, i)).abs() < 1e-15,
951 "GOE not symmetric at ({i},{j})"
952 );
953 }
954 }
955 }
956 #[test]
957 fn goe_frobenius_positive() {
958 let mut rng = Xoshiro256::new(241);
959 let m = GOEMatrix::new(4, &mut rng);
960 assert!(m.frobenius_sq() > 0.0);
961 }
962 #[test]
963 fn gue_hermitian_real_symmetric() {
964 let mut rng = Xoshiro256::new(250);
965 let m = GUEMatrix::new(4, &mut rng);
966 for i in 0..4 {
967 for j in 0..4 {
968 assert!((m.real_part[i * 4 + j] - m.real_part[j * 4 + i]).abs() < 1e-15);
969 assert!((m.imag_part[i * 4 + j] + m.imag_part[j * 4 + i]).abs() < 1e-15);
970 }
971 }
972 }
973 #[test]
974 fn wishart_trace_approx_n_times_p() {
975 let n = 4;
976 let p = 100;
977 let mut rng = Xoshiro256::new(260);
978 let w = WishartMatrix::new(n, p, &mut rng);
979 let tr = w.trace();
980 assert!((tr / (n * p) as f64 - 1.0).abs() < 0.2, "trace={tr}");
981 }
982 #[test]
983 fn wishart_symmetric() {
984 let mut rng = Xoshiro256::new(261);
985 let w = WishartMatrix::new(3, 5, &mut rng);
986 for i in 0..3 {
987 for j in 0..3 {
988 assert!((w.get(i, j) - w.get(j, i)).abs() < 1e-10);
989 }
990 }
991 }
992 #[test]
993 fn sobol_sequence_range() {
994 let mut sobol = SobolSequence::new(3);
995 for _ in 0..100 {
996 let pt = sobol.next_point();
997 for &v in &pt {
998 assert!((0.0..=1.0).contains(&v), "v={v}");
999 }
1000 }
1001 }
1002 #[test]
1003 fn sobol_sequence_generate_count() {
1004 let mut sobol = SobolSequence::new(2);
1005 let pts = sobol.generate(50);
1006 assert_eq!(pts.len(), 50);
1007 }
1008 #[test]
1009 fn scrambled_halton_range() {
1010 let mut rng = Xoshiro256::new(270);
1011 let mut sh = ScrambledHalton::new(3, &mut rng);
1012 for _ in 0..100 {
1013 let pt = sh.next_point();
1014 for &v in &pt {
1015 assert!((0.0..=1.0).contains(&v), "v={v}");
1016 }
1017 }
1018 }
1019 #[test]
1020 fn gaussian_copula_in_unit_square() {
1021 let cop = GaussianCopula::new(0.7);
1022 let mut rng = Xoshiro256::new(280);
1023 for _ in 0..200 {
1024 let (u, v) = cop.sample(&mut rng);
1025 assert!(u > 0.0 && u < 1.0 && v > 0.0 && v < 1.0, "u={u}, v={v}");
1026 }
1027 }
1028 #[test]
1029 fn gaussian_copula_kendall_tau() {
1030 let rho = 0.6;
1031 let cop = GaussianCopula::new(rho);
1032 let tau = cop.kendall_tau();
1033 let expected = (2.0 / PI) * rho.asin();
1034 assert!((tau - expected).abs() < 1e-10);
1035 }
1036 #[test]
1037 fn clayton_copula_in_unit_square() {
1038 let cop = ClaytonCopula::new(2.0);
1039 let mut rng = Xoshiro256::new(290);
1040 for _ in 0..200 {
1041 let (u, v) = cop.sample(&mut rng);
1042 assert!(u > 0.0 && u < 1.0 && v > 0.0 && v < 1.0, "u={u}, v={v}");
1043 }
1044 }
1045 #[test]
1046 fn clayton_copula_kendall_tau() {
1047 let theta = 2.0;
1048 let cop = ClaytonCopula::new(theta);
1049 let tau = cop.kendall_tau();
1050 assert!((tau - theta / (theta + 2.0)).abs() < 1e-10);
1051 }
1052 #[test]
1053 fn gumbel_copula_in_unit_square() {
1054 let cop = GumbelCopula::new(2.0);
1055 let mut rng = Xoshiro256::new(300);
1056 for _ in 0..200 {
1057 let (u, v) = cop.sample(&mut rng);
1058 assert!(u > 0.0 && u < 1.0 && v > 0.0 && v < 1.0, "u={u}, v={v}");
1059 }
1060 }
1061 #[test]
1062 fn gumbel_copula_kendall_tau() {
1063 let theta = 3.0;
1064 let cop = GumbelCopula::new(theta);
1065 assert!((cop.kendall_tau() - (1.0 - 1.0 / theta)).abs() < 1e-10);
1066 }
1067 #[test]
1068 fn frank_copula_in_unit_square() {
1069 let cop = FrankCopula::new(4.0);
1070 let mut rng = Xoshiro256::new(310);
1071 for _ in 0..200 {
1072 let (u, v) = cop.sample(&mut rng);
1073 assert!(u > 0.0 && u < 1.0 && v > 0.0 && v < 1.0, "u={u}, v={v}");
1074 }
1075 }
1076 #[test]
1077 fn antithetic_estimates_integral_linear() {
1078 let av = AntitheticVariates::new(5000);
1079 let mut rng = Xoshiro256::new(320);
1080 let est = av.estimate(&mut rng, &|x| x);
1081 assert!((est - 0.5).abs() < 0.01, "est={est}");
1082 }
1083 #[test]
1084 fn antithetic_estimates_integral_quadratic() {
1085 let av = AntitheticVariates::new(5000);
1086 let mut rng = Xoshiro256::new(321);
1087 let est = av.estimate(&mut rng, &|x| x * x);
1088 assert!((est - 1.0 / 3.0).abs() < 0.02, "est={est}");
1089 }
1090 #[test]
1091 fn stratified_mc_1d_integral() {
1092 let smc = StratifiedMonteCarlo::new(100, 1);
1093 let mut rng = Xoshiro256::new(330);
1094 let est = smc.estimate(&mut rng, &|x| x[0]);
1095 assert!((est - 0.5).abs() < 0.01, "est={est}");
1096 }
1097 #[test]
1098 fn log_gamma_known_values() {
1099 assert!(
1100 (log_gamma(1.0) - 0.0).abs() < 1e-6,
1101 "Γ(1)={}",
1102 log_gamma(1.0).exp()
1103 );
1104 assert!((log_gamma(2.0) - 0.0).abs() < 1e-6);
1105 assert!((log_gamma(3.0) - 2.0_f64.ln()).abs() < 1e-5);
1106 assert!((log_gamma(4.0) - 6.0_f64.ln()).abs() < 1e-4);
1107 }
1108 #[test]
1109 fn normal_cdf_known_values() {
1110 assert!(
1111 (normal_cdf(0.0) - 0.5).abs() < 1e-5,
1112 "Φ(0)={}",
1113 normal_cdf(0.0)
1114 );
1115 assert!((normal_cdf(1.645) - 0.95).abs() < 5e-3);
1116 assert!((normal_cdf(-1.645) - 0.05).abs() < 5e-3);
1117 }
1118 #[test]
1119 fn normal_ppf_inverse_of_cdf() {
1120 for p in [0.1, 0.25, 0.5, 0.75, 0.9] {
1121 let x = normal_ppf(p);
1122 let p2 = normal_cdf(x);
1123 assert!((p - p2).abs() < 1e-4, "p={p}, p2={p2}");
1124 }
1125 }
1126 #[test]
1127 fn truncated_normal_in_range() {
1128 let mut rng = Xoshiro256::new(340);
1129 for _ in 0..100 {
1130 let v = sample_truncated_normal(&mut rng, 0.0, 1.0, -2.0, 2.0);
1131 assert!((-2.0..=2.0).contains(&v), "v={v}");
1132 }
1133 }
1134 #[test]
1135 fn levy_process_cauchy_samples_mostly_finite() {
1136 let lp = LevyProcess::new(1.0, 0.0, 1.0, 0.0);
1137 let mut rng = Xoshiro256::new(350);
1138 let samples: Vec<f64> = (0..1000).map(|_| lp.sample(&mut rng)).collect();
1139 let count_finite = samples.iter().filter(|v| v.is_finite()).count();
1140 assert!(
1141 count_finite > 900,
1142 "too many non-finite samples: {count_finite}/1000"
1143 );
1144 }
1145 #[test]
1146 fn t_copula_in_unit_square() {
1147 let cop = TCopula::new(0.5, 5.0);
1148 let mut rng = Xoshiro256::new(360);
1149 for _ in 0..200 {
1150 let (u, v) = cop.sample(&mut rng);
1151 assert!(
1152 (0.0..=1.0).contains(&u) && (0.0..=1.0).contains(&v),
1153 "u={u}, v={v}"
1154 );
1155 }
1156 }
1157 #[test]
1158 fn rejection_sampler_produces_values_in_range() {
1159 let rs = RejectionSampler::new(2.0);
1160 let mut rng = Xoshiro256::new(370);
1161 let target = |x: f64| -> f64 {
1162 if (0.0..=1.0).contains(&x) {
1163 2.0 * (0.5 - (x - 0.5).abs())
1164 } else {
1165 0.0
1166 }
1167 };
1168 let mut propose = |r: &mut Xoshiro256| -> (f64, f64) {
1169 let x = r.next_f64();
1170 (x, 1.0)
1171 };
1172 for _ in 0..50 {
1173 let v = rs.sample(&mut rng, &mut propose, &target);
1174 assert!((0.0..=1.0).contains(&v));
1175 }
1176 }
1177}