|
| 1 | +use impl_new_derive::ImplNew; |
| 2 | +use ndarray::{s, Array1}; |
| 3 | +use ndarray_rand::RandomExt; |
| 4 | +use rand::Rng; |
| 5 | +use rand_distr::{Exp, Uniform}; |
| 6 | +use scilib::math::basic::gamma; |
| 7 | + |
| 8 | +use crate::{ |
| 9 | + stats::non_central_chi_squared, |
| 10 | + stochastic::{process::poisson::Poisson, Sampling}, |
| 11 | +}; |
| 12 | + |
| 13 | +#[derive(ImplNew)] |
| 14 | +pub struct SVCGMY { |
| 15 | + /// Positive jump rate lambda_plus (corresponds to G) |
| 16 | + pub lambda_plus: f64, // G |
| 17 | + /// Negative jump rate lambda_minus (corresponds to M) |
| 18 | + pub lambda_minus: f64, // M |
| 19 | + /// Jump activity parameter alpha (corresponds to Y), with 0 < alpha < 2 |
| 20 | + pub alpha: f64, |
| 21 | + /// |
| 22 | + pub kappa: f64, |
| 23 | + /// |
| 24 | + pub eta: f64, |
| 25 | + /// |
| 26 | + pub zeta: f64, |
| 27 | + /// |
| 28 | + pub rho: f64, |
| 29 | + /// Number of time steps |
| 30 | + pub n: usize, |
| 31 | + /// Jumps |
| 32 | + pub j: usize, |
| 33 | + /// |
| 34 | + pub x0: Option<f64>, |
| 35 | + /// Initial value |
| 36 | + pub v0: Option<f64>, |
| 37 | + /// Total time horizon |
| 38 | + pub t: Option<f64>, |
| 39 | + /// Number of samples for parallel sampling (not used in this implementation) |
| 40 | + pub m: Option<usize>, |
| 41 | +} |
| 42 | + |
| 43 | +impl Sampling<f64> for SVCGMY { |
| 44 | + fn sample(&self) -> Array1<f64> { |
| 45 | + let t_max = self.t.unwrap_or(1.0); |
| 46 | + let dt = self.t.unwrap_or(1.0) / (self.n - 1) as f64; |
| 47 | + let mut x = Array1::<f64>::zeros(self.n); |
| 48 | + x[0] = self.x0.unwrap_or(0.0); |
| 49 | + |
| 50 | + let C = (gamma(2.0 - self.alpha) |
| 51 | + * (self.lambda_plus.powf(self.alpha - 2.0) + self.lambda_minus.powf(self.alpha - 2.0))) |
| 52 | + .powi(-1); |
| 53 | + let c = (2.0 * self.kappa) / ((1.0 - (-self.kappa * dt).exp()) * self.zeta.powi(2)); |
| 54 | + |
| 55 | + let mut v = Array1::<f64>::zeros(self.n); |
| 56 | + v[0] = self.v0.unwrap_or(0.0); |
| 57 | + |
| 58 | + for i in 1..self.n { |
| 59 | + let xi = non_central_chi_squared::sample( |
| 60 | + 4.0 * self.kappa * self.eta / self.zeta.powi(2), |
| 61 | + 2.0 * c * v[i - 1] * (-self.kappa * dt).exp(), |
| 62 | + ); |
| 63 | + |
| 64 | + v[i] = xi / (2.0 * c); |
| 65 | + } |
| 66 | + |
| 67 | + let U = Array1::<f64>::random(self.j, Uniform::new(0.0, 1.0)); |
| 68 | + let E = Array1::<f64>::random(self.j, Exp::new(1.0).unwrap()); |
| 69 | + let E_ = Array1::<f64>::random(self.j, Exp::new(1.0).unwrap()); |
| 70 | + let P = Poisson::new(1.0, Some(self.j), None, None); |
| 71 | + let mut P = P.sample(); |
| 72 | + |
| 73 | + for (idx, p) in P.iter_mut().enumerate() { |
| 74 | + *p = *p + E_[idx]; |
| 75 | + } |
| 76 | + |
| 77 | + let tau = Array1::<f64>::random(self.j, Uniform::new(0.0, t_max)); |
| 78 | + let mut c_t = Array1::<f64>::zeros(self.j); |
| 79 | + |
| 80 | + for j in 1..self.j { |
| 81 | + c_t[j] = C * v.slice(s![..j - 1]).sum() |
| 82 | + } |
| 83 | + |
| 84 | + let mut rng = rand::thread_rng(); |
| 85 | + |
| 86 | + for i in 1..self.n { |
| 87 | + let mut jump_component = 0.0; |
| 88 | + |
| 89 | + for j in 1..self.j { |
| 90 | + let v_j = if rng.gen_bool(0.5) { |
| 91 | + self.lambda_plus |
| 92 | + } else { |
| 93 | + -self.lambda_minus |
| 94 | + }; |
| 95 | + |
| 96 | + let term1 = ((self.alpha * P[j]) / (2.0 * c_t[j] * t_max)).powf(-1.0 / self.alpha); |
| 97 | + let term2 = E[j] * U[j].powf(1.0 / self.alpha) / v_j.abs(); |
| 98 | + let jump_size = term1.min(term2) * (v_j / v_j.abs()); |
| 99 | + |
| 100 | + jump_component += jump_size; |
| 101 | + } |
| 102 | + |
| 103 | + let b = -(v[i] |
| 104 | + * (self.lambda_plus.powf(self.alpha - 1.0) - self.lambda_minus.powf(self.alpha - 1.0)) |
| 105 | + / ((1.0 - self.alpha) |
| 106 | + * (self.lambda_plus.powf(self.alpha - 2.0) + self.lambda_minus.powf(self.alpha) - 2.0))); |
| 107 | + |
| 108 | + x[i] = x[i - 1] + jump_component + b * dt + self.rho * v[i - 1]; |
| 109 | + } |
| 110 | + |
| 111 | + x |
| 112 | + } |
| 113 | + |
| 114 | + fn n(&self) -> usize { |
| 115 | + self.n |
| 116 | + } |
| 117 | + |
| 118 | + fn m(&self) -> Option<usize> { |
| 119 | + self.m |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +#[cfg(test)] |
| 124 | +mod tests { |
| 125 | + use super::*; |
| 126 | + use crate::{plot_1d, stochastic::N}; |
| 127 | + |
| 128 | + #[test] |
| 129 | + fn svcgmy_length_equals_n() { |
| 130 | + let svcgmy = SVCGMY::new( |
| 131 | + 25.46, |
| 132 | + 4.604, |
| 133 | + 0.52, |
| 134 | + 1.003, |
| 135 | + 0.0711, |
| 136 | + 0.3443, |
| 137 | + -2.0280, |
| 138 | + N, |
| 139 | + 1024, |
| 140 | + None, |
| 141 | + Some(0.0064), |
| 142 | + Some(1.0), |
| 143 | + None, |
| 144 | + ); |
| 145 | + assert_eq!(svcgmy.sample().len(), N); |
| 146 | + } |
| 147 | + |
| 148 | + #[test] |
| 149 | + fn svcgmy_starts_with_x0() { |
| 150 | + let svcgmy = SVCGMY::new( |
| 151 | + 25.46, |
| 152 | + 4.604, |
| 153 | + 0.52, |
| 154 | + 1.003, |
| 155 | + 0.0711, |
| 156 | + 0.3443, |
| 157 | + -2.0280, |
| 158 | + N, |
| 159 | + 1024, |
| 160 | + None, |
| 161 | + Some(0.0064), |
| 162 | + Some(1.0), |
| 163 | + None, |
| 164 | + ); |
| 165 | + assert_eq!(svcgmy.sample()[0], 0.0); |
| 166 | + } |
| 167 | + |
| 168 | + #[test] |
| 169 | + fn svcgmy_plot() { |
| 170 | + let svcgmy = SVCGMY::new( |
| 171 | + 25.46, |
| 172 | + 4.604, |
| 173 | + 0.52, |
| 174 | + 1.003, |
| 175 | + 0.0711, |
| 176 | + 0.3443, |
| 177 | + -2.0280, |
| 178 | + 100, |
| 179 | + 1024, |
| 180 | + None, |
| 181 | + Some(0.0064), |
| 182 | + Some(1.0), |
| 183 | + None, |
| 184 | + ); |
| 185 | + plot_1d!(svcgmy.sample(), "CGMY Process"); |
| 186 | + } |
| 187 | +} |
0 commit comments