1use crate::error::{IntegrateError, IntegrateResult};
47
48pub struct SimpleRng {
56 state: [u64; 4],
57}
58
59impl SimpleRng {
60 pub fn new(seed: u64) -> Self {
62 let mut s = seed;
64 let expand = |x: &mut u64| -> u64 {
65 *x = x.wrapping_add(0x9e3779b97f4a7c15);
66 let mut z = *x;
67 z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
68 z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
69 z ^ (z >> 31)
70 };
71 Self {
72 state: [
73 expand(&mut s),
74 expand(&mut s),
75 expand(&mut s),
76 expand(&mut s),
77 ],
78 }
79 }
80
81 fn next_u64(&mut self) -> u64 {
83 let result = self.state[0].wrapping_add(self.state[3]);
84 let t = self.state[1] << 17;
85 self.state[2] ^= self.state[0];
86 self.state[3] ^= self.state[1];
87 self.state[1] ^= self.state[2];
88 self.state[0] ^= self.state[3];
89 self.state[2] ^= t;
90 self.state[3] = self.state[3].rotate_left(45);
91 result
92 }
93
94 pub fn next_f64(&mut self) -> f64 {
96 (self.next_u64() >> 11) as f64 * (1.0_f64 / (1u64 << 53) as f64)
98 }
99
100 pub fn next_normal(&mut self) -> f64 {
102 loop {
103 let u1 = self.next_f64();
104 let u2 = self.next_f64();
105 if u1 > f64::EPSILON {
106 let r = (-2.0 * u1.ln()).sqrt();
107 let theta = 2.0 * std::f64::consts::PI * u2;
108 return r * theta.cos();
109 }
110 }
111 }
112
113 pub fn brownian_increment(&mut self, m: usize, dt: f64) -> Vec<f64> {
115 let sqrt_dt = dt.sqrt();
116 (0..m).map(|_| sqrt_dt * self.next_normal()).collect()
117 }
118}
119
120pub trait SdeSolver {
126 fn step(&self, x: &[f64], t: f64, dt: f64, rng: &mut SimpleRng) -> Vec<f64>;
128}
129
130pub struct EulerMaruyama {
142 drift: Box<dyn Fn(&[f64], f64) -> Vec<f64> + Send + Sync>,
143 diffusion: Box<dyn Fn(&[f64], f64) -> Vec<Vec<f64>> + Send + Sync>,
144 pub n_dim: usize,
146 pub m_noise: usize,
148}
149
150impl EulerMaruyama {
151 pub fn new(
159 drift: impl Fn(&[f64], f64) -> Vec<f64> + Send + Sync + 'static,
160 diffusion: impl Fn(&[f64], f64) -> Vec<Vec<f64>> + Send + Sync + 'static,
161 n_dim: usize,
162 m_noise: usize,
163 ) -> Self {
164 Self {
165 drift: Box::new(drift),
166 diffusion: Box::new(diffusion),
167 n_dim,
168 m_noise,
169 }
170 }
171}
172
173impl SdeSolver for EulerMaruyama {
174 fn step(&self, x: &[f64], t: f64, dt: f64, rng: &mut SimpleRng) -> Vec<f64> {
176 let f = (self.drift)(x, t);
177 let g = (self.diffusion)(x, t);
178 let dw = rng.brownian_increment(self.m_noise, dt);
179
180 let mut x_new = vec![0.0_f64; self.n_dim];
181 for i in 0..self.n_dim {
182 x_new[i] = x[i] + f[i] * dt;
183 for j in 0..self.m_noise {
184 if i < g.len() && j < g[i].len() {
185 x_new[i] += g[i][j] * dw[j];
186 }
187 }
188 }
189 x_new
190 }
191}
192
193pub struct MilsteinSolver {
205 drift: Box<dyn Fn(f64, f64) -> f64 + Send + Sync>,
206 diffusion: Box<dyn Fn(f64, f64) -> f64 + Send + Sync>,
207 diffusion_deriv: Box<dyn Fn(f64, f64) -> f64 + Send + Sync>,
208}
209
210impl MilsteinSolver {
211 pub fn new(
218 drift: impl Fn(f64, f64) -> f64 + Send + Sync + 'static,
219 diffusion: impl Fn(f64, f64) -> f64 + Send + Sync + 'static,
220 diffusion_deriv: impl Fn(f64, f64) -> f64 + Send + Sync + 'static,
221 ) -> Self {
222 Self {
223 drift: Box::new(drift),
224 diffusion: Box::new(diffusion),
225 diffusion_deriv: Box::new(diffusion_deriv),
226 }
227 }
228
229 pub fn step_scalar(&self, x: f64, t: f64, dt: f64, rng: &mut SimpleRng) -> f64 {
231 let dw = rng.next_normal() * dt.sqrt();
232 let f = (self.drift)(x, t);
233 let g = (self.diffusion)(x, t);
234 let gp = (self.diffusion_deriv)(x, t);
235 x + f * dt + g * dw + 0.5 * g * gp * (dw * dw - dt)
236 }
237}
238
239impl SdeSolver for MilsteinSolver {
240 fn step(&self, x: &[f64], t: f64, dt: f64, rng: &mut SimpleRng) -> Vec<f64> {
242 let x0 = if x.is_empty() { 0.0 } else { x[0] };
243 vec![self.step_scalar(x0, t, dt, rng)]
244 }
245}
246
247pub struct SdeSolution {
253 pub times: Vec<f64>,
255 pub paths: Vec<Vec<f64>>,
258 pub n_dim: usize,
260}
261
262impl SdeSolution {
263 pub fn path(&self, idx: usize) -> Option<(&[f64], Vec<Vec<f64>>)> {
267 if idx >= self.paths.len() {
268 return None;
269 }
270 let n_steps = self.times.len();
271 let n_dim = self.n_dim;
272 let flat = &self.paths[idx];
273 let states: Vec<Vec<f64>> = (0..n_steps)
274 .map(|k| flat[k * n_dim..(k + 1) * n_dim].to_vec())
275 .collect();
276 Some((&self.times, states))
277 }
278
279 pub fn mean_trajectory(&self) -> Vec<Vec<f64>> {
281 let n_steps = self.times.len();
282 let n_dim = self.n_dim;
283 let n_paths = self.paths.len();
284 if n_paths == 0 || n_steps == 0 {
285 return vec![];
286 }
287 let scale = 1.0 / n_paths as f64;
288 (0..n_steps)
289 .map(|k| {
290 (0..n_dim)
291 .map(|d| self.paths.iter().map(|p| p[k * n_dim + d]).sum::<f64>() * scale)
292 .collect()
293 })
294 .collect()
295 }
296
297 pub fn variance_trajectory(&self) -> Vec<Vec<f64>> {
299 let n_steps = self.times.len();
300 let n_dim = self.n_dim;
301 let n_paths = self.paths.len();
302 if n_paths < 2 || n_steps == 0 {
303 return vec![vec![0.0; n_dim]; n_steps];
304 }
305 let mean = self.mean_trajectory();
306 let scale = 1.0 / (n_paths - 1) as f64;
307 (0..n_steps)
308 .map(|k| {
309 (0..n_dim)
310 .map(|d| {
311 self.paths
312 .iter()
313 .map(|p| {
314 let diff = p[k * n_dim + d] - mean[k][d];
315 diff * diff
316 })
317 .sum::<f64>()
318 * scale
319 })
320 .collect()
321 })
322 .collect()
323 }
324}
325
326pub fn solve_sde(
344 solver: &impl SdeSolver,
345 x0: &[f64],
346 t0: f64,
347 t_final: f64,
348 dt: f64,
349 n_paths: usize,
350 seed: u64,
351) -> SdeSolution {
352 let n_dim = x0.len();
353 let n_steps = ((t_final - t0) / dt).ceil() as usize + 1;
354
355 let mut times = Vec::with_capacity(n_steps);
356 let mut t = t0;
357 while t <= t_final + dt * 0.5 {
358 times.push(t);
359 t += dt;
360 if times.len() >= n_steps {
361 break;
362 }
363 }
364 if times.last().copied().unwrap_or(t0) < t_final - f64::EPSILON {
366 times.push(t_final);
367 }
368
369 let actual_steps = times.len();
370 let mut paths = Vec::with_capacity(n_paths);
371
372 for path_idx in 0..n_paths {
373 let mut rng = SimpleRng::new(seed.wrapping_add(path_idx as u64));
374 let mut path_data = Vec::with_capacity(actual_steps * n_dim);
375 path_data.extend_from_slice(x0);
377 let mut x = x0.to_vec();
378 for k in 1..actual_steps {
379 let dt_actual = times[k] - times[k - 1];
380 x = solver.step(&x, times[k - 1], dt_actual, &mut rng);
381 path_data.extend_from_slice(&x);
382 }
383 paths.push(path_data);
384 }
385
386 SdeSolution {
387 times,
388 paths,
389 n_dim,
390 }
391}
392
393pub struct FokkerPlanckSolver {
415 pub n: usize,
417 pub dx: f64,
419 pub x: Vec<f64>,
421 pub pdf: Vec<f64>,
423}
424
425impl FokkerPlanckSolver {
426 pub fn new(x_min: f64, x_max: f64, n: usize, initial_pdf: &[f64]) -> IntegrateResult<Self> {
436 if n < 3 {
437 return Err(IntegrateError::ValueError(
438 "FokkerPlanck: need at least 3 grid points".into(),
439 ));
440 }
441 if initial_pdf.len() != n {
442 return Err(IntegrateError::ValueError(format!(
443 "FokkerPlanck: initial_pdf length {} != n={}",
444 initial_pdf.len(),
445 n
446 )));
447 }
448 let dx = (x_max - x_min) / ((n - 1) as f64);
449 let x: Vec<f64> = (0..n).map(|i| x_min + i as f64 * dx).collect();
450
451 let integral: f64 = initial_pdf.iter().sum::<f64>() * dx;
453 let pdf: Vec<f64> = if integral > 1e-15 {
454 initial_pdf.iter().map(|&p| p.max(0.0) / integral).collect()
455 } else {
456 vec![1.0 / (x_max - x_min); n]
457 };
458
459 Ok(Self { n, dx, x, pdf })
460 }
461
462 pub fn step(&mut self, dt: f64, drift: &[f64], diffusion: &[f64]) {
472 let n = self.n;
473 let dx = self.dx;
474 let inv_dx = 1.0 / dx;
475 let inv_dx2 = inv_dx * inv_dx;
476
477 let mut p_new = vec![0.0_f64; n];
478
479 for i in 1..n - 1 {
480 let p = self.pdf[i];
481 let pp = self.pdf[i + 1];
482 let pm = self.pdf[i - 1];
483
484 let f = drift[i];
486 let adv = if f >= 0.0 {
487 -f * (p - pm) * inv_dx
488 } else {
489 -f * (pp - p) * inv_dx
490 };
491
492 let g2_p = diffusion[i] * diffusion[i] * p;
494 let g2_pp = diffusion[i + 1] * diffusion[i + 1] * pp;
495 let g2_pm = diffusion[i - 1] * diffusion[i - 1] * pm;
496 let diff = 0.5 * (g2_pp - 2.0 * g2_p + g2_pm) * inv_dx2;
497
498 p_new[i] = p + dt * (adv + diff);
499 }
500
501 p_new[0] = p_new[1];
503 p_new[n - 1] = p_new[n - 2];
504
505 for v in p_new.iter_mut() {
507 if *v < 0.0 {
508 *v = 0.0;
509 }
510 }
511
512 let total: f64 = p_new.iter().sum::<f64>() * dx;
514 if total > 1e-15 {
515 for v in p_new.iter_mut() {
516 *v /= total;
517 }
518 }
519
520 self.pdf = p_new;
521 }
522
523 pub fn run(
525 &mut self,
526 n_steps: usize,
527 dt: f64,
528 drift_fn: impl Fn(f64) -> f64,
529 diffusion_fn: impl Fn(f64) -> f64,
530 ) {
531 let drift: Vec<f64> = self.x.iter().map(|&xi| drift_fn(xi)).collect();
532 let diffusion: Vec<f64> = self.x.iter().map(|&xi| diffusion_fn(xi)).collect();
533 for _ in 0..n_steps {
534 self.step(dt, &drift, &diffusion);
535 }
536 }
537
538 pub fn mean(&self) -> f64 {
540 self.x
541 .iter()
542 .zip(self.pdf.iter())
543 .map(|(&xi, &pi)| xi * pi)
544 .sum::<f64>()
545 * self.dx
546 }
547
548 pub fn variance(&self) -> f64 {
550 let mu = self.mean();
551 let ex2: f64 = self
552 .x
553 .iter()
554 .zip(self.pdf.iter())
555 .map(|(&xi, &pi)| xi * xi * pi)
556 .sum::<f64>()
557 * self.dx;
558 (ex2 - mu * mu).max(0.0)
559 }
560
561 pub fn entropy(&self) -> f64 {
563 -self
564 .pdf
565 .iter()
566 .filter(|&&p| p > 1e-300)
567 .map(|&p| p * p.ln())
568 .sum::<f64>()
569 * self.dx
570 }
571
572 pub fn l1_norm(&self) -> f64 {
574 self.pdf.iter().sum::<f64>() * self.dx
575 }
576}
577
578#[cfg(test)]
583mod tests {
584 use super::*;
585
586 #[test]
589 fn test_rng_uniform_range() {
590 let mut rng = SimpleRng::new(42);
591 for _ in 0..1000 {
592 let v = rng.next_f64();
593 assert!((0.0..1.0).contains(&v));
594 }
595 }
596
597 #[test]
598 fn test_rng_normal_zero_mean() {
599 let mut rng = SimpleRng::new(7);
600 let samples: Vec<f64> = (0..10_000).map(|_| rng.next_normal()).collect();
601 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
602 assert!(mean.abs() < 0.1, "mean={}", mean);
603 }
604
605 #[test]
606 fn test_rng_normal_unit_variance() {
607 let mut rng = SimpleRng::new(13);
608 let samples: Vec<f64> = (0..10_000).map(|_| rng.next_normal()).collect();
609 let mean = samples.iter().sum::<f64>() / samples.len() as f64;
610 let var = samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / samples.len() as f64;
611 assert!((var - 1.0).abs() < 0.05, "var={}", var);
612 }
613
614 #[test]
615 fn test_rng_seeded_deterministic() {
616 let mut r1 = SimpleRng::new(99);
617 let mut r2 = SimpleRng::new(99);
618 for _ in 0..100 {
619 assert_eq!(r1.next_u64(), r2.next_u64());
620 }
621 }
622
623 #[test]
626 fn test_em_brownian_motion() {
627 let solver = EulerMaruyama::new(
629 |_x: &[f64], _t: f64| vec![0.0],
630 |_x: &[f64], _t: f64| vec![vec![1.0]],
631 1,
632 1,
633 );
634 let sol = solve_sde(&solver, &[0.0], 0.0, 1.0, 0.01, 1000, 42);
635 let mean_traj = sol.mean_trajectory();
636 let final_mean = mean_traj.last().map(|v| v[0]).unwrap_or(f64::NAN);
637 let var_traj = sol.variance_trajectory();
638 let final_var = var_traj.last().map(|v| v[0]).unwrap_or(f64::NAN);
639 assert!(final_mean.abs() < 0.15, "E[W(1)] ≈ 0, got {}", final_mean);
640 assert!(
641 (final_var - 1.0).abs() < 0.15,
642 "Var[W(1)] ≈ 1, got {}",
643 final_var
644 );
645 }
646
647 #[test]
648 fn test_em_gbm_mean() {
649 let mu = 0.1_f64;
651 let sigma = 0.2_f64;
652 let x0 = 1.0_f64;
653 let t_final = 0.5_f64;
654
655 let solver = EulerMaruyama::new(
656 move |x: &[f64], _t: f64| vec![mu * x[0]],
657 move |x: &[f64], _t: f64| vec![vec![sigma * x[0]]],
658 1,
659 1,
660 );
661 let sol = solve_sde(&solver, &[x0], 0.0, t_final, 0.01, 2000, 7);
662 let mean_traj = sol.mean_trajectory();
663 let final_mean = mean_traj.last().map(|v| v[0]).unwrap_or(f64::NAN);
664 let expected = x0 * (mu * t_final).exp();
665 let rel_err = (final_mean - expected).abs() / expected;
666 assert!(
667 rel_err < 0.05,
668 "E[GBM] rel_err={:.3}, got={:.4} exp={:.4}",
669 rel_err,
670 final_mean,
671 expected
672 );
673 }
674
675 #[test]
678 fn test_milstein_gbm_order() {
679 let mu = 0.05_f64;
681 let sigma = 0.3_f64;
682 let x0 = 1.0_f64;
683
684 let solver = MilsteinSolver::new(
685 move |x: f64, _t: f64| mu * x,
686 move |x: f64, _t: f64| sigma * x,
687 move |_x: f64, _t: f64| sigma, );
689 let sol = solve_sde(&solver, &[x0], 0.0, 0.5, 0.01, 500, 42);
690 assert!(!sol.times.is_empty());
691 let mean = sol.mean_trajectory();
693 let last = mean.last().map(|v| v[0]).unwrap_or(f64::NAN);
694 assert!(last > 0.0 && last.is_finite());
695 }
696
697 #[test]
700 fn test_solution_path_access() {
701 let solver = EulerMaruyama::new(
702 |_x: &[f64], _t: f64| vec![0.0],
703 |_x: &[f64], _t: f64| vec![vec![1.0]],
704 1,
705 1,
706 );
707 let sol = solve_sde(&solver, &[0.0], 0.0, 0.1, 0.01, 3, 1);
708 assert_eq!(sol.paths.len(), 3);
709 assert!(sol.path(0).is_some());
710 assert!(sol.path(10).is_none());
711 }
712
713 #[test]
716 fn test_fp_creation() {
717 let n = 50;
718 let pdf: Vec<f64> = (0..n)
719 .map(|i| {
720 let x = -5.0 + 10.0 * i as f64 / (n - 1) as f64;
721 (-(x * x) / 2.0).exp()
722 })
723 .collect();
724 let solver = FokkerPlanckSolver::new(-5.0, 5.0, n, &pdf);
725 assert!(solver.is_ok());
726 }
727
728 #[test]
729 fn test_fp_wrong_size() {
730 let pdf = vec![1.0; 10];
731 let solver = FokkerPlanckSolver::new(-5.0, 5.0, 20, &pdf);
732 assert!(solver.is_err());
733 }
734
735 #[test]
736 fn test_fp_normalisation_preserved() {
737 let n = 50;
738 let pdf: Vec<f64> = (0..n)
739 .map(|i| {
740 let x = -5.0 + 10.0 * i as f64 / (n - 1) as f64;
741 (-(x * x) / 2.0).exp()
742 })
743 .collect();
744 let mut solver = FokkerPlanckSolver::new(-5.0, 5.0, n, &pdf)
745 .expect("FokkerPlanckSolver::new should succeed with valid params");
746 solver.run(20, 1e-3, |x| -x, |_| 1.0); let norm = solver.l1_norm();
748 assert!((norm - 1.0).abs() < 0.01, "L1 norm = {}", norm);
749 }
750
751 #[test]
752 fn test_fp_ou_mean_mean_reversion() {
753 let n = 101;
756 let center_idx = 85usize; let mut pdf = vec![0.0_f64; n];
758 pdf[center_idx] = 1.0 / (10.0 / n as f64); let mut solver = FokkerPlanckSolver::new(-5.0, 5.0, n, &pdf)
760 .expect("FokkerPlanckSolver::new should succeed with valid params");
761 let m0 = solver.mean();
762 solver.run(100, 1e-3, |x| -x, |_| 1.0);
763 let m1 = solver.mean();
764 assert!(
766 m1.abs() < m0.abs() + 0.5,
767 "mean should move towards 0: m0={:.3} m1={:.3}",
768 m0,
769 m1
770 );
771 }
772
773 #[test]
774 fn test_fp_variance_finite() {
775 let n = 50;
776 let pdf = vec![1.0_f64 / 50.0; n]; let mut solver = FokkerPlanckSolver::new(-1.0, 1.0, n, &pdf)
778 .expect("FokkerPlanckSolver::new should succeed with valid params");
779 solver.run(10, 1e-4, |_| 0.0, |_| 0.5);
780 let v = solver.variance();
781 assert!(v >= 0.0 && v.is_finite(), "variance={}", v);
782 }
783}