use numra_core::Scalar;
use rand::prelude::*;
use rand_distr::StandardNormal;
#[derive(Clone, Debug)]
pub struct WienerIncrement<S: Scalar> {
pub dw: Vec<S>,
pub dt: S,
}
impl<S: Scalar> WienerIncrement<S> {
pub fn new(dw: Vec<S>, dt: S) -> Self {
Self { dw, dt }
}
pub fn zeros(n_wiener: usize, dt: S) -> Self {
Self {
dw: vec![S::ZERO; n_wiener],
dt,
}
}
}
pub struct WienerProcess<R: Rng> {
rng: R,
n_wiener: usize,
}
impl<R: Rng> WienerProcess<R> {
pub fn new(rng: R, n_wiener: usize) -> Self {
Self { rng, n_wiener }
}
pub fn increment<S: Scalar>(&mut self, dt: S) -> WienerIncrement<S> {
let sqrt_dt = dt.sqrt();
let dw: Vec<S> = (0..self.n_wiener)
.map(|_| {
let z: f64 = self.rng.sample(StandardNormal);
S::from_f64(z) * sqrt_dt
})
.collect();
WienerIncrement::new(dw, dt)
}
pub fn path<S: Scalar>(&mut self, t0: S, tf: S, dt: S) -> Vec<WienerIncrement<S>> {
let mut increments = Vec::new();
let mut t = t0;
while t < tf {
let step = dt.min(tf - t);
increments.push(self.increment(step));
t += step;
}
increments
}
pub fn correlated_increment<S: Scalar>(
&mut self,
dt: S,
cholesky: &[S], ) -> WienerIncrement<S> {
let sqrt_dt = dt.sqrt();
let n = self.n_wiener;
let z: Vec<f64> = (0..n).map(|_| self.rng.sample(StandardNormal)).collect();
let mut dw = vec![S::ZERO; n];
for i in 0..n {
for j in 0..=i {
dw[i] += cholesky[i * n + j] * S::from_f64(z[j]);
}
dw[i] *= sqrt_dt;
}
WienerIncrement::new(dw, dt)
}
}
impl WienerProcess<StdRng> {
pub fn with_seed(seed: u64, n_wiener: usize) -> Self {
Self::new(StdRng::seed_from_u64(seed), n_wiener)
}
pub fn from_entropy(n_wiener: usize) -> Self {
Self::new(StdRng::from_entropy(), n_wiener)
}
}
pub fn create_wiener(n_wiener: usize, seed: Option<u64>) -> WienerProcess<StdRng> {
match seed {
Some(s) => WienerProcess::with_seed(s, n_wiener),
None => WienerProcess::from_entropy(n_wiener),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wiener_increment() {
let mut wp = WienerProcess::with_seed(42, 2);
let inc = wp.increment::<f64>(0.01);
assert_eq!(inc.dw.len(), 2);
assert!((inc.dt - 0.01).abs() < 1e-10);
assert!(inc.dw[0].abs() > 0.0 || inc.dw[1].abs() > 0.0);
}
#[test]
fn test_wiener_path() {
let mut wp = WienerProcess::with_seed(42, 1);
let path = wp.path::<f64>(0.0, 1.0, 0.1);
assert!(path.len() >= 10 && path.len() <= 11);
let full_steps = path
.iter()
.filter(|inc| (inc.dt - 0.1).abs() < 0.01)
.count();
assert!(full_steps >= 9);
}
#[test]
fn test_wiener_variance() {
let mut wp = WienerProcess::with_seed(123, 1);
let n = 10000;
let dt = 0.01_f64;
let increments: Vec<f64> = (0..n).map(|_| wp.increment::<f64>(dt).dw[0]).collect();
let mean: f64 = increments.iter().sum::<f64>() / n as f64;
let variance: f64 = increments.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
assert!(mean.abs() < 0.01);
assert!((variance - dt).abs() < 0.001);
}
#[test]
fn test_correlated_wiener() {
let mut wp = WienerProcess::with_seed(42, 2);
let cholesky = [1.0, 0.0, 0.5, 0.75_f64.sqrt()];
let inc = wp.correlated_increment::<f64>(0.01, &cholesky);
assert_eq!(inc.dw.len(), 2);
}
}