1#![allow(clippy::needless_range_loop)]
2#![allow(dead_code)]
24#![allow(clippy::too_many_arguments)]
25
26use std::f64::consts::PI;
27
28pub(crate) struct Lcg {
35 state: u64,
36}
37
38impl Lcg {
39 fn new(seed: u64) -> Self {
40 Self {
41 state: seed.wrapping_add(1),
42 }
43 }
44
45 fn next_u64(&mut self) -> u64 {
46 self.state = self
47 .state
48 .wrapping_mul(6_364_136_223_846_793_005)
49 .wrapping_add(1_442_695_040_888_963_407);
50 self.state
51 }
52
53 fn uniform(&mut self) -> f64 {
55 (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
56 }
57
58 fn normal(&mut self) -> f64 {
60 let u1 = self.uniform().max(1e-300);
61 let u2 = self.uniform();
62 (-2.0 * u1.ln()).sqrt() * (2.0 * PI * u2).cos()
63 }
64
65 fn exponential(&mut self, lambda: f64) -> f64 {
67 let u = self.uniform().max(1e-300);
68 -u.ln() / lambda
69 }
70
71 fn poisson(&mut self, lambda: f64) -> usize {
73 if lambda <= 0.0 {
74 return 0;
75 }
76 let threshold = (-lambda).exp();
77 let mut k = 0usize;
78 let mut p = 1.0_f64;
79 loop {
80 p *= self.uniform();
81 if p < threshold {
82 break;
83 }
84 k += 1;
85 }
86 k
87 }
88
89 fn bernoulli(&mut self, p: f64) -> bool {
91 self.uniform() < p
92 }
93}
94
95pub struct BrownianMotion {
102 pub dt: f64,
104 pub sigma: f64,
106 seed: u64,
107}
108
109impl BrownianMotion {
110 pub fn new(dt: f64, seed: u64) -> Self {
112 Self {
113 dt,
114 sigma: 1.0,
115 seed,
116 }
117 }
118
119 pub fn with_sigma(dt: f64, sigma: f64, seed: u64) -> Self {
121 Self { dt, sigma, seed }
122 }
123
124 fn increment(&self, rng: &mut Lcg) -> f64 {
126 self.sigma * self.dt.sqrt() * rng.normal()
127 }
128
129 pub fn path(&self, n_steps: usize) -> Vec<f64> {
133 let mut rng = Lcg::new(self.seed);
134 let mut w = vec![0.0_f64; n_steps + 1];
135 for i in 1..=n_steps {
136 w[i] = w[i - 1] + self.increment(&mut rng);
137 }
138 w
139 }
140
141 pub fn variance_at(&self, t: f64) -> f64 {
145 self.sigma * self.sigma * t
146 }
147
148 pub fn multi_path(&self, n_paths: usize, n_steps: usize) -> Vec<Vec<f64>> {
150 (0..n_paths)
151 .map(|i| {
152 let mut bm = BrownianMotion::with_sigma(
153 self.dt,
154 self.sigma,
155 self.seed ^ (i as u64 * 2654435761),
156 );
157 bm.seed ^= i as u64;
158 bm.path(n_steps)
159 })
160 .collect()
161 }
162
163 pub fn quadratic_variation(path: &[f64]) -> f64 {
165 path.windows(2).map(|w| (w[1] - w[0]).powi(2)).sum()
166 }
167}
168
169pub struct GeometricBrownianMotion {
176 pub s0: f64,
178 pub mu: f64,
180 pub sigma: f64,
182 seed: u64,
183}
184
185impl GeometricBrownianMotion {
186 pub fn new(s0: f64, mu: f64, sigma: f64, seed: u64) -> Self {
188 Self {
189 s0,
190 mu,
191 sigma,
192 seed,
193 }
194 }
195
196 pub fn exact_path(&self, t_end: f64, n_steps: usize) -> Vec<f64> {
200 let dt = t_end / n_steps as f64;
201 let mut rng = Lcg::new(self.seed);
202 let mut path = vec![0.0_f64; n_steps + 1];
203 path[0] = self.s0;
204 let drift = (self.mu - 0.5 * self.sigma * self.sigma) * dt;
205 let vol = self.sigma * dt.sqrt();
206 let mut log_s = self.s0.ln();
207 for i in 1..=n_steps {
208 log_s += drift + vol * rng.normal();
209 path[i] = log_s.exp();
210 }
211 path
212 }
213
214 pub fn euler_path(&self, t_end: f64, n_steps: usize) -> Vec<f64> {
216 let dt = t_end / n_steps as f64;
217 let mut rng = Lcg::new(self.seed);
218 let mut path = vec![0.0_f64; n_steps + 1];
219 path[0] = self.s0;
220 for i in 1..=n_steps {
221 let s = path[i - 1];
222 let dw = dt.sqrt() * rng.normal();
223 path[i] = s + self.mu * s * dt + self.sigma * s * dw;
224 if path[i] < 0.0 {
225 path[i] = 0.0;
226 }
227 }
228 path
229 }
230
231 pub fn mean_at(&self, t: f64) -> f64 {
233 self.s0 * (self.mu * t).exp()
234 }
235
236 pub fn variance_at(&self, t: f64) -> f64 {
238 let e2mu = (2.0 * self.mu * t).exp();
239 let esig2 = (self.sigma * self.sigma * t).exp() - 1.0;
240 self.s0 * self.s0 * e2mu * esig2
241 }
242}
243
244pub struct OrnsteinUhlenbeck {
253 pub theta: f64,
255 pub mu: f64,
257 pub sigma: f64,
259 pub x0: f64,
261 seed: u64,
262}
263
264impl OrnsteinUhlenbeck {
265 pub fn new(theta: f64, mu: f64, sigma: f64, x0: f64, seed: u64) -> Self {
267 Self {
268 theta,
269 mu,
270 sigma,
271 x0,
272 seed,
273 }
274 }
275
276 pub fn path(&self, t_end: f64, n_steps: usize) -> Vec<f64> {
278 let dt = t_end / n_steps as f64;
279 let mut rng = Lcg::new(self.seed);
280 let mut path = vec![0.0_f64; n_steps + 1];
281 path[0] = self.x0;
282 let exp_neg = (-self.theta * dt).exp();
283 let var =
284 self.sigma * self.sigma * (1.0 - (-2.0 * self.theta * dt).exp()) / (2.0 * self.theta);
285 let std_dev = var.sqrt();
286 for i in 1..=n_steps {
287 let mean = path[i - 1] * exp_neg + self.mu * (1.0 - exp_neg);
288 path[i] = mean + std_dev * rng.normal();
289 }
290 path
291 }
292
293 pub fn stationary_variance(&self) -> f64 {
295 self.sigma * self.sigma / (2.0 * self.theta)
296 }
297
298 pub fn autocorrelation(&self, tau: f64) -> f64 {
300 (-self.theta * tau).exp()
301 }
302}
303
304pub struct JumpDiffusion {
311 pub s0: f64,
313 pub mu: f64,
315 pub sigma: f64,
317 pub lambda: f64,
319 pub mu_j: f64,
321 pub sigma_j: f64,
323 seed: u64,
324}
325
326impl JumpDiffusion {
327 pub fn new(
329 s0: f64,
330 mu: f64,
331 sigma: f64,
332 lambda: f64,
333 mu_j: f64,
334 sigma_j: f64,
335 seed: u64,
336 ) -> Self {
337 Self {
338 s0,
339 mu,
340 sigma,
341 lambda,
342 mu_j,
343 sigma_j,
344 seed,
345 }
346 }
347
348 pub fn expected_jump(&self) -> f64 {
350 (self.mu_j + 0.5 * self.sigma_j * self.sigma_j).exp()
351 }
352
353 pub fn jump_compensation(&self) -> f64 {
355 self.expected_jump() - 1.0
356 }
357
358 pub fn path(&self, t_end: f64, n_steps: usize) -> Vec<f64> {
360 let dt = t_end / n_steps as f64;
361 let mut rng = Lcg::new(self.seed);
362 let mut path = vec![0.0_f64; n_steps + 1];
363 path[0] = self.s0;
364 let k_bar = self.jump_compensation();
365 let compensated_mu = self.mu - self.lambda * k_bar;
366 for i in 1..=n_steps {
367 let s = path[i - 1];
368 let dw = dt.sqrt() * rng.normal();
369 let n_jumps = rng.poisson(self.lambda * dt);
370 let mut jump_factor = 1.0_f64;
371 for _ in 0..n_jumps {
372 let log_j = self.mu_j + self.sigma_j * rng.normal();
373 jump_factor *= log_j.exp();
374 }
375 path[i] = s * (1.0 + compensated_mu * dt + self.sigma * dw) * jump_factor;
376 if path[i] < 0.0 {
377 path[i] = 0.0;
378 }
379 }
380 path
381 }
382}
383
384pub struct FractionalBrownianMotion {
394 pub hurst: f64,
396 pub sigma: f64,
398 seed: u64,
399}
400
401impl FractionalBrownianMotion {
402 pub fn new(hurst: f64, seed: u64) -> Self {
407 assert!(
408 hurst > 0.0 && hurst < 1.0,
409 "Hurst exponent must be in (0, 1)"
410 );
411 Self {
412 hurst,
413 sigma: 1.0,
414 seed,
415 }
416 }
417
418 pub fn with_sigma(hurst: f64, sigma: f64, seed: u64) -> Self {
420 assert!(
421 hurst > 0.0 && hurst < 1.0,
422 "Hurst exponent must be in (0, 1)"
423 );
424 Self { hurst, sigma, seed }
425 }
426
427 pub fn covariance(&self, s: f64, t: f64) -> f64 {
429 let h2 = 2.0 * self.hurst;
430 0.5 * self.sigma
431 * self.sigma
432 * (s.abs().powf(h2) + t.abs().powf(h2) - (s - t).abs().powf(h2))
433 }
434
435 pub fn path(&self, n_steps: usize) -> Vec<f64> {
439 let n = n_steps + 1;
440 let dt = 1.0 / n_steps as f64;
441
442 let times: Vec<f64> = (0..n).map(|i| i as f64 * dt).collect();
444 let mut cov = vec![0.0_f64; n * n];
445 for i in 0..n {
446 for j in 0..n {
447 cov[i * n + j] = self.covariance(times[i], times[j]);
448 }
449 }
450
451 let mut l = vec![0.0_f64; n * n];
453 for i in 0..n {
454 for j in 0..=i {
455 let mut sum = cov[i * n + j];
456 for k in 0..j {
457 sum -= l[i * n + k] * l[j * n + k];
458 }
459 if i == j {
460 l[i * n + j] = sum.max(0.0).sqrt();
461 } else if l[j * n + j].abs() > 1e-14 {
462 l[i * n + j] = sum / l[j * n + j];
463 }
464 }
465 }
466
467 let mut rng = Lcg::new(self.seed);
469 let z: Vec<f64> = (0..n).map(|_| rng.normal()).collect();
470 let mut x = vec![0.0_f64; n];
471 for i in 0..n {
472 for j in 0..=i {
473 x[i] += l[i * n + j] * z[j];
474 }
475 }
476 x
477 }
478}
479
480pub struct CoxIngersollRoss {
489 pub kappa: f64,
491 pub theta: f64,
493 pub sigma: f64,
495 pub r0: f64,
497 seed: u64,
498}
499
500impl CoxIngersollRoss {
501 pub fn new(kappa: f64, theta: f64, sigma: f64, r0: f64, seed: u64) -> Self {
503 Self {
504 kappa,
505 theta,
506 sigma,
507 r0,
508 seed,
509 }
510 }
511
512 pub fn feller_satisfied(&self) -> bool {
514 2.0 * self.kappa * self.theta >= self.sigma * self.sigma
515 }
516
517 pub fn path(&self, t_end: f64, n_steps: usize) -> Vec<f64> {
521 let dt = t_end / n_steps as f64;
522 let mut rng = Lcg::new(self.seed);
523 let mut path = vec![0.0_f64; n_steps + 1];
524 path[0] = self.r0;
525 let sqrt_dt = dt.sqrt();
526 for i in 1..=n_steps {
527 let r = path[i - 1].max(0.0);
528 let dw = sqrt_dt * rng.normal();
529 let sqrt_r = r.sqrt();
530 let milstein_correction = 0.25 * self.sigma * self.sigma * (dw * dw - dt);
532 path[i] = (r
533 + self.kappa * (self.theta - r) * dt
534 + self.sigma * sqrt_r * dw
535 + milstein_correction)
536 .abs();
537 }
538 path
539 }
540
541 pub fn stationary_mean(&self) -> f64 {
543 self.theta
544 }
545
546 pub fn stationary_variance(&self) -> f64 {
548 self.sigma * self.sigma * self.theta / (2.0 * self.kappa)
549 }
550}
551
552pub struct HestonModel {
561 pub s0: f64,
563 pub v0: f64,
565 pub mu: f64,
567 pub kappa: f64,
569 pub theta: f64,
571 pub xi: f64,
573 pub rho: f64,
575 seed: u64,
576}
577
578impl HestonModel {
579 pub fn new(
581 s0: f64,
582 v0: f64,
583 mu: f64,
584 kappa: f64,
585 theta: f64,
586 xi: f64,
587 rho: f64,
588 seed: u64,
589 ) -> Self {
590 Self {
591 s0,
592 v0,
593 mu,
594 kappa,
595 theta,
596 xi,
597 rho,
598 seed,
599 }
600 }
601
602 pub fn path(&self, t_end: f64, n_steps: usize) -> (Vec<f64>, Vec<f64>) {
606 let dt = t_end / n_steps as f64;
607 let sqrt_dt = dt.sqrt();
608 let mut rng = Lcg::new(self.seed);
609 let mut s_path = vec![0.0_f64; n_steps + 1];
610 let mut v_path = vec![0.0_f64; n_steps + 1];
611 s_path[0] = self.s0;
612 v_path[0] = self.v0;
613 let rho2 = (1.0 - self.rho * self.rho).max(0.0).sqrt();
614 for i in 1..=n_steps {
615 let z1 = rng.normal();
616 let z2 = rng.normal();
617 let dw_v = sqrt_dt * z1;
618 let dw_s = sqrt_dt * (self.rho * z1 + rho2 * z2);
619 let v = v_path[i - 1].max(0.0);
620 let sqrt_v = v.sqrt();
621 v_path[i] = (v + self.kappa * (self.theta - v) * dt + self.xi * sqrt_v * dw_v).abs();
622 let s = s_path[i - 1];
623 s_path[i] = s + self.mu * s * dt + sqrt_v * s * dw_s;
624 if s_path[i] < 0.0 {
625 s_path[i] = 0.0;
626 }
627 }
628 (s_path, v_path)
629 }
630}
631
632pub struct LevyStableProcess {
642 pub alpha: f64,
644 pub beta: f64,
646 pub c: f64,
648 pub mu: f64,
650 seed: u64,
651}
652
653impl LevyStableProcess {
654 pub fn new(alpha: f64, beta: f64, c: f64, mu: f64, seed: u64) -> Self {
656 assert!(alpha > 0.0 && alpha <= 2.0, "alpha must be in (0, 2]");
657 assert!((-1.0..=1.0).contains(&beta), "beta must be in [-1, 1]");
658 assert!(c > 0.0, "scale c must be positive");
659 Self {
660 alpha,
661 beta,
662 c,
663 mu,
664 seed,
665 }
666 }
667
668 fn sample_standard(&self, rng: &mut Lcg) -> f64 {
670 let u = PI * (rng.uniform() - 0.5);
671 let w = rng.exponential(1.0);
672 if (self.alpha - 1.0).abs() < 1e-10 {
673 let b_pi = self.beta * PI / 2.0;
675 (1.0 + self.beta * u.tan()) * u.tan() - self.beta * (b_pi.cos() / b_pi.cos()).ln()
676 } else {
677 let zeta = -self.beta * (PI * self.alpha / 2.0).tan();
678 let xi = zeta.atan() / self.alpha;
679 let term1 = (1.0 + zeta * zeta).powf(1.0 / (2.0 * self.alpha));
680 let term2 = (self.alpha * (u + xi)).sin()
681 / ((self.alpha * xi).cos() * u.cos()).powf(1.0 / self.alpha);
682 let term3 =
683 ((u - self.alpha * (u + xi)).cos() / w).powf((1.0 - self.alpha) / self.alpha);
684 term1 * term2 * term3
685 }
686 }
687
688 pub fn increments(&self, n: usize) -> Vec<f64> {
690 let mut rng = Lcg::new(self.seed);
691 (0..n)
692 .map(|_| self.mu + self.c * self.sample_standard(&mut rng))
693 .collect()
694 }
695
696 pub fn path(&self, n: usize) -> Vec<f64> {
698 let incs = self.increments(n);
699 let mut path = vec![0.0_f64; n + 1];
700 for i in 0..n {
701 path[i + 1] = path[i] + incs[i];
702 }
703 path
704 }
705}
706
707pub struct BranchingProcess {
714 pub z0: usize,
716 pub mean_offspring: f64,
718 seed: u64,
719 dist: OffspringDist,
721}
722
723#[derive(Clone, Copy)]
725pub enum OffspringDist {
726 Poisson(f64),
728 Geometric(f64),
730 Bernoulli(f64),
732}
733
734impl BranchingProcess {
735 pub fn poisson(z0: usize, lambda: f64, seed: u64) -> Self {
737 Self {
738 z0,
739 mean_offspring: lambda,
740 seed,
741 dist: OffspringDist::Poisson(lambda),
742 }
743 }
744
745 pub fn geometric(z0: usize, p: f64, seed: u64) -> Self {
747 let mean = (1.0 - p) / p;
748 Self {
749 z0,
750 mean_offspring: mean,
751 seed,
752 dist: OffspringDist::Geometric(p),
753 }
754 }
755
756 pub fn bernoulli(z0: usize, p: f64, seed: u64) -> Self {
758 Self {
759 z0,
760 mean_offspring: p,
761 seed,
762 dist: OffspringDist::Bernoulli(p),
763 }
764 }
765
766 fn sample_offspring(&self, rng: &mut Lcg) -> usize {
767 match self.dist {
768 OffspringDist::Poisson(lambda) => rng.poisson(lambda),
769 OffspringDist::Geometric(p) => {
770 let u = rng.uniform().max(1e-300);
771 (u.ln() / (1.0 - p).ln()).floor() as usize
772 }
773 OffspringDist::Bernoulli(p) => rng.bernoulli(p) as usize,
774 }
775 }
776
777 pub fn simulate(&self, n_generations: usize) -> Vec<usize> {
781 let mut rng = Lcg::new(self.seed);
782 let mut pop = vec![0usize; n_generations + 1];
783 pop[0] = self.z0;
784 for g in 0..n_generations {
785 let mut next_pop = 0usize;
786 for _ in 0..pop[g] {
787 next_pop = next_pop.saturating_add(self.sample_offspring(&mut rng));
788 }
789 pop[g + 1] = next_pop;
790 }
791 pop
792 }
793
794 pub fn extinction_probability(&self) -> f64 {
801 if self.mean_offspring <= 1.0 {
802 return 1.0;
803 }
804 match self.dist {
805 OffspringDist::Poisson(lambda) => {
806 let mut q = 0.0_f64;
808 for _ in 0..1000 {
809 let q_new = (lambda * (q - 1.0)).exp();
810 if (q_new - q).abs() < 1e-12 {
811 return q_new;
812 }
813 q = q_new;
814 }
815 q
816 }
817 OffspringDist::Geometric(p) => {
818 let mean = (1.0 - p) / p;
820 if mean <= 1.0 { 1.0 } else { p / (1.0 - p) }
821 }
822 OffspringDist::Bernoulli(p) => {
823 if p <= 0.5 {
824 1.0
825 } else {
826 (1.0 - p) / p
827 }
828 }
829 }
830 }
831
832 pub fn mc_extinction_probability(&self, n_paths: usize, n_generations: usize) -> f64 {
834 let mut extinct = 0usize;
835 for k in 0..n_paths {
836 let mut bp = BranchingProcess {
837 z0: self.z0,
838 mean_offspring: self.mean_offspring,
839 seed: self.seed ^ (k as u64 * 2654435761),
840 dist: self.dist,
841 };
842 bp.seed ^= k as u64;
843 let pop = bp.simulate(n_generations);
844 if *pop.last().unwrap_or(&1) == 0 {
845 extinct += 1;
846 }
847 }
848 extinct as f64 / n_paths as f64
849 }
850}
851
852pub struct MarkovChain {
858 pub transition: Vec<Vec<f64>>,
860 pub n_states: usize,
862 seed: u64,
863}
864
865impl MarkovChain {
866 pub fn new(transition: Vec<Vec<f64>>, seed: u64) -> Self {
870 let n_states = transition.len();
871 Self {
872 transition,
873 n_states,
874 seed,
875 }
876 }
877
878 fn next_state(&self, state: usize, rng: &mut Lcg) -> usize {
880 let u = rng.uniform();
881 let mut cumul = 0.0;
882 for (j, &p) in self.transition[state].iter().enumerate() {
883 cumul += p;
884 if u < cumul {
885 return j;
886 }
887 }
888 self.n_states - 1
889 }
890
891 pub fn simulate(&self, start_state: usize, n_steps: usize) -> Vec<usize> {
893 let mut rng = Lcg::new(self.seed);
894 let mut traj = vec![0usize; n_steps + 1];
895 traj[0] = start_state;
896 for i in 1..=n_steps {
897 traj[i] = self.next_state(traj[i - 1], &mut rng);
898 }
899 traj
900 }
901
902 pub fn stationary_distribution(&self) -> Vec<f64> {
906 let n = self.n_states;
907 let mut pi: Vec<f64> = vec![1.0 / n as f64; n];
908 for _ in 0..10_000 {
909 let mut pi_new = vec![0.0_f64; n];
910 for i in 0..n {
911 for j in 0..n {
912 pi_new[j] += pi[i] * self.transition[i][j];
913 }
914 }
915 let sum: f64 = pi_new.iter().sum();
916 for x in &mut pi_new {
917 *x /= sum;
918 }
919 let diff: f64 = pi_new.iter().zip(&pi).map(|(a, b)| (a - b).abs()).sum();
920 pi = pi_new;
921 if diff < 1e-12 {
922 break;
923 }
924 }
925 pi
926 }
927
928 pub fn hitting_time(&self, start_state: usize, target_state: usize, n_samples: usize) -> f64 {
930 let mut rng = Lcg::new(self.seed ^ 0xdeadbeef);
931 let mut total = 0usize;
932 let max_steps = 100_000;
933 for _ in 0..n_samples {
934 let mut state = start_state;
935 for step in 1..=max_steps {
936 state = self.next_state(state, &mut rng);
937 if state == target_state {
938 total += step;
939 break;
940 }
941 }
942 }
943 total as f64 / n_samples as f64
944 }
945
946 pub fn n_step_matrix(&self, n: usize) -> Vec<Vec<f64>> {
948 let size = self.n_states;
949 let mut result = vec![vec![0.0_f64; size]; size];
951 for i in 0..size {
952 result[i][i] = 1.0;
953 }
954 let mut base = self.transition.clone();
955 let mut power = n;
956 while power > 0 {
957 if power % 2 == 1 {
958 result = mat_mul(&result, &base, size);
959 }
960 base = mat_mul(&base, &base, size);
961 power /= 2;
962 }
963 result
964 }
965}
966
967fn mat_mul(a: &[Vec<f64>], b: &[Vec<f64>], n: usize) -> Vec<Vec<f64>> {
969 let mut c = vec![vec![0.0_f64; n]; n];
970 for i in 0..n {
971 for k in 0..n {
972 if a[i][k].abs() < 1e-15 {
973 continue;
974 }
975 for j in 0..n {
976 c[i][j] += a[i][k] * b[k][j];
977 }
978 }
979 }
980 c
981}
982
983pub struct PoissonProcess {
987 pub rate: f64,
989 seed: u64,
990}
991
992impl PoissonProcess {
993 pub fn new(rate: f64, seed: u64) -> Self {
995 Self { rate, seed }
996 }
997
998 pub fn arrivals(&self, t_end: f64) -> Vec<f64> {
1002 let mut rng = Lcg::new(self.seed);
1003 let mut times = Vec::new();
1004 let mut t = 0.0_f64;
1005 loop {
1006 let inter = rng.exponential(self.rate);
1007 t += inter;
1008 if t > t_end {
1009 break;
1010 }
1011 times.push(t);
1012 }
1013 times
1014 }
1015
1016 pub fn count(&self, t_end: f64) -> usize {
1018 self.arrivals(t_end).len()
1019 }
1020
1021 pub fn inhomogeneous_arrivals<F>(&self, t_end: f64, lambda_max: f64, lambda_fn: F) -> Vec<f64>
1024 where
1025 F: Fn(f64) -> f64,
1026 {
1027 let mut rng = Lcg::new(self.seed ^ 0xabcdef12);
1028 let mut times = Vec::new();
1029 let mut t = 0.0_f64;
1030 loop {
1031 let inter = rng.exponential(lambda_max);
1032 t += inter;
1033 if t > t_end {
1034 break;
1035 }
1036 let accept_prob = lambda_fn(t) / lambda_max;
1037 if rng.uniform() < accept_prob {
1038 times.push(t);
1039 }
1040 }
1041 times
1042 }
1043
1044 pub fn inter_arrival_times(&self, n: usize) -> Vec<f64> {
1046 let mut rng = Lcg::new(self.seed ^ 0x12345678);
1047 (0..n).map(|_| rng.exponential(self.rate)).collect()
1048 }
1049}
1050
1051pub fn antithetic_variates<F>(evaluator: F, n_pairs: usize, seed: u64) -> (f64, f64)
1061where
1062 F: Fn(f64, f64) -> f64,
1063{
1064 let mut rng = Lcg::new(seed);
1065 let mut sum = 0.0_f64;
1066 let mut sum_sq = 0.0_f64;
1067 for _ in 0..n_pairs {
1068 let u = rng.uniform();
1069 let val = (evaluator(u, 1.0 - u) + evaluator(1.0 - u, u)) / 2.0;
1070 sum += val;
1071 sum_sq += val * val;
1072 }
1073 let mean = sum / n_pairs as f64;
1074 let variance = (sum_sq / n_pairs as f64 - mean * mean).max(0.0);
1075 (mean, variance)
1076}
1077
1078pub fn control_variate_estimator(pairs: &[(f64, f64)], mu_c: f64) -> f64 {
1088 let n = pairs.len() as f64;
1089 let mean_y: f64 = pairs.iter().map(|(y, _)| y).sum::<f64>() / n;
1090 let mean_c: f64 = pairs.iter().map(|(_, c)| c).sum::<f64>() / n;
1091 let cov_yc: f64 = pairs
1092 .iter()
1093 .map(|(y, c)| (y - mean_y) * (c - mean_c))
1094 .sum::<f64>()
1095 / n;
1096 let var_c: f64 = pairs.iter().map(|(_, c)| (c - mean_c).powi(2)).sum::<f64>() / n;
1097 if var_c.abs() < 1e-15 {
1098 return mean_y;
1099 }
1100 let b = cov_yc / var_c;
1101 mean_y - b * (mean_c - mu_c)
1102}
1103
1104pub fn quasi_monte_carlo<F>(integrand: F, n: usize) -> f64
1109where
1110 F: Fn(f64, f64) -> f64,
1111{
1112 let sum: f64 = (1..=n).map(|i| integrand(halton(i, 2), halton(i, 3))).sum();
1113 sum / n as f64
1114}
1115
1116pub fn halton(mut i: usize, base: usize) -> f64 {
1118 let mut result = 0.0_f64;
1119 let mut denominator = 1.0_f64;
1120 while i > 0 {
1121 denominator *= base as f64;
1122 result += (i % base) as f64 / denominator;
1123 i /= base;
1124 }
1125 result
1126}
1127
1128#[cfg(test)]
1131mod tests {
1132 use super::*;
1133
1134 #[test]
1137 fn test_bm_path_length() {
1138 let bm = BrownianMotion::new(0.01, 42);
1139 let path = bm.path(100);
1140 assert_eq!(path.len(), 101);
1141 }
1142
1143 #[test]
1144 fn test_bm_starts_at_zero() {
1145 let bm = BrownianMotion::new(0.01, 42);
1146 let path = bm.path(100);
1147 assert_eq!(path[0], 0.0);
1148 }
1149
1150 #[test]
1151 fn test_bm_variance_grows_with_time() {
1152 let sigma = 1.0;
1154 let dt = 0.1;
1155 let n_steps = 10;
1156 let t = n_steps as f64 * dt;
1157 let n_paths = 5000;
1158 let mut sum_sq = 0.0_f64;
1159 for k in 0..n_paths {
1160 let bm = BrownianMotion::with_sigma(dt, sigma, k as u64 * 1234 + 1);
1161 let path = bm.path(n_steps);
1162 let w_t = path[n_steps];
1163 sum_sq += w_t * w_t;
1164 }
1165 let empirical_var = sum_sq / n_paths as f64;
1166 let theoretical_var = sigma * sigma * t;
1167 assert!(
1168 (empirical_var - theoretical_var).abs() < 0.15,
1169 "BM variance: empirical={:.6} theoretical={:.6}",
1170 empirical_var,
1171 theoretical_var
1172 );
1173 }
1174
1175 #[test]
1176 fn test_bm_quadratic_variation_converges() {
1177 let bm = BrownianMotion::with_sigma(0.001, 2.0, 7777);
1179 let path = bm.path(1000);
1180 let qv = BrownianMotion::quadratic_variation(&path);
1181 assert!(
1183 (qv - 4.0).abs() < 1.0,
1184 "QV should be near 4.0, got {:.6}",
1185 qv
1186 );
1187 }
1188
1189 #[test]
1190 fn test_bm_multi_path_count() {
1191 let bm = BrownianMotion::new(0.01, 1);
1192 let paths = bm.multi_path(10, 50);
1193 assert_eq!(paths.len(), 10);
1194 for path in &paths {
1195 assert_eq!(path.len(), 51);
1196 }
1197 }
1198
1199 #[test]
1200 fn test_bm_theoretical_variance() {
1201 let bm = BrownianMotion::with_sigma(0.01, 3.0, 1);
1202 let var = bm.variance_at(2.0);
1203 assert!((var - 18.0).abs() < 1e-10); }
1205
1206 #[test]
1209 fn test_gbm_starts_at_s0() {
1210 let gbm = GeometricBrownianMotion::new(100.0, 0.05, 0.2, 99);
1211 let path = gbm.exact_path(1.0, 100);
1212 assert!((path[0] - 100.0).abs() < 1e-10);
1213 }
1214
1215 #[test]
1216 fn test_gbm_stays_positive() {
1217 let gbm = GeometricBrownianMotion::new(10.0, 0.1, 0.5, 55);
1218 let path = gbm.exact_path(2.0, 200);
1219 assert!(path.iter().all(|&s| s >= 0.0));
1220 }
1221
1222 #[test]
1223 fn test_gbm_mean_convergence() {
1224 let s0 = 100.0;
1226 let mu = 0.10;
1227 let sigma = 0.2;
1228 let t_end = 1.0;
1229 let n_steps = 252;
1230 let n_paths = 5000;
1231 let mut sum = 0.0_f64;
1232 for k in 0..n_paths {
1233 let gbm = GeometricBrownianMotion::new(s0, mu, sigma, k as u64 + 1);
1234 let path = gbm.exact_path(t_end, n_steps);
1235 sum += path[n_steps];
1236 }
1237 let empirical_mean = sum / n_paths as f64;
1238 let theoretical_mean = s0 * (mu * t_end).exp();
1239 let rel_err = (empirical_mean - theoretical_mean).abs() / theoretical_mean;
1240 assert!(rel_err < 0.05, "GBM mean rel error: {:.6}", rel_err);
1241 }
1242
1243 #[test]
1244 fn test_gbm_log_normality() {
1245 let gbm = GeometricBrownianMotion::new(1.0, 0.0, 1.0, 123);
1247 let path = gbm.exact_path(1.0, 1000);
1248 let log_return = path[1000].ln();
1249 assert!(log_return.is_finite());
1251 }
1252
1253 #[test]
1254 fn test_gbm_euler_stays_positive() {
1255 let gbm = GeometricBrownianMotion::new(50.0, 0.05, 0.3, 333);
1256 let path = gbm.euler_path(1.0, 100);
1257 assert!(path.iter().all(|&s| s >= 0.0));
1258 }
1259
1260 #[test]
1263 fn test_ou_starts_at_x0() {
1264 let ou = OrnsteinUhlenbeck::new(2.0, 0.5, 0.3, 1.0, 42);
1265 let path = ou.path(1.0, 100);
1266 assert!((path[0] - 1.0).abs() < 1e-10);
1267 }
1268
1269 #[test]
1270 fn test_ou_mean_reversion() {
1271 let theta = 5.0;
1273 let mu = 3.0;
1274 let ou = OrnsteinUhlenbeck::new(theta, mu, 0.5, 10.0, 77);
1275 let path = ou.path(10.0, 10000);
1276 let tail_mean: f64 = path[8000..].iter().sum::<f64>() / 2000.0;
1277 assert!(
1278 (tail_mean - mu).abs() < 0.3,
1279 "OU mean: {:.6} vs {:.6}",
1280 tail_mean,
1281 mu
1282 );
1283 }
1284
1285 #[test]
1286 fn test_ou_stationary_variance() {
1287 let ou = OrnsteinUhlenbeck::new(2.0, 0.0, 1.0, 0.0, 1);
1288 let sv = ou.stationary_variance();
1289 assert!((sv - 0.25).abs() < 1e-10);
1291 }
1292
1293 #[test]
1294 fn test_ou_autocorrelation_decay() {
1295 let ou = OrnsteinUhlenbeck::new(1.0, 0.0, 1.0, 0.0, 1);
1296 let ac0 = ou.autocorrelation(0.0);
1297 let ac1 = ou.autocorrelation(1.0);
1298 assert!((ac0 - 1.0).abs() < 1e-10);
1299 assert!((ac1 - (-1.0_f64).exp()).abs() < 1e-10);
1300 }
1301
1302 #[test]
1305 fn test_jump_diffusion_starts_at_s0() {
1306 let jd = JumpDiffusion::new(100.0, 0.05, 0.2, 1.0, -0.1, 0.2, 42);
1307 let path = jd.path(1.0, 100);
1308 assert!((path[0] - 100.0).abs() < 1e-10);
1309 }
1310
1311 #[test]
1312 fn test_jump_diffusion_stays_nonneg() {
1313 let jd = JumpDiffusion::new(50.0, 0.05, 0.3, 2.0, 0.0, 0.3, 7);
1314 let path = jd.path(1.0, 252);
1315 assert!(path.iter().all(|&s| s >= 0.0));
1316 }
1317
1318 #[test]
1319 fn test_jump_diffusion_compensation() {
1320 let jd = JumpDiffusion::new(1.0, 0.0, 0.1, 1.0, 0.0, 0.1, 1);
1321 let comp = jd.jump_compensation();
1322 let expected = (0.0_f64 + 0.5 * 0.01_f64).exp() - 1.0;
1324 assert!((comp - expected).abs() < 1e-10);
1325 }
1326
1327 #[test]
1330 fn test_fbm_path_length() {
1331 let fbm = FractionalBrownianMotion::new(0.7, 1);
1332 let path = fbm.path(10);
1333 assert_eq!(path.len(), 11);
1334 }
1335
1336 #[test]
1337 fn test_fbm_starts_at_zero() {
1338 let fbm = FractionalBrownianMotion::new(0.5, 42);
1339 let path = fbm.path(20);
1340 assert!(path[0].abs() < 1e-10);
1341 }
1342
1343 #[test]
1344 fn test_fbm_hurst_validation() {
1345 let result = std::panic::catch_unwind(|| FractionalBrownianMotion::new(1.5, 1));
1346 assert!(result.is_err());
1347 }
1348
1349 #[test]
1350 fn test_fbm_covariance_positive_definite() {
1351 let fbm = FractionalBrownianMotion::new(0.7, 1);
1352 let cov = fbm.covariance(1.0, 1.0);
1353 assert!(cov > 0.0);
1355 }
1356
1357 #[test]
1360 fn test_cir_stays_positive() {
1361 let cir = CoxIngersollRoss::new(2.0, 0.05, 0.1, 0.04, 99);
1362 let path = cir.path(10.0, 1000);
1363 assert!(path.iter().all(|&r| r >= 0.0));
1364 }
1365
1366 #[test]
1367 fn test_cir_feller_condition() {
1368 let cir = CoxIngersollRoss::new(2.0, 0.05, 0.1, 0.04, 1);
1370 assert!(cir.feller_satisfied());
1372 }
1373
1374 #[test]
1375 fn test_cir_mean_reversion() {
1376 let theta = 0.05;
1377 let cir = CoxIngersollRoss::new(3.0, theta, 0.1, 0.2, 55);
1378 let path = cir.path(20.0, 20000);
1379 let tail_mean: f64 = path[15000..].iter().sum::<f64>() / 5000.0;
1380 assert!(
1381 (tail_mean - theta).abs() < 0.01,
1382 "CIR mean: {:.6} vs {:.6}",
1383 tail_mean,
1384 theta
1385 );
1386 }
1387
1388 #[test]
1389 fn test_cir_stationary_variance() {
1390 let cir = CoxIngersollRoss::new(2.0, 0.05, 0.1, 0.04, 1);
1391 let sv = cir.stationary_variance();
1392 assert!((sv - 0.000125).abs() < 1e-10);
1394 }
1395
1396 #[test]
1399 fn test_heston_path_lengths() {
1400 let heston = HestonModel::new(100.0, 0.04, 0.05, 2.0, 0.04, 0.3, -0.7, 11);
1401 let (s, v) = heston.path(1.0, 100);
1402 assert_eq!(s.len(), 101);
1403 assert_eq!(v.len(), 101);
1404 }
1405
1406 #[test]
1407 fn test_heston_starts_correctly() {
1408 let heston = HestonModel::new(100.0, 0.04, 0.05, 2.0, 0.04, 0.3, -0.7, 11);
1409 let (s, v) = heston.path(1.0, 100);
1410 assert!((s[0] - 100.0).abs() < 1e-10);
1411 assert!((v[0] - 0.04).abs() < 1e-10);
1412 }
1413
1414 #[test]
1415 fn test_heston_variance_stays_nonneg() {
1416 let heston = HestonModel::new(50.0, 0.1, 0.05, 1.0, 0.1, 0.5, -0.5, 77);
1417 let (_, v) = heston.path(2.0, 500);
1418 assert!(v.iter().all(|&vi| vi >= 0.0));
1419 }
1420
1421 #[test]
1424 fn test_levy_path_length() {
1425 let levy = LevyStableProcess::new(1.5, 0.0, 1.0, 0.0, 42);
1426 let path = levy.path(100);
1427 assert_eq!(path.len(), 101);
1428 }
1429
1430 #[test]
1431 fn test_levy_path_starts_at_zero() {
1432 let levy = LevyStableProcess::new(1.8, 0.0, 1.0, 0.0, 1);
1433 let path = levy.path(50);
1434 assert_eq!(path[0], 0.0);
1435 }
1436
1437 #[test]
1438 fn test_levy_gaussian_case_finite() {
1439 let levy = LevyStableProcess::new(2.0, 0.0, 1.0, 0.0, 88);
1441 let incs = levy.increments(1000);
1442 assert!(incs.iter().all(|x| x.is_finite()));
1443 }
1444
1445 #[test]
1446 fn test_levy_alpha_validation() {
1447 let result = std::panic::catch_unwind(|| LevyStableProcess::new(0.0, 0.0, 1.0, 0.0, 1));
1448 assert!(result.is_err());
1449 }
1450
1451 #[test]
1454 fn test_branching_path_length() {
1455 let bp = BranchingProcess::poisson(10, 1.5, 42);
1456 let pop = bp.simulate(20);
1457 assert_eq!(pop.len(), 21);
1458 }
1459
1460 #[test]
1461 fn test_branching_starts_at_z0() {
1462 let bp = BranchingProcess::poisson(5, 1.2, 1);
1463 let pop = bp.simulate(10);
1464 assert_eq!(pop[0], 5);
1465 }
1466
1467 #[test]
1468 fn test_branching_subcritical_extinction() {
1469 let bp = BranchingProcess::poisson(1, 0.5, 100);
1471 let q = bp.extinction_probability();
1472 assert!((q - 1.0).abs() < 1e-10);
1473 }
1474
1475 #[test]
1476 fn test_branching_supercritical_extinction_less_than_one() {
1477 let bp = BranchingProcess::poisson(1, 2.0, 42);
1479 let q = bp.extinction_probability();
1480 assert!(q < 1.0 && q > 0.0);
1481 }
1482
1483 #[test]
1484 fn test_branching_mc_extinction_subcritical() {
1485 let bp = BranchingProcess::poisson(1, 0.5, 42);
1487 let q = bp.mc_extinction_probability(500, 50);
1488 assert!(
1489 q > 0.8,
1490 "Subcritical extinction prob should be high, got {:.6}",
1491 q
1492 );
1493 }
1494
1495 fn two_state_chain(seed: u64) -> MarkovChain {
1498 MarkovChain::new(vec![vec![0.7, 0.3], vec![0.4, 0.6]], seed)
1499 }
1500
1501 #[test]
1502 fn test_markov_simulate_length() {
1503 let mc = two_state_chain(1);
1504 let traj = mc.simulate(0, 100);
1505 assert_eq!(traj.len(), 101);
1506 }
1507
1508 #[test]
1509 fn test_markov_states_in_range() {
1510 let mc = two_state_chain(5);
1511 let traj = mc.simulate(0, 1000);
1512 assert!(traj.iter().all(|&s| s < 2));
1513 }
1514
1515 #[test]
1516 fn test_markov_stationary_distribution() {
1517 let mc = two_state_chain(1);
1519 let pi = mc.stationary_distribution();
1520 let expected = [4.0 / 7.0, 3.0 / 7.0];
1521 assert!((pi[0] - expected[0]).abs() < 1e-6, "pi[0]={:.8}", pi[0]);
1522 assert!((pi[1] - expected[1]).abs() < 1e-6, "pi[1]={:.8}", pi[1]);
1523 }
1524
1525 #[test]
1526 fn test_markov_stationary_sums_to_one() {
1527 let mc = two_state_chain(7);
1528 let pi = mc.stationary_distribution();
1529 let sum: f64 = pi.iter().sum();
1530 assert!((sum - 1.0).abs() < 1e-10);
1531 }
1532
1533 #[test]
1534 fn test_markov_n_step_matrix() {
1535 let mc = two_state_chain(1);
1536 let p1 = mc.n_step_matrix(1);
1537 assert!((p1[0][0] - 0.7).abs() < 1e-10);
1539 assert!((p1[0][1] - 0.3).abs() < 1e-10);
1540 }
1541
1542 #[test]
1543 fn test_markov_hitting_time_positive() {
1544 let mc = two_state_chain(42);
1545 let ht = mc.hitting_time(0, 1, 100);
1546 assert!(ht > 0.0);
1547 }
1548
1549 #[test]
1552 fn test_poisson_arrivals_ordered() {
1553 let pp = PoissonProcess::new(5.0, 1234);
1554 let arrivals = pp.arrivals(10.0);
1555 for i in 1..arrivals.len() {
1556 assert!(arrivals[i] > arrivals[i - 1]);
1557 }
1558 }
1559
1560 #[test]
1561 fn test_poisson_arrivals_in_range() {
1562 let pp = PoissonProcess::new(3.0, 99);
1563 let arrivals = pp.arrivals(5.0);
1564 assert!(arrivals.iter().all(|&t| t > 0.0 && t <= 5.0));
1565 }
1566
1567 #[test]
1568 fn test_poisson_mean_count() {
1569 let rate = 4.0;
1571 let t = 10.0;
1572 let n_samples = 2000;
1573 let mut total = 0usize;
1574 for k in 0..n_samples {
1575 let pp = PoissonProcess::new(rate, k as u64 + 1);
1576 total += pp.count(t);
1577 }
1578 let empirical_mean = total as f64 / n_samples as f64;
1579 let expected = rate * t;
1580 assert!(
1581 (empirical_mean - expected).abs() < 2.0,
1582 "Poisson mean: {:.6} vs {:.6}",
1583 empirical_mean,
1584 expected
1585 );
1586 }
1587
1588 #[test]
1589 fn test_poisson_inter_arrival_exponential() {
1590 let rate = 2.0;
1593 let n = 10000;
1594 let pp = PoissonProcess::new(rate, 42);
1595 let iat = pp.inter_arrival_times(n);
1596 let mean: f64 = iat.iter().sum::<f64>() / n as f64;
1597 let expected_mean = 1.0 / rate;
1598 assert!(
1599 (mean - expected_mean).abs() < 0.05,
1600 "Mean inter-arrival: {:.6} vs {:.6}",
1601 mean,
1602 expected_mean
1603 );
1604 }
1605
1606 #[test]
1607 fn test_poisson_inhomogeneous() {
1608 let pp = PoissonProcess::new(2.0, 1);
1610 let arrivals = pp.inhomogeneous_arrivals(10.0, 2.0, |_t| 2.0);
1611 assert!(arrivals.iter().all(|&t| (0.0..=10.0).contains(&t)));
1612 }
1613
1614 #[test]
1617 fn test_antithetic_variates_constant() {
1618 let (mean, _var) = antithetic_variates(|_u, _u_anti| 1.0, 1000, 42);
1620 assert!((mean - 1.0).abs() < 1e-10);
1621 }
1622
1623 #[test]
1624 fn test_antithetic_variates_uniform_mean() {
1625 let (mean, _var) = antithetic_variates(|u, _| u, 5000, 7);
1627 assert!((mean - 0.5).abs() < 0.02, "Antithetic mean: {:.6}", mean);
1628 }
1629
1630 #[test]
1631 fn test_control_variate_identity() {
1632 let pairs: Vec<(f64, f64)> = (1..=100).map(|i| (i as f64, i as f64)).collect();
1634 let mu_c = 50.5_f64;
1635 let est = control_variate_estimator(&pairs, mu_c);
1636 assert!((est - mu_c).abs() < 0.01, "CV estimate: {:.6}", est);
1637 }
1638
1639 #[test]
1640 fn test_quasi_monte_carlo_unit_square() {
1641 let est = quasi_monte_carlo(|_x, _y| 1.0, 1000);
1643 assert!((est - 1.0).abs() < 1e-10);
1644 }
1645
1646 #[test]
1647 fn test_quasi_monte_carlo_linear() {
1648 let est = quasi_monte_carlo(|x, y| x + y, 10000);
1650 assert!((est - 1.0).abs() < 0.01, "QMC estimate: {:.6}", est);
1651 }
1652
1653 #[test]
1654 fn test_halton_sequence_base2() {
1655 assert!((halton(1, 2) - 0.5).abs() < 1e-10);
1657 assert!((halton(2, 2) - 0.25).abs() < 1e-10);
1658 assert!((halton(3, 2) - 0.75).abs() < 1e-10);
1659 }
1660
1661 #[test]
1662 fn test_halton_in_unit_interval() {
1663 for i in 1..=1000 {
1664 let h = halton(i, 2);
1665 assert!((0.0..1.0).contains(&h), "Halton({}, 2) = {:.6}", i, h);
1666 }
1667 }
1668}