use crate::{
FloatExt, SimulationError, XResult, check_duration_time_step,
simulation::{continuous::Bm, prelude::*},
utils::{CirculantEmbedding, cumsum},
};
use rand_distr::{Distribution, StandardNormal};
use realfft::FftNum;
#[derive(Debug, Clone)]
pub struct FBm<T: FloatExt = f64> {
start_position: T,
hurst_exponent: T,
}
impl<T: FloatExt> FBm<T> {
pub fn new(start_position: T, hurst_exponent: T) -> XResult<Self> {
if hurst_exponent <= T::zero() || hurst_exponent >= T::one() {
return Err(SimulationError::InvalidParameters(format!(
"The `hurst_exponent` must be in the range (0, 1), got {hurst_exponent:?}"
))
.into());
}
Ok(Self {
start_position,
hurst_exponent,
})
}
pub fn get_start_position(&self) -> T {
self.start_position
}
pub fn get_hurst_exponent(&self) -> T {
self.hurst_exponent
}
}
impl<T: FloatExt + FftNum> ContinuousProcess<T> for FBm<T>
where
StandardNormal: Distribution<T>,
{
fn start(&self) -> T {
self.start_position
}
fn simulate(&self, duration: T, time_step: T) -> XResult<(Vec<T>, Vec<T>)> {
simulate_fbm(
self.start_position,
self.hurst_exponent,
duration,
time_step,
)
}
fn displacement(&self, duration: T, time_step: T) -> XResult<T> {
if self.hurst_exponent == T::from(0.5).unwrap() {
let bm = Bm::default();
return bm.displacement(duration, time_step);
}
let num_steps = ((duration / time_step).ceil().to_usize().unwrap()).max(1);
let dt = duration / T::from(num_steps).unwrap();
let mut circulant =
CirculantEmbedding::new(num_steps, fbm_correlation(self.hurst_exponent, dt));
let noise = circulant.generate()?;
let x = noise.into_iter().sum::<T>();
Ok(x)
}
}
pub fn simulate_fbm<T: FloatExt + FftNum>(
start_position: T,
hurst_exponent: T,
duration: T,
time_step: T,
) -> XResult<(Vec<T>, Vec<T>)>
where
StandardNormal: Distribution<T>,
{
check_duration_time_step(duration, time_step)?;
if hurst_exponent == T::from(0.5).unwrap() {
return crate::simulation::continuous::bm::simulate_bm(
start_position,
T::from(0.5).unwrap(),
duration,
time_step,
);
}
let num_steps = ((duration / time_step).ceil().to_usize().unwrap()).max(1);
let dt = duration / T::from(num_steps).unwrap();
let mut t = Vec::with_capacity(num_steps + 1);
for i in 0..=num_steps {
t.push(T::from(i).unwrap() * dt);
}
let mut circulant = CirculantEmbedding::new(num_steps, fbm_correlation(hurst_exponent, dt));
let noise = circulant.generate()?;
let x = cumsum(start_position, &noise);
Ok((t, x))
}
fn fbm_correlation<T: FloatExt>(hurst: T, time_step: T) -> impl Fn(usize) -> T {
move |k: usize| {
let two = T::from(2.0).unwrap();
let h2 = two * hurst;
T::from(0.5).unwrap()
* time_step.powf(h2)
* ((T::from(k + 1).unwrap()).powf(h2) - two * (T::from(k).unwrap()).powf(h2)
+ (T::from(k).unwrap() - T::one()).abs().powf(h2))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::simulation::prelude::Moment;
#[test]
fn test_simulate_fbm() {
let fbm = FBm::new(10.0, 0.5).unwrap();
let duration = 1.0;
let time_step = 0.1;
let (t, x) = fbm.simulate(duration, time_step).unwrap();
println!("t: {t:?}");
println!("x: {x:?}");
}
#[test]
fn test_raw_moment() {
let fbm = FBm::new(10.0, 0.5).unwrap();
let duration = 1.0;
let time_step = 0.1;
let traj = fbm.duration(duration).unwrap();
let moment = traj.raw_moment(1, 1000, time_step).unwrap();
println!("moment: {moment:?}");
}
#[test]
fn test_fpt() {
let fbm = FBm::new(0.0, 0.5).unwrap();
let time_step = 0.1;
let fpt = fbm.fpt((-1.0, 1.0), 1000.0, time_step).unwrap();
println!("fpt: {fpt:?}");
}
#[test]
fn test_occupation_time() {
let fbm = FBm::new(0.0, 0.5).unwrap();
let time_step = 0.1;
let ot = fbm.occupation_time((-1.0, 1.0), 10.0, time_step).unwrap();
println!("ot: {ot:?}");
}
#[test]
fn test_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<FBm>();
}
}