1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
13
14use std::f64::consts::PI;
15
16#[allow(dead_code)]
22pub struct LcgRng {
23 state: u64,
24}
25
26impl LcgRng {
27 fn new(seed: u64) -> Self {
28 Self { state: seed.max(1) }
29 }
30
31 fn next_u64(&mut self) -> u64 {
32 self.state = self
33 .state
34 .wrapping_mul(6_364_136_223_846_793_005)
35 .wrapping_add(1_442_695_040_888_963_407);
36 self.state
37 }
38
39 fn next_f64(&mut self) -> f64 {
40 (self.next_u64() >> 11) as f64 * (1.0 / (1u64 << 53) as f64)
41 }
42
43 fn next_normal(&mut self) -> f64 {
44 box_muller_normal(self.next_f64(), self.next_f64())
45 }
46}
47
48pub fn box_muller_normal(u1: f64, u2: f64) -> f64 {
58 let u1 = u1.max(1e-15);
59 (-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos()
60}
61
62pub fn inverse_cdf(p: f64) -> f64 {
66 assert!(p > 0.0 && p < 1.0, "p must be in (0, 1)");
68 let p_adj = if p < 0.5 { p } else { 1.0 - p };
69 let t = (-2.0 * p_adj.ln()).sqrt();
70 let c0 = 2.515_517;
71 let c1 = 0.802_853;
72 let c2 = 0.010_328;
73 let d1 = 1.432_788;
74 let d2 = 0.189_269;
75 let d3 = 0.001_308;
76 let num = c0 + c1 * t + c2 * t * t;
77 let den = 1.0 + d1 * t + d2 * t * t + d3 * t * t * t;
78 let x = t - num / den;
79 if p >= 0.5 { x } else { -x }
80}
81
82pub fn effective_sample_size(weights: &[f64]) -> f64 {
86 let sum_w: f64 = weights.iter().sum();
87 if sum_w < 1e-15 {
88 return 0.0;
89 }
90 let sum_w2: f64 = weights.iter().map(|&w| (w / sum_w).powi(2)).sum();
91 1.0 / sum_w2
92}
93
94pub fn autocorrelation(x: &[f64], k: usize) -> f64 {
98 let n = x.len();
99 if k >= n {
100 return 0.0;
101 }
102 let mean = x.iter().sum::<f64>() / n as f64;
103 let var: f64 = x.iter().map(|&xi| (xi - mean).powi(2)).sum::<f64>() / n as f64;
104 if var < 1e-15 {
105 return 1.0;
106 }
107 let cov: f64 = (0..n - k)
108 .map(|i| (x[i] - mean) * (x[i + k] - mean))
109 .sum::<f64>()
110 / n as f64;
111 cov / var
112}
113
114pub struct McSampler {
122 rng: LcgRng,
123}
124
125impl McSampler {
126 pub fn new(seed: u64) -> Self {
128 Self {
129 rng: LcgRng::new(seed),
130 }
131 }
132
133 pub fn uniform(&mut self) -> f64 {
135 self.rng.next_f64()
136 }
137
138 pub fn uniform_range(&mut self, a: f64, b: f64) -> f64 {
140 a + (b - a) * self.rng.next_f64()
141 }
142
143 pub fn normal(&mut self, mu: f64, sigma: f64) -> f64 {
145 mu + sigma * self.rng.next_normal()
146 }
147
148 pub fn exponential(&mut self, lambda: f64) -> f64 {
150 -self.rng.next_f64().max(1e-15).ln() / lambda
151 }
152
153 pub fn poisson(&mut self, lambda: f64) -> u64 {
155 let limit = (-lambda).exp();
156 let mut k = 0u64;
157 let mut p = 1.0;
158 loop {
159 p *= self.rng.next_f64();
160 if p <= limit {
161 break;
162 }
163 k += 1;
164 }
165 k
166 }
167
168 pub fn beta(&mut self, alpha: f64, beta: f64) -> f64 {
170 let x = self.gamma(alpha, 1.0);
172 let y = self.gamma(beta, 1.0);
173 x / (x + y)
174 }
175
176 pub fn gamma(&mut self, shape: f64, scale: f64) -> f64 {
178 if shape < 1.0 {
179 let g = self.gamma(shape + 1.0, scale);
181 let u = self.rng.next_f64().max(1e-15);
182 return g * u.powf(1.0 / shape);
183 }
184 let d = shape - 1.0 / 3.0;
185 let c = 1.0 / (9.0 * d).sqrt();
186 loop {
187 let z = self.rng.next_normal();
188 let v_inner = 1.0 + c * z;
189 if v_inner <= 0.0 {
190 continue;
191 }
192 let v = v_inner.powi(3);
193 let u = self.rng.next_f64().max(1e-15);
194 if u < 1.0 - 0.0331 * z.powi(4) {
195 return d * v * scale;
196 }
197 if u.ln() < 0.5 * z * z + d * (1.0 - v + v.ln()) {
198 return d * v * scale;
199 }
200 }
201 }
202
203 pub fn normal_samples(&mut self, n: usize, mu: f64, sigma: f64) -> Vec<f64> {
205 (0..n).map(|_| self.normal(mu, sigma)).collect()
206 }
207
208 pub fn uniform_samples(&mut self, n: usize, a: f64, b: f64) -> Vec<f64> {
210 (0..n).map(|_| self.uniform_range(a, b)).collect()
211 }
212}
213
214pub struct ImportanceSampler {
222 rng: LcgRng,
223 pub proposal_sigma: f64,
225}
226
227impl ImportanceSampler {
228 pub fn new(seed: u64, proposal_sigma: f64) -> Self {
230 Self {
231 rng: LcgRng::new(seed),
232 proposal_sigma,
233 }
234 }
235
236 pub fn accept_reject<F, G>(&mut self, target: F, proposal_sample: G, c: f64) -> f64
240 where
241 F: Fn(f64) -> f64,
242 G: Fn(&mut LcgRng) -> (f64, f64), {
244 loop {
245 let (x, q_x) = proposal_sample(&mut self.rng);
246 let p_x = target(x);
247 let u = self.rng.next_f64();
248 if u <= p_x / (c * q_x) {
249 return x;
250 }
251 }
252 }
253
254 pub fn importance_weights<F, G>(&self, samples: &[f64], target: F, proposal: G) -> Vec<f64>
258 where
259 F: Fn(f64) -> f64,
260 G: Fn(f64) -> f64,
261 {
262 samples
263 .iter()
264 .map(|&x| {
265 let q = proposal(x).max(1e-300);
266 target(x) / q
267 })
268 .collect()
269 }
270
271 pub fn estimate<F, H>(&mut self, n: usize, f_fn: F, log_w_fn: H) -> f64
273 where
274 F: Fn(f64) -> f64,
275 H: Fn(f64) -> f64, {
277 let sigma = self.proposal_sigma;
278 let samples: Vec<f64> = (0..n).map(|_| self.rng.next_normal() * sigma).collect();
279 let log_w: Vec<f64> = samples.iter().map(|&x| log_w_fn(x)).collect();
280 let log_w_max = log_w.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
281 let w: Vec<f64> = log_w.iter().map(|&lw| (lw - log_w_max).exp()).collect();
282 let sum_w: f64 = w.iter().sum();
283 let numerator: f64 = samples
284 .iter()
285 .zip(w.iter())
286 .map(|(&x, &wi)| f_fn(x) * wi)
287 .sum();
288 numerator / sum_w
289 }
290}
291
292pub struct Estimator {
298 pub samples: Vec<f64>,
300}
301
302impl Estimator {
303 pub fn new(samples: Vec<f64>) -> Self {
305 Self { samples }
306 }
307
308 pub fn mean(&self) -> f64 {
310 let n = self.samples.len();
311 if n == 0 {
312 return 0.0;
313 }
314 self.samples.iter().sum::<f64>() / n as f64
315 }
316
317 pub fn variance(&self) -> f64 {
319 let n = self.samples.len();
320 if n < 2 {
321 return 0.0;
322 }
323 let mu = self.mean();
324 self.samples.iter().map(|&x| (x - mu).powi(2)).sum::<f64>() / (n - 1) as f64
325 }
326
327 pub fn standard_error(&self) -> f64 {
329 let n = self.samples.len();
330 if n == 0 {
331 return 0.0;
332 }
333 (self.variance() / n as f64).sqrt()
334 }
335
336 pub fn confidence_interval(&self, alpha: f64) -> (f64, f64) {
340 let mu = self.mean();
341 let se = self.standard_error();
342 let z = inverse_cdf(1.0 - alpha / 2.0);
344 (mu - z * se, mu + z * se)
345 }
346
347 pub fn effective_sample_size(&self) -> f64 {
351 let n = self.samples.len();
352 if n == 0 {
353 return 0.0;
354 }
355 let mut sum_rho = 0.0;
356 let max_lag = (n / 2).min(50);
357 for k in 1..max_lag {
358 let rho = autocorrelation(&self.samples, k);
359 if rho.abs() < 0.05 {
360 break;
361 }
362 sum_rho += rho;
363 }
364 n as f64 / (1.0 + 2.0 * sum_rho).max(0.01)
365 }
366
367 pub fn median(&self) -> f64 {
369 let n = self.samples.len();
370 if n == 0 {
371 return 0.0;
372 }
373 let mut sorted = self.samples.clone();
374 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
375 if n % 2 == 1 {
376 sorted[n / 2]
377 } else {
378 0.5 * (sorted[n / 2 - 1] + sorted[n / 2])
379 }
380 }
381}
382
383pub struct MetropolisHastings {
391 rng: LcgRng,
392 pub step_size: f64,
394 pub burn_in: usize,
396 pub thinning: usize,
398 pub n_accepted: usize,
400}
401
402impl MetropolisHastings {
403 pub fn new(seed: u64, step_size: f64, burn_in: usize, thinning: usize) -> Self {
405 Self {
406 rng: LcgRng::new(seed),
407 step_size,
408 burn_in,
409 thinning,
410 n_accepted: 0,
411 }
412 }
413
414 pub fn sample<F>(&mut self, x0: f64, n_samples: usize, log_target: F) -> Vec<f64>
418 where
419 F: Fn(f64) -> f64,
420 {
421 let mut x = x0;
422 let total = self.burn_in + n_samples * self.thinning;
423 let mut chain = Vec::with_capacity(n_samples);
424 self.n_accepted = 0;
425 let mut n_proposed = 0usize;
426
427 for step in 0..total {
428 let x_prop = x + self.step_size * self.rng.next_normal();
429 let log_alpha = (log_target(x_prop) - log_target(x)).min(0.0);
430 let u = self.rng.next_f64().max(1e-15).ln();
431 n_proposed += 1;
432 if u <= log_alpha {
433 x = x_prop;
434 if step >= self.burn_in {
435 self.n_accepted += 1;
436 }
437 }
438 if step >= self.burn_in && (step - self.burn_in).is_multiple_of(self.thinning) {
439 chain.push(x);
440 }
441 }
442 let _ = n_proposed;
443 chain
444 }
445
446 pub fn acceptance_rate(&self, n_total: usize) -> f64 {
448 if n_total == 0 {
449 0.0
450 } else {
451 self.n_accepted as f64 / n_total as f64
452 }
453 }
454
455 pub fn diagnostics(&self, chain: &[f64]) -> (f64, f64, f64) {
457 let est = Estimator::new(chain.to_vec());
458 (est.mean(), est.variance(), est.effective_sample_size())
459 }
460}
461
462pub struct HamiltonianMC {
471 rng: LcgRng,
472 pub step_size: f64,
474 pub n_leapfrog: usize,
476 pub burn_in: usize,
478 pub n_accepted: usize,
480}
481
482impl HamiltonianMC {
483 pub fn new(seed: u64, step_size: f64, n_leapfrog: usize, burn_in: usize) -> Self {
485 Self {
486 rng: LcgRng::new(seed),
487 step_size,
488 n_leapfrog,
489 burn_in,
490 n_accepted: 0,
491 }
492 }
493
494 pub fn kinetic_energy(p: f64) -> f64 {
496 0.5 * p * p
497 }
498
499 pub fn leapfrog<F>(&self, q0: f64, p0: f64, grad_u: F) -> (f64, f64)
503 where
504 F: Fn(f64) -> f64,
505 {
506 let eps = self.step_size;
507 let mut q = q0;
508 let mut p = p0;
509 p -= 0.5 * eps * grad_u(q);
511 for _ in 0..self.n_leapfrog {
512 q += eps * p;
513 let is_last = false;
514 let _ = is_last;
515 p -= eps * grad_u(q);
516 }
517 p += eps * grad_u(q);
519 p -= 0.5 * eps * grad_u(q);
520 (q, p)
521 }
522
523 pub fn sample<F, G>(&mut self, q0: f64, n_samples: usize, log_target: F, grad_u: G) -> Vec<f64>
528 where
529 F: Fn(f64) -> f64,
530 G: Fn(f64) -> f64,
531 {
532 let total = self.burn_in + n_samples;
533 let mut q = q0;
534 let mut chain = Vec::with_capacity(n_samples);
535 self.n_accepted = 0;
536
537 for step in 0..total {
538 let p = self.rng.next_normal();
539 let h_current = -log_target(q) + Self::kinetic_energy(p);
540 let (q_prop, p_prop) = self.leapfrog(q, p, &grad_u);
541 let h_prop = -log_target(q_prop) + Self::kinetic_energy(p_prop);
542 let delta_h = h_prop - h_current;
543 let u = self.rng.next_f64().max(1e-15).ln();
544 if u <= -delta_h.max(0.0) || delta_h <= 0.0 {
545 q = q_prop;
546 if step >= self.burn_in {
547 self.n_accepted += 1;
548 }
549 }
550 if step >= self.burn_in {
551 chain.push(q);
552 }
553 }
554 chain
555 }
556}
557
558pub struct MarkovChainMC {
566 rng: LcgRng,
567 pub step_size: f64,
569}
570
571impl MarkovChainMC {
572 pub fn new(seed: u64, step_size: f64) -> Self {
574 Self {
575 rng: LcgRng::new(seed),
576 step_size,
577 }
578 }
579
580 pub fn metropolis_hastings<F>(
582 &mut self,
583 x0: f64,
584 n_samples: usize,
585 burn_in: usize,
586 log_target: F,
587 ) -> Vec<f64>
588 where
589 F: Fn(f64) -> f64,
590 {
591 let mut mh = MetropolisHastings::new(42, self.step_size, burn_in, 1);
592 mh.sample(x0, n_samples, log_target)
593 }
594
595 pub fn gibbs_2d<F, G>(
599 &mut self,
600 x0: f64,
601 y0: f64,
602 n_samples: usize,
603 burn_in: usize,
604 sample_x: F,
605 sample_y: G,
606 ) -> Vec<(f64, f64)>
607 where
608 F: Fn(f64, &mut LcgRng) -> f64, G: Fn(f64, &mut LcgRng) -> f64, {
611 let total = burn_in + n_samples;
612 let mut chain = Vec::with_capacity(n_samples);
613 let mut state = (x0, y0);
615 for step in 0..total {
616 let new_x = sample_x(state.1, &mut self.rng);
617 let new_y = sample_y(new_x, &mut self.rng);
618 state = (new_x, new_y);
619 if step >= burn_in {
620 chain.push(state);
621 }
622 }
623 chain
624 }
625
626 pub fn mala<F, G>(
630 &mut self,
631 x0: f64,
632 n_samples: usize,
633 eps: f64,
634 log_target: F,
635 grad_log_target: G,
636 ) -> Vec<f64>
637 where
638 F: Fn(f64) -> f64,
639 G: Fn(f64) -> f64,
640 {
641 let mut x = x0;
642 let mut chain = Vec::with_capacity(n_samples);
643 for _ in 0..n_samples {
644 let g = grad_log_target(x);
645 let x_prop = x + 0.5 * eps * eps * g + eps * self.rng.next_normal();
646 let log_alpha = log_target(x_prop) - log_target(x);
648 if self.rng.next_f64().max(1e-15).ln() <= log_alpha.min(0.0) {
649 x = x_prop;
650 }
651 chain.push(x);
652 }
653 chain
654 }
655}
656
657pub struct MonteCarloIntegral {
663 rng: LcgRng,
664 pub n_samples: usize,
666 pub dim: usize,
668}
669
670impl MonteCarloIntegral {
671 pub fn new(seed: u64, n_samples: usize, dim: usize) -> Self {
673 Self {
674 rng: LcgRng::new(seed),
675 n_samples,
676 dim,
677 }
678 }
679
680 pub fn integrate_plain<F>(&mut self, f: F) -> (f64, f64)
684 where
685 F: Fn(&[f64]) -> f64,
686 {
687 let n = self.n_samples;
688 let d = self.dim;
689 let mut sum = 0.0;
690 let mut sum_sq = 0.0;
691 let mut x = vec![0.0_f64; d];
692 for _ in 0..n {
693 for xi in x.iter_mut() {
694 *xi = self.rng.next_f64();
695 }
696 let val = f(&x);
697 sum += val;
698 sum_sq += val * val;
699 }
700 let mean = sum / n as f64;
701 let var = (sum_sq / n as f64 - mean * mean).max(0.0);
702 (mean, (var / n as f64).sqrt())
703 }
704
705 pub fn integrate_stratified<F>(&mut self, f: F, k: usize) -> (f64, f64)
709 where
710 F: Fn(&[f64]) -> f64,
711 {
712 let d = self.dim;
713 let n_strata = k.pow(d as u32);
714 let mut sum = 0.0;
715 let mut sum_sq = 0.0;
716 let vol = 1.0 / n_strata as f64;
717 for s in 0..n_strata {
718 let mut x = vec![0.0_f64; d];
719 let mut idx = s;
720 for j in 0..d {
721 let cell = idx % k;
722 idx /= k;
723 x[j] = (cell as f64 + self.rng.next_f64()) / k as f64;
724 }
725 let val = f(&x) * vol * n_strata as f64;
726 sum += val;
727 sum_sq += val * val;
728 }
729 let mean = sum / n_strata as f64;
730 let var = (sum_sq / n_strata as f64 - mean * mean).max(0.0);
731 (
732 mean * vol * n_strata as f64 / n_strata as f64,
733 (var / n_strata as f64).sqrt(),
734 )
735 }
736
737 pub fn integrate_lhs<F>(&mut self, f: F) -> (f64, f64)
741 where
742 F: Fn(&[f64]) -> f64,
743 {
744 let n = self.n_samples;
745 let d = self.dim;
746 let mut perm: Vec<Vec<usize>> = (0..d)
748 .map(|_| {
749 let mut p: Vec<usize> = (0..n).collect();
750 for i in (1..n).rev() {
752 let j = (self.rng.next_u64() as usize) % (i + 1);
753 p.swap(i, j);
754 }
755 p
756 })
757 .collect();
758 let mut sum = 0.0;
759 let mut sum_sq = 0.0;
760 for i in 0..n {
761 let x: Vec<f64> = (0..d)
762 .map(|j| (perm[j][i] as f64 + self.rng.next_f64()) / n as f64)
763 .collect();
764 let val = f(&x);
765 sum += val;
766 sum_sq += val * val;
767 let _ = &mut perm; }
769 let mean = sum / n as f64;
770 let var = (sum_sq / n as f64 - mean * mean).max(0.0);
771 (mean, (var / n as f64).sqrt())
772 }
773
774 pub fn integrate_halton<F>(&mut self, f: F) -> (f64, f64)
778 where
779 F: Fn(&[f64]) -> f64,
780 {
781 let primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
782 let n = self.n_samples;
783 let d = self.dim.min(primes.len());
784 let mut sum = 0.0;
785 let mut sum_sq = 0.0;
786 for i in 1..=n {
787 let x: Vec<f64> = (0..d).map(|j| halton_seq(i, primes[j])).collect();
788 let val = f(&x);
789 sum += val;
790 sum_sq += val * val;
791 }
792 let mean = sum / n as f64;
793 let var = (sum_sq / n as f64 - mean * mean).max(0.0);
794 (mean, (var / n as f64).sqrt())
795 }
796}
797
798fn halton_seq(mut i: usize, base: usize) -> f64 {
800 let mut f = 1.0_f64;
801 let mut r = 0.0_f64;
802 let b = base as f64;
803 while i > 0 {
804 f /= b;
805 r += f * (i % base) as f64;
806 i /= base;
807 }
808 r
809}
810
811pub struct BootstrapCI {
819 rng: LcgRng,
820 pub n_boot: usize,
822}
823
824impl BootstrapCI {
825 pub fn new(seed: u64, n_boot: usize) -> Self {
827 Self {
828 rng: LcgRng::new(seed),
829 n_boot,
830 }
831 }
832
833 pub fn resample(&mut self, data: &[f64]) -> Vec<f64> {
835 let n = data.len();
836 (0..n)
837 .map(|_| data[self.rng.next_u64() as usize % n])
838 .collect()
839 }
840
841 pub fn percentile_ci(&mut self, data: &[f64], alpha: f64) -> (f64, f64) {
845 let n_boot = self.n_boot;
846 let mut boot_means: Vec<f64> = (0..n_boot)
847 .map(|_| {
848 let resamp = self.resample(data);
849 resamp.iter().sum::<f64>() / resamp.len() as f64
850 })
851 .collect();
852 boot_means.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
853 let lo_idx = ((alpha / 2.0 * n_boot as f64) as usize).min(n_boot - 1);
854 let hi_idx = (((1.0 - alpha / 2.0) * n_boot as f64) as usize).min(n_boot - 1);
855 (boot_means[lo_idx], boot_means[hi_idx])
856 }
857
858 pub fn bca_ci(&mut self, data: &[f64], alpha: f64) -> (f64, f64) {
862 let n = data.len();
863 let n_boot = self.n_boot;
864 let theta_hat = data.iter().sum::<f64>() / n as f64;
865
866 let mut boot_stats: Vec<f64> = (0..n_boot)
868 .map(|_| {
869 let resamp = self.resample(data);
870 resamp.iter().sum::<f64>() / resamp.len() as f64
871 })
872 .collect();
873 boot_stats.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
874
875 let count_less = boot_stats.iter().filter(|&&b| b < theta_hat).count();
877 let p0 = (count_less as f64 + 0.5) / (n_boot as f64 + 1.0);
878 let z0 = inverse_cdf(p0.clamp(0.001, 0.999));
879
880 let jack_means: Vec<f64> = (0..n)
882 .map(|i| {
883 let jack_sum: f64 = data
884 .iter()
885 .enumerate()
886 .filter(|&(j, _)| j != i)
887 .map(|(_, &x)| x)
888 .sum();
889 jack_sum / (n - 1) as f64
890 })
891 .collect();
892 let jack_mean_mean = jack_means.iter().sum::<f64>() / n as f64;
893 let numer: f64 = jack_means
894 .iter()
895 .map(|&m| (jack_mean_mean - m).powi(3))
896 .sum();
897 let denom: f64 = 6.0
898 * jack_means
899 .iter()
900 .map(|&m| (jack_mean_mean - m).powi(2))
901 .sum::<f64>()
902 .powf(1.5);
903 let a = if denom.abs() < 1e-15 {
904 0.0
905 } else {
906 numer / denom
907 };
908
909 let adjust_quantile = |p: f64| -> f64 {
910 let z_p = inverse_cdf(p.clamp(0.001, 0.999));
911 let inner = z0 + (z0 + z_p) / (1.0 - a * (z0 + z_p));
912 0.5 * (1.0 + libm_erf(inner / 2.0_f64.sqrt()))
915 };
916
917 let p_lo = adjust_quantile(alpha / 2.0);
918 let p_hi = adjust_quantile(1.0 - alpha / 2.0);
919 let lo_idx = ((p_lo * n_boot as f64) as usize).min(n_boot - 1);
920 let hi_idx = ((p_hi * n_boot as f64) as usize).min(n_boot - 1);
921 (boot_stats[lo_idx], boot_stats[hi_idx])
922 }
923}
924
925fn libm_erf(x: f64) -> f64 {
927 let t = 1.0 / (1.0 + 0.3275911 * x.abs());
928 let poly = t
929 * (0.254829592
930 + t * (-0.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429))));
931 let sign = if x >= 0.0 { 1.0 } else { -1.0 };
932 sign * (1.0 - poly * (-x * x).exp())
933}
934
935pub struct AnisotropicMC {
943 rng: LcgRng,
944 pub kappa: f64,
946 pub mu: [f64; 3],
948}
949
950impl AnisotropicMC {
951 pub fn new(seed: u64, kappa: f64, mu: [f64; 3]) -> Self {
953 Self {
954 rng: LcgRng::new(seed),
955 kappa,
956 mu,
957 }
958 }
959
960 pub fn sample_vmf(&mut self) -> [f64; 3] {
964 let kappa = self.kappa;
965 let cos_theta = if kappa < 1e-6 {
967 2.0 * self.rng.next_f64() - 1.0
968 } else {
969 let b = -kappa + (kappa * kappa + 1.0).sqrt();
970 let x0 = (1.0 - b) / (1.0 + b);
971 let c = kappa * x0 + 2.0 * (1.0 + b).ln() - (1.0 + x0).ln();
972 loop {
973 let z = self.rng.next_f64();
974 let w = (1.0 - (1.0 + b) * z) / (1.0 - (1.0 - b) * z);
975 let t = 2.0 * kappa * w / (1.0 + b * w);
976 let u = self.rng.next_f64();
977 if u.max(1e-15).ln() + c - t <= 0.0 {
978 break w;
979 }
980 }
981 };
982 let sin_theta = (1.0 - cos_theta * cos_theta).max(0.0).sqrt();
983 let phi = 2.0 * PI * self.rng.next_f64();
984 let v = [sin_theta * phi.cos(), sin_theta * phi.sin(), cos_theta];
986 self.rotate_to_mu(v)
987 }
988
989 fn rotate_to_mu(&self, v: [f64; 3]) -> [f64; 3] {
990 let mu = self.mu;
992 let norm_mu = (mu[0] * mu[0] + mu[1] * mu[1] + mu[2] * mu[2])
993 .sqrt()
994 .max(1e-15);
995 let mu_hat = [mu[0] / norm_mu, mu[1] / norm_mu, mu[2] / norm_mu];
996 if (mu_hat[2] - 1.0).abs() < 1e-10 {
998 return v;
999 }
1000 if (mu_hat[2] + 1.0).abs() < 1e-10 {
1001 return [-v[0], -v[1], -v[2]];
1002 }
1003 let k = [mu_hat[1], -mu_hat[0], 0.0];
1005 let k_norm = (k[0] * k[0] + k[1] * k[1]).sqrt().max(1e-15);
1006 let k_hat = [k[0] / k_norm, k[1] / k_norm, 0.0];
1007 let cos_a = mu_hat[2];
1008 let sin_a = (1.0 - cos_a * cos_a).sqrt();
1009 let k_cross_v = [
1011 k_hat[1] * v[2] - k_hat[2] * v[1],
1012 k_hat[2] * v[0] - k_hat[0] * v[2],
1013 k_hat[0] * v[1] - k_hat[1] * v[0],
1014 ];
1015 let k_dot_v = k_hat[0] * v[0] + k_hat[1] * v[1] + k_hat[2] * v[2];
1016 [
1017 v[0] * cos_a + k_cross_v[0] * sin_a + k_hat[0] * k_dot_v * (1.0 - cos_a),
1018 v[1] * cos_a + k_cross_v[1] * sin_a + k_hat[1] * k_dot_v * (1.0 - cos_a),
1019 v[2] * cos_a + k_cross_v[2] * sin_a + k_hat[2] * k_dot_v * (1.0 - cos_a),
1020 ]
1021 }
1022}
1023
1024pub struct BrownianBridge {
1032 rng: LcgRng,
1033 pub sigma: f64,
1035}
1036
1037impl BrownianBridge {
1038 pub fn new(seed: u64, sigma: f64) -> Self {
1040 Self {
1041 rng: LcgRng::new(seed),
1042 sigma,
1043 }
1044 }
1045
1046 pub fn brownian_bridge_sample(&mut self, n: usize, a: f64, b: f64, t_end: f64) -> Vec<f64> {
1050 let total = n + 2;
1051 let dt = t_end / (n + 1) as f64;
1052 let mut path = vec![0.0_f64; total];
1053 path[0] = a;
1054 path[total - 1] = b;
1055 let mut free = vec![a];
1057 for _ in 1..total {
1058 let prev = *free.last().expect("free path is non-empty");
1059 free.push(prev + self.sigma * dt.sqrt() * self.rng.next_normal());
1060 }
1061 for i in 1..total - 1 {
1063 let t = i as f64 * dt;
1064 let t_rem = t_end - t;
1065 let mean_bridge = (t_rem * a + t * b) / t_end;
1066 let free_mean = a + (free[i] - a) * t / (total - 1) as f64;
1067 let _ = (free_mean, t_rem);
1068 path[i] = mean_bridge + (free[i] - free[0] - (free[total - 1] - free[0]) * t / t_end);
1069 }
1070 path
1071 }
1072
1073 pub fn ornstein_uhlenbeck(
1077 &mut self,
1078 x0: f64,
1079 theta: f64,
1080 mu: f64,
1081 dt: f64,
1082 n: usize,
1083 ) -> Vec<f64> {
1084 let sigma = self.sigma;
1085 let mut path = Vec::with_capacity(n);
1086 let mut x = x0;
1087 let exp_dt = (-theta * dt).exp();
1088 let std_step = sigma * ((1.0 - exp_dt * exp_dt) / (2.0 * theta)).sqrt().max(0.0);
1089 for _ in 0..n {
1090 x = mu + (x - mu) * exp_dt + std_step * self.rng.next_normal();
1091 path.push(x);
1092 }
1093 path
1094 }
1095}
1096
1097pub struct ParticleFilter {
1105 rng: LcgRng,
1106 pub n_particles: usize,
1108 pub particles: Vec<f64>,
1110 pub weights: Vec<f64>,
1112}
1113
1114impl ParticleFilter {
1115 pub fn new(seed: u64, n_particles: usize, mu0: f64, sigma0: f64) -> Self {
1117 let mut rng = LcgRng::new(seed);
1118 let particles: Vec<f64> = (0..n_particles)
1119 .map(|_| mu0 + sigma0 * rng.next_normal())
1120 .collect();
1121 let weights = vec![1.0 / n_particles as f64; n_particles];
1122 Self {
1123 rng,
1124 n_particles,
1125 particles,
1126 weights,
1127 }
1128 }
1129
1130 pub fn predict<F>(&mut self, dynamics: F, noise_std: f64)
1132 where
1133 F: Fn(f64) -> f64,
1134 {
1135 for x in self.particles.iter_mut() {
1136 *x = dynamics(*x) + noise_std * self.rng.next_normal();
1137 }
1138 }
1139
1140 pub fn update<F>(&mut self, y: f64, likelihood: F)
1144 where
1145 F: Fn(f64, f64) -> f64,
1146 {
1147 for (x, w) in self.particles.iter().zip(self.weights.iter_mut()) {
1148 *w *= likelihood(*x, y);
1149 }
1150 self.normalize_weights();
1151 }
1152
1153 pub fn normalize_weights(&mut self) {
1155 let sum: f64 = self.weights.iter().sum();
1156 if sum > 1e-300 {
1157 for w in self.weights.iter_mut() {
1158 *w /= sum;
1159 }
1160 } else {
1161 let uniform = 1.0 / self.n_particles as f64;
1162 for w in self.weights.iter_mut() {
1163 *w = uniform;
1164 }
1165 }
1166 }
1167
1168 pub fn resample_systematic(&mut self) {
1170 let n = self.n_particles;
1171 let mut cumulative = vec![0.0_f64; n + 1];
1172 for i in 0..n {
1173 cumulative[i + 1] = cumulative[i] + self.weights[i];
1174 }
1175 let u = self.rng.next_f64() / n as f64;
1176 let mut new_particles = Vec::with_capacity(n);
1177 let mut j = 0;
1178 for i in 0..n {
1179 let threshold = u + i as f64 / n as f64;
1180 while j < n && cumulative[j + 1] < threshold {
1181 j += 1;
1182 }
1183 new_particles.push(self.particles[j.min(n - 1)]);
1184 }
1185 self.particles = new_particles;
1186 let uniform = 1.0 / n as f64;
1187 self.weights.fill(uniform);
1188 }
1189
1190 pub fn resample_multinomial(&mut self) {
1192 let n = self.n_particles;
1193 let mut cumulative = vec![0.0_f64; n + 1];
1194 for i in 0..n {
1195 cumulative[i + 1] = cumulative[i] + self.weights[i];
1196 }
1197 let mut new_particles = Vec::with_capacity(n);
1198 for _ in 0..n {
1199 let u = self.rng.next_f64();
1200 let idx = cumulative
1201 .partition_point(|&c| c < u)
1202 .saturating_sub(1)
1203 .min(n - 1);
1204 new_particles.push(self.particles[idx]);
1205 }
1206 self.particles = new_particles;
1207 let uniform = 1.0 / n as f64;
1208 self.weights.fill(uniform);
1209 }
1210
1211 pub fn estimate_mean(&self) -> f64 {
1213 self.particles
1214 .iter()
1215 .zip(self.weights.iter())
1216 .map(|(&x, &w)| x * w)
1217 .sum()
1218 }
1219
1220 pub fn ess(&self) -> f64 {
1222 effective_sample_size(&self.weights)
1223 }
1224}
1225
1226#[cfg(test)]
1231mod tests {
1232 use super::*;
1233
1234 #[test]
1235 fn test_box_muller_finite() {
1236 let v = box_muller_normal(0.5, 0.25);
1237 assert!(v.is_finite());
1238 }
1239
1240 #[test]
1241 fn test_inverse_cdf_symmetry() {
1242 let x = inverse_cdf(0.975);
1243 assert!((x - 1.96).abs() < 0.01, "x={}", x);
1244 let y = inverse_cdf(0.025);
1245 assert!((y + 1.96).abs() < 0.01, "y={}", y);
1246 }
1247
1248 #[test]
1249 fn test_effective_sample_size_uniform() {
1250 let w = vec![0.25_f64; 4];
1251 let ess = effective_sample_size(&w);
1252 assert!((ess - 4.0).abs() < 1e-10);
1253 }
1254
1255 #[test]
1256 fn test_effective_sample_size_degenerate() {
1257 let mut w = vec![0.0_f64; 10];
1258 w[0] = 1.0;
1259 let ess = effective_sample_size(&w);
1260 assert!((ess - 1.0).abs() < 1e-10);
1261 }
1262
1263 #[test]
1264 fn test_autocorrelation_lag0() {
1265 let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1266 let r0 = autocorrelation(&x, 0);
1267 assert!((r0 - 1.0).abs() < 1e-10);
1268 }
1269
1270 #[test]
1271 fn test_autocorrelation_constant() {
1272 let x = vec![3.0_f64; 10];
1273 let r = autocorrelation(&x, 1);
1274 assert!((r - 1.0).abs() < 1e-10);
1276 }
1277
1278 #[test]
1279 fn test_mc_sampler_uniform_range() {
1280 let mut s = McSampler::new(42);
1281 for _ in 0..100 {
1282 let x = s.uniform_range(2.0, 5.0);
1283 assert!((2.0..5.0).contains(&x));
1284 }
1285 }
1286
1287 #[test]
1288 fn test_mc_sampler_normal_stats() {
1289 let mut s = McSampler::new(123);
1290 let samples: Vec<f64> = (0..5000).map(|_| s.normal(0.0, 1.0)).collect();
1291 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
1292 let var = samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / samples.len() as f64;
1293 assert!(mean.abs() < 0.1, "mean={}", mean);
1294 assert!((var - 1.0).abs() < 0.1, "var={}", var);
1295 }
1296
1297 #[test]
1298 fn test_mc_sampler_exponential_positive() {
1299 let mut s = McSampler::new(7);
1300 for _ in 0..100 {
1301 assert!(s.exponential(1.0) > 0.0);
1302 }
1303 }
1304
1305 #[test]
1306 fn test_mc_sampler_poisson_mean() {
1307 let mut s = McSampler::new(99);
1308 let lambda = 3.0;
1309 let samples: Vec<f64> = (0..2000).map(|_| s.poisson(lambda) as f64).collect();
1310 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
1311 assert!((mean - lambda).abs() < 0.2, "mean={}", mean);
1312 }
1313
1314 #[test]
1315 fn test_mc_sampler_beta_range() {
1316 let mut s = McSampler::new(55);
1317 for _ in 0..100 {
1318 let x = s.beta(2.0, 3.0);
1319 assert!((0.0..=1.0).contains(&x));
1320 }
1321 }
1322
1323 #[test]
1324 fn test_mc_sampler_gamma_positive() {
1325 let mut s = McSampler::new(77);
1326 for _ in 0..100 {
1327 assert!(s.gamma(2.0, 1.0) > 0.0);
1328 }
1329 }
1330
1331 #[test]
1332 fn test_estimator_mean_variance() {
1333 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1334 let est = Estimator::new(data);
1335 assert!((est.mean() - 3.0).abs() < 1e-12);
1336 assert!((est.variance() - 2.5).abs() < 1e-12);
1337 }
1338
1339 #[test]
1340 fn test_estimator_standard_error() {
1341 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1342 let est = Estimator::new(data);
1343 let se = est.standard_error();
1344 assert!(se > 0.0);
1345 }
1346
1347 #[test]
1348 fn test_estimator_confidence_interval() {
1349 let data: Vec<f64> = (0..100).map(|i| i as f64).collect();
1350 let est = Estimator::new(data);
1351 let (lo, hi) = est.confidence_interval(0.05);
1352 assert!(lo < hi);
1353 assert!(lo < 50.0 && hi > 50.0);
1354 }
1355
1356 #[test]
1357 fn test_estimator_median_odd() {
1358 let data = vec![3.0, 1.0, 2.0, 5.0, 4.0];
1359 let est = Estimator::new(data);
1360 assert!((est.median() - 3.0).abs() < 1e-12);
1361 }
1362
1363 #[test]
1364 fn test_metropolis_hastings_normal() {
1365 let mut mh = MetropolisHastings::new(42, 1.0, 500, 1);
1366 let samples = mh.sample(0.0, 1000, |x: f64| -0.5 * x * x);
1367 let est = Estimator::new(samples);
1368 assert!(est.mean().abs() < 0.2);
1369 assert!((est.variance() - 1.0).abs() < 0.3);
1370 }
1371
1372 #[test]
1373 fn test_hamiltonian_mc_normal() {
1374 let mut hmc = HamiltonianMC::new(42, 0.3, 10, 200);
1375 let samples = hmc.sample(0.0, 500, |x: f64| -0.5 * x * x, |x: f64| x);
1376 let est = Estimator::new(samples);
1377 assert!(est.mean().abs() < 0.3);
1378 }
1379
1380 #[test]
1381 fn test_mc_integral_plain_pi() {
1382 let mut mc = MonteCarloIntegral::new(42, 10000, 2);
1384 let (est, _se) = mc.integrate_plain(|x: &[f64]| {
1385 if x[0] * x[0] + x[1] * x[1] <= 1.0 {
1386 1.0
1387 } else {
1388 0.0
1389 }
1390 });
1391 assert!((est - PI / 4.0).abs() < 0.05, "est={}", est);
1392 }
1393
1394 #[test]
1395 fn test_mc_integral_halton_constant() {
1396 let mut mc = MonteCarloIntegral::new(1, 1000, 1);
1397 let (est, _se) = mc.integrate_halton(|_: &[f64]| 1.0);
1398 assert!((est - 1.0).abs() < 0.01);
1399 }
1400
1401 #[test]
1402 fn test_bootstrap_ci_contains_mean() {
1403 let data: Vec<f64> = (0..50).map(|i| i as f64).collect();
1404 let mut boot = BootstrapCI::new(42, 500);
1405 let (lo, hi) = boot.percentile_ci(&data, 0.05);
1406 assert!(lo < 24.5 && hi > 24.5);
1407 }
1408
1409 #[test]
1410 fn test_brownian_bridge_endpoints() {
1411 let mut bb = BrownianBridge::new(42, 0.1);
1412 let path = bb.brownian_bridge_sample(10, 0.0, 1.0, 1.0);
1413 assert_eq!(path.len(), 12);
1414 assert!((path[0] - 0.0).abs() < 1e-12);
1415 assert!((path[11] - 1.0).abs() < 1e-12);
1416 }
1417
1418 #[test]
1419 fn test_ou_process_mean_reversion() {
1420 let mut bb = BrownianBridge::new(42, 0.1);
1421 let path = bb.ornstein_uhlenbeck(5.0, 1.0, 0.0, 0.1, 100);
1422 let final_val = path.last().unwrap().abs();
1424 assert!(final_val < 5.0, "final_val={}", final_val);
1425 }
1426
1427 #[test]
1428 fn test_particle_filter_mean_estimate() {
1429 let mut pf = ParticleFilter::new(42, 200, 0.0, 1.0);
1430 pf.update(2.0, |x: f64, y: f64| {
1432 let d = (x - y) / 0.5;
1433 (-0.5 * d * d).exp()
1434 });
1435 let mean = pf.estimate_mean();
1436 assert!(mean > 0.0);
1438 }
1439
1440 #[test]
1441 fn test_particle_filter_resample_systematic() {
1442 let mut pf = ParticleFilter::new(42, 50, 0.0, 1.0);
1443 pf.update(1.0, |x: f64, y: f64| (-(x - y).powi(2)).exp());
1444 let ess_before = pf.ess();
1445 pf.resample_systematic();
1446 let ess_after = pf.ess();
1447 assert!(ess_after >= ess_before || ess_after > 1.0);
1449 }
1450
1451 #[test]
1452 fn test_anisotropic_mc_unit_sphere() {
1453 let mut amc = AnisotropicMC::new(42, 2.0, [0.0, 0.0, 1.0]);
1454 for _ in 0..20 {
1455 let v = amc.sample_vmf();
1456 let norm = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
1457 assert!((norm - 1.0).abs() < 1e-10, "norm={}", norm);
1458 }
1459 }
1460
1461 #[test]
1462 fn test_mc_integral_lhs() {
1463 let mut mc = MonteCarloIntegral::new(42, 100, 1);
1464 let (est, _) = mc.integrate_lhs(|x: &[f64]| x[0]);
1465 assert!((est - 0.5).abs() < 0.1, "est={}", est);
1467 }
1468
1469 #[test]
1470 fn test_halton_sequence() {
1471 let h = halton_seq(1, 2);
1472 assert!((h - 0.5).abs() < 1e-12);
1473 let h2 = halton_seq(2, 2);
1474 assert!((h2 - 0.25).abs() < 1e-12);
1475 }
1476
1477 #[test]
1478 fn test_importance_sampler_estimate() {
1479 let mut is = ImportanceSampler::new(42, 1.0);
1480 let est = is.estimate(
1482 1000,
1483 |x: f64| x * x,
1484 |_x: f64| 0.0, );
1486 assert!((est - 1.0).abs() < 0.2, "est={}", est);
1487 }
1488}