use std::f64::consts::PI;
use super::DiffusionModel;
pub(super) fn gaussian_pdf(x: f64, mean: f64, var: f64) -> f64 {
if var <= 0.0 {
return 1e-30;
}
let z = (x - mean) / var.sqrt();
(-0.5 * z * z).exp() / (2.0 * PI * var).sqrt()
}
#[derive(Clone, Copy, Debug)]
pub enum DensityApprox {
Exact,
Euler,
Ozaki,
ShojiOzaki,
Elerian,
Kessler,
AitSahalia,
}
impl DensityApprox {
pub fn density(&self, model: &dyn DiffusionModel, x0: f64, xt: f64, t0: f64, dt: f64) -> f64 {
match self {
DensityApprox::Exact => Self::density_exact(model, x0, xt, t0, dt),
DensityApprox::Euler => Self::density_euler(model, x0, xt, t0, dt),
DensityApprox::Ozaki => Self::density_ozaki(model, x0, xt, t0, dt),
DensityApprox::ShojiOzaki => Self::density_shoji_ozaki(model, x0, xt, t0, dt),
DensityApprox::Elerian => Self::density_elerian(model, x0, xt, t0, dt),
DensityApprox::Kessler => Self::density_kessler(model, x0, xt, t0, dt),
DensityApprox::AitSahalia => Self::density_ait_sahalia(model, x0, xt, t0, dt),
}
}
fn density_exact(model: &dyn DiffusionModel, x0: f64, xt: f64, t0: f64, dt: f64) -> f64 {
model
.exact_density(x0, xt, t0, dt)
.expect("Exact density not implemented for this model")
}
fn density_euler(model: &dyn DiffusionModel, x0: f64, xt: f64, t0: f64, dt: f64) -> f64 {
let mu = model.drift(x0, t0);
let sig = model.diffusion(x0, t0);
let mean = x0 + mu * dt;
let var = sig * sig * dt;
gaussian_pdf(xt, mean, var)
}
fn density_ozaki(model: &dyn DiffusionModel, x0: f64, xt: f64, t0: f64, dt: f64) -> f64 {
let mu = model.drift(x0, t0);
let sig = model.diffusion(x0, t0);
let mu_x = model.drift_x(x0, t0);
if mu_x.abs() < 1e-10 {
return Self::density_euler(model, x0, xt, t0, dt);
}
let mt = x0 + mu * ((mu_x * dt).exp() - 1.0) / mu_x;
let diff = mt - x0;
let kt = if x0.abs() > 1e-10 {
let ratio = 1.0 + diff / x0;
if ratio > 1e-30 {
(2.0 / dt) * ratio.ln()
} else {
mu_x
}
} else {
mu_x
};
let exp_kt_dt = (kt * dt).exp();
let vt_sq = if kt.abs() > 1e-10 && exp_kt_dt > 1.0 {
sig * sig * (exp_kt_dt - 1.0) / kt
} else {
sig * sig * dt
};
gaussian_pdf(xt, mt, vt_sq)
}
fn density_shoji_ozaki(model: &dyn DiffusionModel, x0: f64, xt: f64, t0: f64, dt: f64) -> f64 {
let mu = model.drift(x0, t0);
let sig = model.diffusion(x0, t0);
let mu_x = model.drift_x(x0, t0);
let mu_xx = model.drift_xx(x0, t0);
let mu_t = model.drift_t(x0, t0);
let mt = 0.5 * sig * sig * mu_xx + mu_t;
let lt = mu_x;
if lt.abs() > 1e-10 {
let exp_lt = (lt * dt).exp();
let b2 = sig * sig * ((2.0 * lt * dt).exp() - 1.0) / (2.0 * lt);
let a = x0 + mu / lt * (exp_lt - 1.0) + mt / (lt * lt) * (exp_lt - 1.0 - lt * dt);
gaussian_pdf(xt, a, b2)
} else {
let b2 = sig * sig * dt;
let a = x0 + mu * dt + mt * dt * dt / 2.0;
gaussian_pdf(xt, a, b2)
}
}
fn density_elerian(model: &dyn DiffusionModel, x0: f64, xt: f64, t0: f64, dt: f64) -> f64 {
let mu = model.drift(x0, t0);
let sig = model.diffusion(x0, t0);
let sig_x = model.diffusion_x(x0, t0);
if sig_x.abs() < 1e-10 {
return Self::density_euler(model, x0, xt, t0, dt);
}
let a_coeff = sig * sig_x * dt * 0.5;
let b_coeff = -0.5 * sig / sig_x + x0 + mu * dt - a_coeff;
if a_coeff.abs() < 1e-30 {
return Self::density_euler(model, x0, xt, t0, dt);
}
let z = (xt - b_coeff) / a_coeff;
let c = 1.0 / (sig_x * sig_x * dt);
if z <= 0.0 {
return 1e-30;
}
let val = z.powf(-0.5) * (c * z).sqrt().cosh() * (-0.5 * (c + z)).exp()
/ (2.0 * a_coeff.abs() * (2.0 * PI).sqrt());
if val.is_finite() && val > 0.0 {
val
} else {
1e-30
}
}
fn density_kessler(model: &dyn DiffusionModel, x0: f64, xt: f64, t0: f64, dt: f64) -> f64 {
let mu = model.drift(x0, t0);
let sig = model.diffusion(x0, t0);
let mu_x = model.drift_x(x0, t0);
let sig_x = model.diffusion_x(x0, t0);
let sig_xx = model.diffusion_xx(x0, t0);
let sig2 = sig * sig;
let d = dt * dt / 2.0;
let e_x = x0 + mu * dt + (mu * mu_x + 0.5 * sig2 * sig_xx) * d;
let term = 2.0 * sig * sig_x;
let e_x2 = x0 * x0
+ (2.0 * mu * x0 + sig2) * dt
+ (2.0 * mu * (mu_x * x0 + mu + sig * sig_x)
+ sig2 * (sig_xx * x0 + 2.0 * sig_x + term + sig * sig_xx))
* d;
let v = (e_x2 - e_x * e_x).abs();
let std = v.sqrt();
if std < 1e-30 {
return 1e-30;
}
let z = (xt - e_x) / std;
(-0.5 * z * z).exp() / ((2.0 * PI).sqrt() * std)
}
fn density_ait_sahalia(model: &dyn DiffusionModel, x0: f64, xt: f64, t0: f64, dt: f64) -> f64 {
model
.ait_sahalia_density(x0, xt, t0, dt)
.expect("Ait-Sahalia density not implemented for this model")
}
}