use crate::error::{IntegrateError, IntegrateResult};
pub struct SimpleRng {
state: [u64; 4],
}
impl SimpleRng {
pub fn new(seed: u64) -> Self {
let mut s = seed;
let expand = |x: &mut u64| -> u64 {
*x = x.wrapping_add(0x9e3779b97f4a7c15);
let mut z = *x;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb);
z ^ (z >> 31)
};
Self {
state: [expand(&mut s), expand(&mut s), expand(&mut s), expand(&mut s)],
}
}
fn next_u64(&mut self) -> u64 {
let result = self.state[0].wrapping_add(self.state[3]);
let t = self.state[1] << 17;
self.state[2] ^= self.state[0];
self.state[3] ^= self.state[1];
self.state[1] ^= self.state[2];
self.state[0] ^= self.state[3];
self.state[2] ^= t;
self.state[3] = self.state[3].rotate_left(45);
result
}
pub fn next_f64(&mut self) -> f64 {
(self.next_u64() >> 11) as f64 * (1.0_f64 / (1u64 << 53) as f64)
}
pub fn next_normal(&mut self) -> f64 {
loop {
let u1 = self.next_f64();
let u2 = self.next_f64();
if u1 > f64::EPSILON {
let r = (-2.0 * u1.ln()).sqrt();
let theta = 2.0 * std::f64::consts::PI * u2;
return r * theta.cos();
}
}
}
pub fn brownian_increment(&mut self, m: usize, dt: f64) -> Vec<f64> {
let sqrt_dt = dt.sqrt();
(0..m).map(|_| sqrt_dt * self.next_normal()).collect()
}
}
pub trait SdeSolver {
fn step(&self, x: &[f64], t: f64, dt: f64, rng: &mut SimpleRng) -> Vec<f64>;
}
pub struct EulerMaruyama {
drift: Box<dyn Fn(&[f64], f64) -> Vec<f64> + Send + Sync>,
diffusion: Box<dyn Fn(&[f64], f64) -> Vec<Vec<f64>> + Send + Sync>,
pub n_dim: usize,
pub m_noise: usize,
}
impl EulerMaruyama {
pub fn new(
drift: impl Fn(&[f64], f64) -> Vec<f64> + Send + Sync + 'static,
diffusion: impl Fn(&[f64], f64) -> Vec<Vec<f64>> + Send + Sync + 'static,
n_dim: usize,
m_noise: usize,
) -> Self {
Self {
drift: Box::new(drift),
diffusion: Box::new(diffusion),
n_dim,
m_noise,
}
}
}
impl SdeSolver for EulerMaruyama {
fn step(&self, x: &[f64], t: f64, dt: f64, rng: &mut SimpleRng) -> Vec<f64> {
let f = (self.drift)(x, t);
let g = (self.diffusion)(x, t);
let dw = rng.brownian_increment(self.m_noise, dt);
let mut x_new = vec![0.0_f64; self.n_dim];
for i in 0..self.n_dim {
x_new[i] = x[i] + f[i] * dt;
for j in 0..self.m_noise {
if i < g.len() && j < g[i].len() {
x_new[i] += g[i][j] * dw[j];
}
}
}
x_new
}
}
pub struct MilsteinSolver {
drift: Box<dyn Fn(f64, f64) -> f64 + Send + Sync>,
diffusion: Box<dyn Fn(f64, f64) -> f64 + Send + Sync>,
diffusion_deriv: Box<dyn Fn(f64, f64) -> f64 + Send + Sync>,
}
impl MilsteinSolver {
pub fn new(
drift: impl Fn(f64, f64) -> f64 + Send + Sync + 'static,
diffusion: impl Fn(f64, f64) -> f64 + Send + Sync + 'static,
diffusion_deriv: impl Fn(f64, f64) -> f64 + Send + Sync + 'static,
) -> Self {
Self {
drift: Box::new(drift),
diffusion: Box::new(diffusion),
diffusion_deriv: Box::new(diffusion_deriv),
}
}
pub fn step_scalar(&self, x: f64, t: f64, dt: f64, rng: &mut SimpleRng) -> f64 {
let dw = rng.next_normal() * dt.sqrt();
let f = (self.drift)(x, t);
let g = (self.diffusion)(x, t);
let gp = (self.diffusion_deriv)(x, t);
x + f * dt + g * dw + 0.5 * g * gp * (dw * dw - dt)
}
}
impl SdeSolver for MilsteinSolver {
fn step(&self, x: &[f64], t: f64, dt: f64, rng: &mut SimpleRng) -> Vec<f64> {
let x0 = if x.is_empty() { 0.0 } else { x[0] };
vec![self.step_scalar(x0, t, dt, rng)]
}
}
pub struct SdeSolution {
pub times: Vec<f64>,
pub paths: Vec<Vec<f64>>,
pub n_dim: usize,
}
impl SdeSolution {
pub fn path(&self, idx: usize) -> Option<(&[f64], Vec<Vec<f64>>)> {
if idx >= self.paths.len() {
return None;
}
let n_steps = self.times.len();
let n_dim = self.n_dim;
let flat = &self.paths[idx];
let states: Vec<Vec<f64>> = (0..n_steps)
.map(|k| flat[k * n_dim..(k + 1) * n_dim].to_vec())
.collect();
Some((&self.times, states))
}
pub fn mean_trajectory(&self) -> Vec<Vec<f64>> {
let n_steps = self.times.len();
let n_dim = self.n_dim;
let n_paths = self.paths.len();
if n_paths == 0 || n_steps == 0 {
return vec![];
}
let scale = 1.0 / n_paths as f64;
(0..n_steps)
.map(|k| {
(0..n_dim)
.map(|d| {
self.paths.iter().map(|p| p[k * n_dim + d]).sum::<f64>() * scale
})
.collect()
})
.collect()
}
pub fn variance_trajectory(&self) -> Vec<Vec<f64>> {
let n_steps = self.times.len();
let n_dim = self.n_dim;
let n_paths = self.paths.len();
if n_paths < 2 || n_steps == 0 {
return vec![vec![0.0; n_dim]; n_steps];
}
let mean = self.mean_trajectory();
let scale = 1.0 / (n_paths - 1) as f64;
(0..n_steps)
.map(|k| {
(0..n_dim)
.map(|d| {
self.paths
.iter()
.map(|p| {
let diff = p[k * n_dim + d] - mean[k][d];
diff * diff
})
.sum::<f64>()
* scale
})
.collect()
})
.collect()
}
}
pub fn solve_sde(
solver: &impl SdeSolver,
x0: &[f64],
t0: f64,
t_final: f64,
dt: f64,
n_paths: usize,
seed: u64,
) -> SdeSolution {
let n_dim = x0.len();
let n_steps = ((t_final - t0) / dt).ceil() as usize + 1;
let mut times = Vec::with_capacity(n_steps);
let mut t = t0;
while t <= t_final + dt * 0.5 {
times.push(t);
t += dt;
if times.len() >= n_steps {
break;
}
}
if times.last().copied().unwrap_or(t0) < t_final - f64::EPSILON {
times.push(t_final);
}
let actual_steps = times.len();
let mut paths = Vec::with_capacity(n_paths);
for path_idx in 0..n_paths {
let mut rng = SimpleRng::new(seed.wrapping_add(path_idx as u64));
let mut path_data = Vec::with_capacity(actual_steps * n_dim);
path_data.extend_from_slice(x0);
let mut x = x0.to_vec();
for k in 1..actual_steps {
let dt_actual = times[k] - times[k - 1];
x = solver.step(&x, times[k - 1], dt_actual, &mut rng);
path_data.extend_from_slice(&x);
}
paths.push(path_data);
}
SdeSolution {
times,
paths,
n_dim,
}
}
pub struct FokkerPlanckSolver {
pub n: usize,
pub dx: f64,
pub x: Vec<f64>,
pub pdf: Vec<f64>,
}
impl FokkerPlanckSolver {
pub fn new(
x_min: f64,
x_max: f64,
n: usize,
initial_pdf: &[f64],
) -> IntegrateResult<Self> {
if n < 3 {
return Err(IntegrateError::ValueError(
"FokkerPlanck: need at least 3 grid points".into(),
));
}
if initial_pdf.len() != n {
return Err(IntegrateError::ValueError(format!(
"FokkerPlanck: initial_pdf length {} != n={}",
initial_pdf.len(),
n
)));
}
let dx = (x_max - x_min) / ((n - 1) as f64);
let x: Vec<f64> = (0..n).map(|i| x_min + i as f64 * dx).collect();
let integral: f64 = initial_pdf.iter().sum::<f64>() * dx;
let pdf: Vec<f64> = if integral > 1e-15 {
initial_pdf.iter().map(|&p| p.max(0.0) / integral).collect()
} else {
vec![1.0 / (x_max - x_min); n]
};
Ok(Self { n, dx, x, pdf })
}
pub fn step(&mut self, dt: f64, drift: &[f64], diffusion: &[f64]) {
let n = self.n;
let dx = self.dx;
let inv_dx = 1.0 / dx;
let inv_dx2 = inv_dx * inv_dx;
let mut p_new = vec![0.0_f64; n];
for i in 1..n - 1 {
let p = self.pdf[i];
let pp = self.pdf[i + 1];
let pm = self.pdf[i - 1];
let f = drift[i];
let adv = if f >= 0.0 {
-f * (p - pm) * inv_dx
} else {
-f * (pp - p) * inv_dx
};
let g2_p = diffusion[i] * diffusion[i] * p;
let g2_pp = diffusion[i + 1] * diffusion[i + 1] * pp;
let g2_pm = diffusion[i - 1] * diffusion[i - 1] * pm;
let diff = 0.5 * (g2_pp - 2.0 * g2_p + g2_pm) * inv_dx2;
p_new[i] = p + dt * (adv + diff);
}
p_new[0] = p_new[1];
p_new[n - 1] = p_new[n - 2];
for v in p_new.iter_mut() {
if *v < 0.0 {
*v = 0.0;
}
}
let total: f64 = p_new.iter().sum::<f64>() * dx;
if total > 1e-15 {
for v in p_new.iter_mut() {
*v /= total;
}
}
self.pdf = p_new;
}
pub fn run(
&mut self,
n_steps: usize,
dt: f64,
drift_fn: impl Fn(f64) -> f64,
diffusion_fn: impl Fn(f64) -> f64,
) {
let drift: Vec<f64> = self.x.iter().map(|&xi| drift_fn(xi)).collect();
let diffusion: Vec<f64> = self.x.iter().map(|&xi| diffusion_fn(xi)).collect();
for _ in 0..n_steps {
self.step(dt, &drift, &diffusion);
}
}
pub fn mean(&self) -> f64 {
self.x
.iter()
.zip(self.pdf.iter())
.map(|(&xi, &pi)| xi * pi)
.sum::<f64>()
* self.dx
}
pub fn variance(&self) -> f64 {
let mu = self.mean();
let ex2: f64 = self
.x
.iter()
.zip(self.pdf.iter())
.map(|(&xi, &pi)| xi * xi * pi)
.sum::<f64>()
* self.dx;
(ex2 - mu * mu).max(0.0)
}
pub fn entropy(&self) -> f64 {
-self
.pdf
.iter()
.filter(|&&p| p > 1e-300)
.map(|&p| p * p.ln())
.sum::<f64>()
* self.dx
}
pub fn l1_norm(&self) -> f64 {
self.pdf.iter().sum::<f64>() * self.dx
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rng_uniform_range() {
let mut rng = SimpleRng::new(42);
for _ in 0..1000 {
let v = rng.next_f64();
assert!((0.0..1.0).contains(&v));
}
}
#[test]
fn test_rng_normal_zero_mean() {
let mut rng = SimpleRng::new(7);
let samples: Vec<f64> = (0..10_000).map(|_| rng.next_normal()).collect();
let mean = samples.iter().sum::<f64>() / samples.len() as f64;
assert!(mean.abs() < 0.1, "mean={}", mean);
}
#[test]
fn test_rng_normal_unit_variance() {
let mut rng = SimpleRng::new(13);
let samples: Vec<f64> = (0..10_000).map(|_| rng.next_normal()).collect();
let mean = samples.iter().sum::<f64>() / samples.len() as f64;
let var = samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / samples.len() as f64;
assert!((var - 1.0).abs() < 0.05, "var={}", var);
}
#[test]
fn test_rng_seeded_deterministic() {
let mut r1 = SimpleRng::new(99);
let mut r2 = SimpleRng::new(99);
for _ in 0..100 {
assert_eq!(r1.next_u64(), r2.next_u64());
}
}
#[test]
fn test_em_brownian_motion() {
let solver = EulerMaruyama::new(
|_x: &[f64], _t: f64| vec![0.0],
|_x: &[f64], _t: f64| vec![vec![1.0]],
1,
1,
);
let sol = solve_sde(&solver, &[0.0], 0.0, 1.0, 0.01, 1000, 42);
let mean_traj = sol.mean_trajectory();
let final_mean = mean_traj.last().map(|v| v[0]).unwrap_or(f64::NAN);
let var_traj = sol.variance_trajectory();
let final_var = var_traj.last().map(|v| v[0]).unwrap_or(f64::NAN);
assert!(final_mean.abs() < 0.15, "E[W(1)] ≈ 0, got {}", final_mean);
assert!((final_var - 1.0).abs() < 0.15, "Var[W(1)] ≈ 1, got {}", final_var);
}
#[test]
fn test_em_gbm_mean() {
let mu = 0.1_f64;
let sigma = 0.2_f64;
let x0 = 1.0_f64;
let t_final = 0.5_f64;
let solver = EulerMaruyama::new(
move |x: &[f64], _t: f64| vec![mu * x[0]],
move |x: &[f64], _t: f64| vec![vec![sigma * x[0]]],
1,
1,
);
let sol = solve_sde(&solver, &[x0], 0.0, t_final, 0.01, 2000, 7);
let mean_traj = sol.mean_trajectory();
let final_mean = mean_traj.last().map(|v| v[0]).unwrap_or(f64::NAN);
let expected = x0 * (mu * t_final).exp();
let rel_err = (final_mean - expected).abs() / expected;
assert!(rel_err < 0.05, "E[GBM] rel_err={:.3}, got={:.4} exp={:.4}", rel_err, final_mean, expected);
}
#[test]
fn test_milstein_gbm_order() {
let mu = 0.05_f64;
let sigma = 0.3_f64;
let x0 = 1.0_f64;
let solver = MilsteinSolver::new(
move |x: f64, _t: f64| mu * x,
move |x: f64, _t: f64| sigma * x,
move |_x: f64, _t: f64| sigma, );
let sol = solve_sde(&solver, &[x0], 0.0, 0.5, 0.01, 500, 42);
assert!(!sol.times.is_empty());
let mean = sol.mean_trajectory();
let last = mean.last().map(|v| v[0]).unwrap_or(f64::NAN);
assert!(last > 0.0 && last.is_finite());
}
#[test]
fn test_solution_path_access() {
let solver = EulerMaruyama::new(
|_x: &[f64], _t: f64| vec![0.0],
|_x: &[f64], _t: f64| vec![vec![1.0]],
1,
1,
);
let sol = solve_sde(&solver, &[0.0], 0.0, 0.1, 0.01, 3, 1);
assert_eq!(sol.paths.len(), 3);
assert!(sol.path(0).is_some());
assert!(sol.path(10).is_none());
}
#[test]
fn test_fp_creation() {
let n = 50;
let pdf: Vec<f64> = (0..n).map(|i| {
let x = -5.0 + 10.0 * i as f64 / (n - 1) as f64;
(-(x * x) / 2.0).exp()
}).collect();
let solver = FokkerPlanckSolver::new(-5.0, 5.0, n, &pdf);
assert!(solver.is_ok());
}
#[test]
fn test_fp_wrong_size() {
let pdf = vec![1.0; 10];
let solver = FokkerPlanckSolver::new(-5.0, 5.0, 20, &pdf);
assert!(solver.is_err());
}
#[test]
fn test_fp_normalisation_preserved() {
let n = 50;
let pdf: Vec<f64> = (0..n).map(|i| {
let x = -5.0 + 10.0 * i as f64 / (n - 1) as f64;
(-(x * x) / 2.0).exp()
}).collect();
let mut solver = FokkerPlanckSolver::new(-5.0, 5.0, n, &pdf).expect("FokkerPlanckSolver::new should succeed with valid params");
solver.run(20, 1e-3, |x| -x, |_| 1.0); let norm = solver.l1_norm();
assert!((norm - 1.0).abs() < 0.01, "L1 norm = {}", norm);
}
#[test]
fn test_fp_ou_mean_mean_reversion() {
let n = 101;
let center_idx = 85usize; let mut pdf = vec![0.0_f64; n];
pdf[center_idx] = 1.0 / (10.0 / n as f64); let mut solver = FokkerPlanckSolver::new(-5.0, 5.0, n, &pdf).expect("FokkerPlanckSolver::new should succeed with valid params");
let m0 = solver.mean();
solver.run(100, 1e-3, |x| -x, |_| 1.0);
let m1 = solver.mean();
assert!(m1.abs() < m0.abs() + 0.5, "mean should move towards 0: m0={:.3} m1={:.3}", m0, m1);
}
#[test]
fn test_fp_variance_finite() {
let n = 50;
let pdf = vec![1.0_f64 / 50.0; n]; let mut solver = FokkerPlanckSolver::new(-1.0, 1.0, n, &pdf).expect("FokkerPlanckSolver::new should succeed with valid params");
solver.run(10, 1e-4, |_| 0.0, |_| 0.5);
let v = solver.variance();
assert!(v >= 0.0 && v.is_finite(), "variance={}", v);
}
}