use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::random::prelude::{Normal, Rng};
use scirs2_core::Distribution;
pub fn levy_area_wiktorsson<R: Rng>(
dim: usize,
h: f64,
_dw: &Array1<f64>,
k: usize,
rng: &mut R,
) -> Array2<f64> {
let mut a = Array2::<f64>::zeros((dim, dim));
if dim < 2 || k == 0 {
return a;
}
let normal = match Normal::new(0.0_f64, 1.0_f64) {
Ok(n) => n,
Err(_) => return a, };
let scale = h / (2.0 * std::f64::consts::PI);
for r in 1..=k {
let r_inv = 1.0 / r as f64;
let xi_r: Vec<f64> = (0..dim).map(|_| normal.sample(rng)).collect();
let eta_r: Vec<f64> = (0..dim).map(|_| normal.sample(rng)).collect();
for i in 0..dim {
for j in (i + 1)..dim {
let contrib = r_inv * (xi_r[i] * eta_r[j] - xi_r[j] * eta_r[i]);
a[[i, j]] += contrib;
a[[j, i]] -= contrib; }
}
}
a.mapv_inplace(|x| x * scale);
a
}
pub fn iterated_integral(dw: &Array1<f64>, h: f64, levy_area: &Array2<f64>) -> Array2<f64> {
let dim = dw.len();
let mut result = Array2::<f64>::zeros((dim, dim));
for i in 0..dim {
for j in 0..dim {
let symmetric_part = if i == j {
(dw[i] * dw[j] - h) / 2.0
} else {
dw[i] * dw[j] / 2.0
};
result[[i, j]] = symmetric_part + levy_area[[i, j]];
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
use scirs2_core::random::prelude::seeded_rng;
#[test]
fn test_levy_area_antisymmetry() {
let dim = 3;
let h = 0.1_f64;
let dw = array![0.05_f64, -0.03, 0.07];
let mut rng = seeded_rng(42);
let a = levy_area_wiktorsson(dim, h, &dw, 10, &mut rng);
for i in 0..dim {
for j in 0..dim {
assert!(
(a[[i, j]] + a[[j, i]]).abs() < 1e-14,
"Antisymmetry violated: A[{i},{j}]={:.6} but A[{j},{i}]={:.6}",
a[[i, j]],
a[[j, i]]
);
}
}
}
#[test]
fn test_levy_area_zero_diagonal() {
let dim = 4;
let h = 0.05_f64;
let dw = array![0.1_f64, -0.2, 0.05, -0.1];
let mut rng = seeded_rng(7);
let a = levy_area_wiktorsson(dim, h, &dw, 5, &mut rng);
for i in 0..dim {
assert!(
a[[i, i]].abs() < 1e-15,
"Diagonal should be zero: A[{i},{i}] = {:.6}",
a[[i, i]]
);
}
}
#[test]
fn test_levy_area_shape() {
let dim = 5;
let h = 0.01_f64;
let dw = Array1::zeros(dim);
let mut rng = seeded_rng(1);
let a = levy_area_wiktorsson(dim, h, &dw, 5, &mut rng);
assert_eq!(a.shape(), &[dim, dim]);
}
#[test]
fn test_levy_area_deterministic_with_seed() {
let dim = 2;
let h = 0.1_f64;
let dw = array![0.05_f64, -0.03];
let mut rng1 = seeded_rng(123);
let mut rng2 = seeded_rng(123);
let a1 = levy_area_wiktorsson(dim, h, &dw, 5, &mut rng1);
let a2 = levy_area_wiktorsson(dim, h, &dw, 5, &mut rng2);
assert!(
(a1[[0, 1]] - a2[[0, 1]]).abs() < 1e-15,
"Same seed should give same result"
);
}
#[test]
fn test_levy_area_variance_matches_theoretical() {
let dim = 2;
let h = 0.1_f64;
let dw = array![0.0_f64, 0.0];
let n_samples = 5000;
let theoretical = h * h / 12.0;
let tol_lo = theoretical * 0.7 * 0.70; let tol_hi = theoretical * 1.30;
let samples: Vec<f64> = (0..n_samples)
.map(|seed| {
let mut rng = seeded_rng(seed as u64);
let a = levy_area_wiktorsson(dim, h, &dw, 5, &mut rng);
a[[0, 1]]
})
.collect();
let mean: f64 = samples.iter().sum::<f64>() / n_samples as f64;
let var: f64 =
samples.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / (n_samples - 1) as f64;
assert!(
var > tol_lo && var < tol_hi,
"Lévy area variance {:.6e} outside [{:.6e}, {:.6e}] (theoretical h²/12 = {:.6e})",
var,
tol_lo,
tol_hi,
theoretical
);
}
#[test]
fn test_iterated_integral_shape() {
let dim = 3;
let h = 0.01_f64;
let dw = array![0.05_f64, -0.03, 0.07];
let levy = Array2::zeros((dim, dim));
let integ = iterated_integral(&dw, h, &levy);
assert_eq!(integ.shape(), &[dim, dim]);
}
#[test]
fn test_iterated_integral_symmetric_noise() {
let dim = 2;
let h = 0.1_f64;
let dw = array![0.15_f64, -0.08];
let levy_zero = Array2::zeros((dim, dim));
let integ = iterated_integral(&dw, h, &levy_zero);
let sum_off_diag = integ[[0, 1]] + integ[[1, 0]];
assert!(
(sum_off_diag - dw[0] * dw[1]).abs() < 1e-14,
"I_01 + I_10 should equal dW_0 * dW_1 = {:.6}, got {:.6}",
dw[0] * dw[1],
sum_off_diag
);
let expected_diag = (dw[0] * dw[0] - h) / 2.0;
assert!(
(integ[[0, 0]] - expected_diag).abs() < 1e-14,
"I_00 should equal (dW_0² - h)/2 = {:.6}, got {:.6}",
expected_diag,
integ[[0, 0]]
);
}
}