Skip to content

Commit e7dc082

Browse files
committed
feat: rework simd acceleration
1 parent 8eda5e3 commit e7dc082

File tree

30 files changed

+264
-506
lines changed

30 files changed

+264
-506
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "stochastic-rs"
3-
version = "0.15.1"
3+
version = "0.15.2"
44
edition = "2021"
55
license = "MIT"
66
description = "A Rust library for quant finance and simulating stochastic processes."
@@ -82,7 +82,7 @@ f64 = []
8282
jemalloc = ["dep:tikv-jemallocator"]
8383
malliavin = []
8484
mimalloc = ["dep:mimalloc"]
85-
simd = []
85+
simd = ["f32"]
8686
yahoo = ["dep:time", "dep:yahoo_finance_api", "dep:polars"]
8787

8888
[lib]

src/stochastic/diffusion/cev.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -61,25 +61,6 @@ impl SamplingExt<f64> for CEV<f64> {
6161
cev
6262
}
6363

64-
#[cfg(feature = "simd")]
65-
fn sample_simd(&self) -> Array1<f64> {
66-
use crate::stats::distr::normal::SimdNormal;
67-
68-
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64;
69-
let gn = Array1::random(self.n - 1, SimdNormal::new(0.0, dt.sqrt() as f32));
70-
71-
let mut cev = Array1::<f64>::zeros(self.n);
72-
cev[0] = self.x0.unwrap_or(0.0);
73-
74-
for i in 1..self.n {
75-
cev[i] = cev[i - 1]
76-
+ self.mu * cev[i - 1] * dt
77-
+ self.sigma * cev[i - 1].powf(self.gamma) * gn[i - 1] as f64
78-
}
79-
80-
cev
81-
}
82-
8364
/// Number of time steps
8465
fn n(&self) -> usize {
8566
self.n

src/stochastic/diffusion/cir.rs

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,34 +48,6 @@ impl SamplingExt<f64> for CIR<f64> {
4848
cir
4949
}
5050

51-
#[cfg(feature = "simd")]
52-
fn sample_simd(&self) -> Array1<f64> {
53-
use crate::stats::distr::normal::SimdNormal;
54-
55-
assert!(
56-
2.0 * self.theta * self.mu >= self.sigma.powi(2),
57-
"2 * theta * mu < sigma^2"
58-
);
59-
60-
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64;
61-
let gn = Array1::random(self.n - 1, SimdNormal::new(0.0, dt.sqrt() as f32));
62-
63-
let mut cir = Array1::<f64>::zeros(self.n);
64-
cir[0] = self.x0.unwrap_or(0.0);
65-
66-
for i in 1..self.n {
67-
let dcir = self.theta * (self.mu - cir[i - 1]) * dt
68-
+ self.sigma * (cir[i - 1]).abs().sqrt() * gn[i - 1] as f64;
69-
70-
cir[i] = match self.use_sym.unwrap_or(false) {
71-
true => (cir[i - 1] + dcir).abs(),
72-
false => (cir[i - 1] + dcir).max(0.0),
73-
};
74-
}
75-
76-
cir
77-
}
78-
7951
/// Number of time steps
8052
fn n(&self) -> usize {
8153
self.n

src/stochastic/diffusion/feller.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,30 @@ impl SamplingExt<f32> for FellerLogistic<f32> {
7575
x
7676
}
7777

78+
#[cfg(feature = "simd")]
79+
fn sample_simd(&self) -> Array1<f32> {
80+
use crate::stats::distr::normal::SimdNormal;
81+
82+
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f32;
83+
let gn = Array1::random(self.n - 1, SimdNormal::new(0.0, dt.sqrt()));
84+
85+
let mut x = Array1::<f32>::zeros(self.n);
86+
x[0] = self.x0.unwrap_or(0.0);
87+
88+
for i in 1..self.n {
89+
let xi = x[i - 1].max(0.0);
90+
let drift = self.kappa * (self.theta - xi) * xi * dt;
91+
let diff = self.sigma * xi.sqrt() * gn[i - 1];
92+
let next = xi + drift + diff;
93+
x[i] = match self.use_sym.unwrap_or(false) {
94+
true => next.abs(),
95+
false => next.max(0.0),
96+
};
97+
}
98+
99+
x
100+
}
101+
78102
fn n(&self) -> usize {
79103
self.n
80104
}

src/stochastic/diffusion/gbm.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,6 @@ impl SamplingExt<f64> for GBM<f64> {
5959
gbm
6060
}
6161

62-
#[cfg(feature = "simd")]
63-
fn sample_simd(&self) -> Array1<f64> {
64-
use crate::stats::distr::normal::SimdNormal;
65-
66-
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64;
67-
let gn = Array1::random(self.n - 1, SimdNormal::new(0.0, dt.sqrt() as f32));
68-
69-
let mut gbm = Array1::<f64>::zeros(self.n);
70-
gbm[0] = self.x0.unwrap_or(0.0);
71-
72-
for i in 1..self.n {
73-
gbm[i] = gbm[i - 1] + self.mu * gbm[i - 1] * dt + self.sigma * gbm[i - 1] * gn[i - 1] as f64
74-
}
75-
76-
gbm
77-
}
78-
7962
/// Number of time steps
8063
fn n(&self) -> usize {
8164
self.n

src/stochastic/diffusion/gbm_ih.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ impl SamplingExt<f32> for GBMIH<f32> {
7171
x
7272
}
7373

74+
#[cfg(feature = "simd")]
75+
fn sample_simd(&self) -> Array1<f32> {
76+
use crate::stats::distr::normal::SimdNormal;
77+
78+
if let Some(s) = &self.sigmas {
79+
assert_eq!(s.len(), self.n - 1, "sigmas length must be n - 1");
80+
}
81+
82+
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f32;
83+
let gn = Array1::random(self.n - 1, SimdNormal::new(0.0, dt.sqrt()));
84+
85+
let mut x = Array1::<f32>::zeros(self.n);
86+
x[0] = self.x0.unwrap_or(0.0);
87+
88+
for i in 1..self.n {
89+
let sigma_i = self.sigmas.as_ref().map(|s| s[i - 1]).unwrap_or(self.sigma);
90+
x[i] = x[i - 1] + self.mu * x[i - 1] * dt + sigma_i * x[i - 1] * gn[i - 1];
91+
}
92+
93+
x
94+
}
95+
7496
fn n(&self) -> usize {
7597
self.n
7698
}

src/stochastic/diffusion/gompertz.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,27 @@ impl SamplingExt<f32> for Gompertz<f32> {
6767
x
6868
}
6969

70+
#[cfg(feature = "simd")]
71+
fn sample_simd(&self) -> Array1<f32> {
72+
use crate::stats::distr::normal::SimdNormal;
73+
74+
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f32;
75+
let gn = Array1::random(self.n - 1, SimdNormal::new(0.0, dt.sqrt()));
76+
77+
let mut x = Array1::<f32>::zeros(self.n);
78+
x[0] = self.x0.unwrap_or(0.0).max(1e-8);
79+
80+
for i in 1..self.n {
81+
let xi = x[i - 1].max(1e-8);
82+
let drift = (self.a - self.b * xi.ln()) * xi * dt;
83+
let diff = self.sigma * xi * gn[i - 1];
84+
let next = xi + drift + diff;
85+
x[i] = next.max(1e-8);
86+
}
87+
88+
x
89+
}
90+
7091
fn n(&self) -> usize {
7192
self.n
7293
}

src/stochastic/diffusion/jacobi.rs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -46,36 +46,6 @@ impl SamplingExt<f64> for Jacobi<f64> {
4646
jacobi
4747
}
4848

49-
#[cfg(feature = "simd")]
50-
fn sample_simd(&self) -> Array1<f64> {
51-
use crate::stats::distr::normal::SimdNormal;
52-
53-
assert!(self.alpha > 0.0, "alpha must be positive");
54-
assert!(self.beta > 0.0, "beta must be positive");
55-
assert!(self.sigma > 0.0, "sigma must be positive");
56-
assert!(self.alpha < self.beta, "alpha must be less than beta");
57-
58-
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64;
59-
let gn = Array1::random(self.n - 1, SimdNormal::new(0.0, dt.sqrt() as f32));
60-
61-
let mut jacobi = Array1::<f64>::zeros(self.n);
62-
jacobi[0] = self.x0.unwrap_or(0.0);
63-
64-
for i in 1..self.n {
65-
jacobi[i] = match jacobi[i - 1] {
66-
_ if jacobi[i - 1] <= 0.0 && i > 0 => 0.0,
67-
_ if jacobi[i - 1] >= 1.0 && i > 0 => 1.0,
68-
_ => {
69-
jacobi[i - 1]
70-
+ (self.alpha - self.beta * jacobi[i - 1]) * dt
71-
+ self.sigma * (jacobi[i - 1] * (1.0 - jacobi[i - 1])).sqrt() * gn[i - 1] as f64
72-
}
73-
}
74-
}
75-
76-
jacobi
77-
}
78-
7949
/// Number of time steps
8050
fn n(&self) -> usize {
8151
self.n

src/stochastic/diffusion/kimura.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,29 @@ impl SamplingExt<f32> for Kimura<f32> {
7171
x
7272
}
7373

74+
#[cfg(feature = "simd")]
75+
fn sample_simd(&self) -> Array1<f32> {
76+
use crate::stats::distr::normal::SimdNormal;
77+
78+
let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f32;
79+
let gn = Array1::random(self.n - 1, SimdNormal::new(0.0, dt.sqrt()));
80+
81+
let mut x = Array1::<f32>::zeros(self.n);
82+
x[0] = self.x0.unwrap_or(0.0);
83+
84+
for i in 1..self.n {
85+
let xi = x[i - 1].clamp(0.0, 1.0);
86+
let sqrt_term = (xi * (1.0 - xi)).sqrt();
87+
let drift = self.a * xi * (1.0 - xi) * dt;
88+
let diff = self.sigma * sqrt_term * gn[i - 1];
89+
let mut next = xi + drift + diff;
90+
next = next.clamp(0.0, 1.0);
91+
x[i] = next;
92+
}
93+
94+
x
95+
}
96+
7497
fn n(&self) -> usize {
7598
self.n
7699
}

src/stochastic/diffusion/ou.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,6 @@ impl SamplingExt<f64> for OU<f64> {
3333
ou
3434
}
3535

36-
#[cfg(feature = "simd")]
37-
// TODO: experimental
38-
fn sample_simd(&self) -> Array1<f64> {
39-
use crate::stats::distr::normal::SimdNormal;
40-
41-
let dt = self.t.unwrap_or(1.0) as f32 / (self.n - 1) as f32;
42-
let gn = Array1::random(self.n, SimdNormal::new(0.0, dt.sqrt()));
43-
44-
let mut ou = Array1::<f64>::zeros(self.n);
45-
ou[0] = self.x0.unwrap_or(0.0);
46-
47-
for i in 1..self.n {
48-
ou[i] =
49-
ou[i - 1] + self.theta * (self.mu - ou[i - 1]) * dt as f64 + self.sigma * gn[i - 1] as f64
50-
}
51-
52-
ou
53-
}
54-
5536
/// Number of time steps
5637
fn n(&self) -> usize {
5738
self.n

0 commit comments

Comments
 (0)