1use numra_core::Scalar;
8use rand::prelude::*;
9use rand_distr::StandardNormal;
10
11#[derive(Clone, Debug)]
13pub struct WienerIncrement<S: Scalar> {
14 pub dw: Vec<S>,
16 pub dt: S,
18}
19
20impl<S: Scalar> WienerIncrement<S> {
21 pub fn new(dw: Vec<S>, dt: S) -> Self {
23 Self { dw, dt }
24 }
25
26 pub fn zeros(n_wiener: usize, dt: S) -> Self {
28 Self {
29 dw: vec![S::ZERO; n_wiener],
30 dt,
31 }
32 }
33}
34
35pub struct WienerProcess<R: Rng> {
39 rng: R,
40 n_wiener: usize,
41}
42
43impl<R: Rng> WienerProcess<R> {
44 pub fn new(rng: R, n_wiener: usize) -> Self {
46 Self { rng, n_wiener }
47 }
48
49 pub fn increment<S: Scalar>(&mut self, dt: S) -> WienerIncrement<S> {
51 let sqrt_dt = dt.sqrt();
52 let dw: Vec<S> = (0..self.n_wiener)
53 .map(|_| {
54 let z: f64 = self.rng.sample(StandardNormal);
55 S::from_f64(z) * sqrt_dt
56 })
57 .collect();
58 WienerIncrement::new(dw, dt)
59 }
60
61 pub fn path<S: Scalar>(&mut self, t0: S, tf: S, dt: S) -> Vec<WienerIncrement<S>> {
63 let mut increments = Vec::new();
64 let mut t = t0;
65 while t < tf {
66 let step = dt.min(tf - t);
67 increments.push(self.increment(step));
68 t += step;
69 }
70 increments
71 }
72
73 pub fn correlated_increment<S: Scalar>(
78 &mut self,
79 dt: S,
80 cholesky: &[S], ) -> WienerIncrement<S> {
82 let sqrt_dt = dt.sqrt();
83 let n = self.n_wiener;
84
85 let z: Vec<f64> = (0..n).map(|_| self.rng.sample(StandardNormal)).collect();
87
88 let mut dw = vec![S::ZERO; n];
90 for i in 0..n {
91 for j in 0..=i {
92 dw[i] += cholesky[i * n + j] * S::from_f64(z[j]);
93 }
94 dw[i] *= sqrt_dt;
95 }
96
97 WienerIncrement::new(dw, dt)
98 }
99}
100
101impl WienerProcess<StdRng> {
102 pub fn with_seed(seed: u64, n_wiener: usize) -> Self {
104 Self::new(StdRng::seed_from_u64(seed), n_wiener)
105 }
106
107 pub fn from_entropy(n_wiener: usize) -> Self {
109 Self::new(StdRng::from_entropy(), n_wiener)
110 }
111}
112
113pub fn create_wiener(n_wiener: usize, seed: Option<u64>) -> WienerProcess<StdRng> {
115 match seed {
116 Some(s) => WienerProcess::with_seed(s, n_wiener),
117 None => WienerProcess::from_entropy(n_wiener),
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[test]
126 fn test_wiener_increment() {
127 let mut wp = WienerProcess::with_seed(42, 2);
128 let inc = wp.increment::<f64>(0.01);
129
130 assert_eq!(inc.dw.len(), 2);
131 assert!((inc.dt - 0.01).abs() < 1e-10);
132 assert!(inc.dw[0].abs() > 0.0 || inc.dw[1].abs() > 0.0);
134 }
135
136 #[test]
137 fn test_wiener_path() {
138 let mut wp = WienerProcess::with_seed(42, 1);
139 let path = wp.path::<f64>(0.0, 1.0, 0.1);
140
141 assert!(path.len() >= 10 && path.len() <= 11);
143
144 let full_steps = path
146 .iter()
147 .filter(|inc| (inc.dt - 0.1).abs() < 0.01)
148 .count();
149 assert!(full_steps >= 9);
150 }
151
152 #[test]
153 fn test_wiener_variance() {
154 let mut wp = WienerProcess::with_seed(123, 1);
156 let n = 10000;
157 let dt = 0.01_f64;
158
159 let increments: Vec<f64> = (0..n).map(|_| wp.increment::<f64>(dt).dw[0]).collect();
160
161 let mean: f64 = increments.iter().sum::<f64>() / n as f64;
162 let variance: f64 = increments.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n as f64;
163
164 assert!(mean.abs() < 0.01);
166 assert!((variance - dt).abs() < 0.001);
168 }
169
170 #[test]
171 fn test_correlated_wiener() {
172 let mut wp = WienerProcess::with_seed(42, 2);
175 let cholesky = [1.0, 0.0, 0.5, 0.75_f64.sqrt()];
176 let inc = wp.correlated_increment::<f64>(0.01, &cholesky);
177
178 assert_eq!(inc.dw.len(), 2);
179 }
180}