Skip to content

Commit 2ded411

Browse files
committed
feat: add multiple fou estimartors
1 parent d253ed9 commit 2ded411

File tree

2 files changed

+313
-16
lines changed

2 files changed

+313
-16
lines changed

src/stats.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ pub mod cir;
22
pub mod double_exp;
33
pub mod fd;
44
pub mod fou_estimator;
5+
pub mod fou_estimator_v2;
56
pub mod mle;
67
pub mod non_central_chi_squared;

src/stats/fou_estimator.rs

Lines changed: 312 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
use impl_new_derive::ImplNew;
2-
use ndarray::{array, Array1};
2+
use ndarray::{array, s, Array1};
33
use statrs::function::gamma::gamma;
44
use std::f64::consts::SQRT_2;
55

6+
use crate::stochastic::{noise::fgn::FGN, Sampling};
7+
8+
// Version 1: FOUParameterEstimationV1 with linear filter methods
69
#[derive(ImplNew)]
7-
pub struct FOUParameterEstimation {
10+
pub struct FOUParameterEstimationV1 {
811
pub path: Array1<f64>,
9-
pub T: f64,
1012
pub filter_type: FilterType,
1113
// Estimated parameters
1214
hurst: Option<f64>,
@@ -26,7 +28,7 @@ pub enum FilterType {
2628
Classical,
2729
}
2830

29-
impl FOUParameterEstimation {
31+
impl FOUParameterEstimationV1 {
3032
pub fn estimate_parameters(&mut self) -> (f64, f64, f64, f64) {
3133
self.linear_filter();
3234
self.hurst_estimator();
@@ -86,7 +88,7 @@ impl FOUParameterEstimation {
8688
let denominator = sigma.powi(2) * gamma(2.0 * hurst + 1.0);
8789
let theta = (numerator / denominator).powf(-1.0 / (2.0 * hurst));
8890

89-
self.theta = Some(theta / self.T);
91+
self.theta = Some(theta);
9092
}
9193

9294
fn linear_filter(&mut self) {
@@ -138,7 +140,6 @@ impl FOUParameterEstimation {
138140
}
139141

140142
fn lfilter(&self, b: &Array1<f64>, a: &Array1<f64>, x: &Array1<f64>) -> Array1<f64> {
141-
// Implementing the difference equation: y[n] = b[0]*x[n] + b[1]*x[n-1] + ... - a[1]*y[n-1] - ...
142143
let n = x.len();
143144
let mut y = Array1::<f64>::zeros(n);
144145

@@ -161,20 +162,268 @@ impl FOUParameterEstimation {
161162
}
162163
}
163164

165+
// Version 2: FOUParameterEstimationV2 without linear filters
166+
#[derive(ImplNew)]
167+
pub struct FOUParameterEstimationV2 {
168+
pub path: Array1<f64>,
169+
pub delta: f64,
170+
pub series_length: usize,
171+
// Estimated parameters
172+
hurst: Option<f64>,
173+
sigma: Option<f64>,
174+
mu: Option<f64>,
175+
theta: Option<f64>,
176+
}
177+
178+
impl FOUParameterEstimationV2 {
179+
pub fn estimate_parameters(&mut self) -> (f64, f64, f64, f64) {
180+
self.hurst_estimator();
181+
self.sigma_estimator();
182+
self.mu_estimator();
183+
self.theta_estimator();
184+
185+
(
186+
self.hurst.unwrap(),
187+
self.sigma.unwrap(),
188+
self.mu.unwrap(),
189+
self.theta.unwrap(),
190+
)
191+
}
192+
193+
fn hurst_estimator(&mut self) {
194+
let X = &self.path;
195+
let N = self.series_length;
196+
197+
let sum1: f64 = (0..(N - 4))
198+
.map(|i| {
199+
let diff = X[i + 4] - 2.0 * X[i + 2] + X[i];
200+
diff * diff
201+
})
202+
.sum();
203+
204+
let sum2: f64 = (0..(N - 2))
205+
.map(|i| {
206+
let diff = X[i + 2] - 2.0 * X[i + 1] + X[i];
207+
diff * diff
208+
})
209+
.sum();
210+
211+
let estimated_hurst = 0.5 * (sum1 / sum2).log2();
212+
self.hurst = Some(estimated_hurst);
213+
}
214+
215+
fn sigma_estimator(&mut self) {
216+
let H = self.hurst.unwrap();
217+
let X = &self.path;
218+
let N = self.series_length as f64;
219+
let delta = self.delta;
220+
221+
let numerator: f64 = (0..(self.series_length - 2))
222+
.map(|i| {
223+
let diff = X[i + 2] - 2.0 * X[i + 1] + X[i];
224+
diff * diff
225+
})
226+
.sum();
227+
228+
let denominator = N * (4.0 - 2.0_f64.powf(2.0 * H)) * delta.powf(2.0 * H);
229+
let estimated_sigma = (numerator / denominator).sqrt();
230+
self.sigma = Some(estimated_sigma);
231+
}
232+
233+
fn mu_estimator(&mut self) {
234+
let mean = self.path.mean().unwrap();
235+
self.mu = Some(mean);
236+
}
237+
238+
fn theta_estimator(&mut self) {
239+
let X = &self.path;
240+
let H = self.hurst.unwrap();
241+
let N = self.series_length as f64;
242+
let sigma = self.sigma.unwrap();
243+
244+
let sum_X_squared = X.mapv(|x| x * x).sum();
245+
let sum_X = X.sum();
246+
let numerator = N * sum_X_squared - sum_X.powi(2);
247+
let denominator = N.powi(2) * sigma.powi(2) * H * gamma(2.0 * H);
248+
249+
let estimated_theta = (numerator / denominator).powf(-1.0 / (2.0 * H));
250+
self.theta = Some(estimated_theta);
251+
}
252+
}
253+
254+
// Version 3: FOUParameterEstimationV3 with get_path method
255+
pub struct FOUParameterEstimationV3 {
256+
alpha: f64,
257+
mu: f64,
258+
sigma: f64,
259+
initial_value: f64,
260+
T: f64,
261+
delta: f64,
262+
series_length: usize,
263+
hurst: f64,
264+
path: Option<Array1<f64>>,
265+
// Estimated parameters
266+
estimated_hurst: Option<f64>,
267+
estimated_sigma: Option<f64>,
268+
estimated_mu: Option<f64>,
269+
estimated_alpha: Option<f64>,
270+
}
271+
272+
impl FOUParameterEstimationV3 {
273+
pub fn new(
274+
series_length: usize,
275+
hurst: f64,
276+
sigma: f64,
277+
alpha: f64,
278+
mu: f64,
279+
initial_value: f64,
280+
T: f64,
281+
delta: f64,
282+
) -> Self {
283+
FOUParameterEstimationV3 {
284+
alpha,
285+
mu,
286+
sigma,
287+
initial_value,
288+
T,
289+
delta,
290+
series_length,
291+
hurst,
292+
path: None,
293+
estimated_hurst: None,
294+
estimated_sigma: None,
295+
estimated_mu: None,
296+
estimated_alpha: None,
297+
}
298+
}
299+
300+
pub fn estimate_parameters(&mut self) -> (f64, f64, f64, f64) {
301+
self.get_path();
302+
self.hurst_estimator();
303+
self.sigma_estimator();
304+
self.mu_estimator();
305+
self.alpha_estimator();
306+
307+
(
308+
self.estimated_hurst.unwrap(),
309+
self.estimated_sigma.unwrap(),
310+
self.estimated_mu.unwrap(),
311+
self.estimated_alpha.unwrap(),
312+
)
313+
}
314+
315+
fn get_path(&mut self) {
316+
let M = 8;
317+
let gamma = self.delta / M as f64;
318+
319+
let fgn_length = self.series_length * M;
320+
321+
// Generate fGN sample of length fgn_length
322+
let fgn = FGN::new(self.hurst, fgn_length - 1, Some(self.T), None);
323+
let fgn_sample = fgn.sample();
324+
325+
// Initialize full_fou array
326+
let mut full_fou = Array1::<f64>::zeros(fgn_length);
327+
full_fou[0] = self.initial_value;
328+
329+
for i in 1..fgn_length {
330+
full_fou[i] = full_fou[i - 1]
331+
+ self.alpha * (self.mu - full_fou[i - 1]) * gamma
332+
+ self.sigma * fgn_sample[i - 1];
333+
}
334+
335+
// Initialize fou array
336+
let mut fou = Array1::<f64>::zeros(self.series_length);
337+
fou[0] = self.initial_value;
338+
339+
for i in 1..self.series_length {
340+
let start = (i - 1) * M;
341+
let end = i * M;
342+
343+
let sum_sub_series: f64 = full_fou.slice(s![start..end]).sum() * gamma / M as f64;
344+
fou[i] = full_fou[end - 1] + self.alpha * sum_sub_series;
345+
}
346+
347+
// Store the path
348+
self.path = Some(fou);
349+
}
350+
351+
fn hurst_estimator(&mut self) {
352+
let X = self.path.as_ref().unwrap();
353+
let N = self.series_length;
354+
355+
let sum1: f64 = (0..(N - 4))
356+
.map(|i| {
357+
let diff = X[i + 4] - 2.0 * X[i + 2] + X[i];
358+
diff * diff
359+
})
360+
.sum();
361+
362+
let sum2: f64 = (0..(N - 2))
363+
.map(|i| {
364+
let diff = X[i + 2] - 2.0 * X[i + 1] + X[i];
365+
diff * diff
366+
})
367+
.sum();
368+
369+
let estimated_hurst = 0.5 * (sum1 / sum2).log2();
370+
self.estimated_hurst = Some(estimated_hurst);
371+
}
372+
373+
fn sigma_estimator(&mut self) {
374+
let H = self.estimated_hurst.unwrap();
375+
let X = self.path.as_ref().unwrap();
376+
let N = self.series_length as f64;
377+
let delta = self.delta;
378+
379+
let numerator: f64 = (0..(self.series_length - 2))
380+
.map(|i| {
381+
let diff = X[i + 2] - 2.0 * X[i + 1] + X[i];
382+
diff * diff
383+
})
384+
.sum();
385+
386+
let denominator = N * (4.0 - 2.0_f64.powf(2.0 * H)) * delta.powf(2.0 * H);
387+
let estimated_sigma = (numerator / denominator).sqrt();
388+
self.estimated_sigma = Some(estimated_sigma);
389+
}
390+
391+
fn mu_estimator(&mut self) {
392+
let X = self.path.as_ref().unwrap();
393+
let mean = X.mean().unwrap();
394+
self.estimated_mu = Some(mean);
395+
}
396+
397+
fn alpha_estimator(&mut self) {
398+
let X = self.path.as_ref().unwrap();
399+
let H = self.estimated_hurst.unwrap();
400+
let N = self.series_length as f64;
401+
let sigma = self.estimated_sigma.unwrap();
402+
403+
let sum_X_squared = X.mapv(|x| x * x).sum();
404+
let sum_X = X.sum();
405+
let numerator = N * sum_X_squared - sum_X.powi(2);
406+
let denominator = N.powi(2) * sigma.powi(2) * H * gamma(2.0 * H);
407+
408+
let estimated_alpha = (numerator / denominator).powf(-1.0 / (2.0 * H));
409+
self.estimated_alpha = Some(estimated_alpha);
410+
}
411+
}
412+
164413
#[cfg(test)]
165414
mod tests {
166415
use super::*;
167416
use crate::stochastic::{diffusion::fou::FOU, noise::fgn::FGN, Sampling};
168417

169418
#[test]
170-
fn test_fou_parameter_estimation() {
419+
fn test_fou_parameter_estimation_v1() {
171420
const N: usize = 10000;
172421
const X0: f64 = 0.0;
173422

174-
let fgn = FGN::new(0.75, 1599, Some(1.0), None);
175-
let fou = FOU::new(3.0, 5.0, 3.0, 1600, Some(X0), Some(16.0), None, fgn);
423+
let fgn = FGN::new(0.70, 4095, Some(1.0), None);
424+
let fou = FOU::new(5.0, 2.8, 1.0, 4096, Some(X0), Some(16.0), None, fgn);
176425
let path = fou.sample();
177-
let mut estimator = FOUParameterEstimation::new(path, 1.0, FilterType::Daubechies);
426+
let mut estimator = FOUParameterEstimationV1::new(path, FilterType::Daubechies);
178427

179428
// Estimate the parameters
180429
let (estimated_hurst, estimated_sigma, estimated_mu, estimated_theta) =
@@ -185,13 +434,60 @@ mod tests {
185434
println!("Estimated sigma: {}", estimated_sigma);
186435
println!("Estimated mu: {}", estimated_mu);
187436
println!("Estimated theta: {}", estimated_theta);
437+
}
188438

189-
// Assert that the estimated parameters are close to the original ones
190-
let tolerance = 0.1; // Adjust tolerance as needed
439+
#[test]
440+
fn test_fou_parameter_estimation_v2() {
441+
const N: usize = 4096;
442+
const X0: f64 = 0.0;
443+
let delta = 1.0 / 256.0;
444+
445+
let fgn = FGN::new(0.70, N - 1, Some(1.0), None);
446+
let fou = FOU::new(5.0, 2.8, 2.0, N, Some(X0), Some(16.0), None, fgn);
447+
let path = fou.sample();
448+
let mut estimator = FOUParameterEstimationV2::new(path, delta, N);
191449

192-
assert!((estimated_hurst - 0.75).abs() < tolerance);
193-
assert!((estimated_sigma - 2.0).abs() < tolerance);
194-
assert!((estimated_mu - 3.0).abs() < tolerance);
195-
assert!((estimated_theta - 2.0).abs() < tolerance);
450+
// Estimate the parameters
451+
let (estimated_hurst, estimated_sigma, estimated_mu, estimated_theta) =
452+
estimator.estimate_parameters();
453+
454+
// Print the estimated parameters
455+
println!("Estimated Hurst exponent: {}", estimated_hurst);
456+
println!("Estimated sigma: {}", estimated_sigma);
457+
println!("Estimated mu: {}", estimated_mu);
458+
println!("Estimated theta: {}", estimated_theta);
459+
}
460+
461+
#[test]
462+
fn test_fou_parameter_estimation_v3() {
463+
let series_length = 4096;
464+
let hurst = 0.70;
465+
let sigma = 2.0;
466+
let alpha = 5.0;
467+
let mu = 2.8;
468+
let initial_value = 0.0;
469+
let T = 16.0;
470+
let delta = 1.0 / 256.0;
471+
472+
let mut estimator = FOUParameterEstimationV3::new(
473+
series_length,
474+
hurst,
475+
sigma,
476+
alpha,
477+
mu,
478+
initial_value,
479+
T,
480+
delta,
481+
);
482+
483+
// Estimate the parameters
484+
let (estimated_hurst, estimated_sigma, estimated_mu, estimated_alpha) =
485+
estimator.estimate_parameters();
486+
487+
// Print the estimated parameters
488+
println!("Estimated Hurst exponent: {}", estimated_hurst);
489+
println!("Estimated sigma: {}", estimated_sigma);
490+
println!("Estimated mu: {}", estimated_mu);
491+
println!("Estimated alpha: {}", estimated_alpha);
196492
}
197493
}

0 commit comments

Comments
 (0)