use rand::{SeedableRng, rngs::StdRng};
use rand_distr::{Distribution, Normal};
pub fn laplace_random_walk(start: f64, num_steps: usize, scale: f64, drift: f64, seed: Option<u64>) -> Vec<f64> {
let mut rng = match seed {
Some(s) => StdRng::seed_from_u64(s),
None => StdRng::from_rng(&mut rand::rng()),
};
let normal = Normal::new(0.0, 1.0).unwrap();
let steps: Vec<f64> = (1..=num_steps)
.map(|_| {
let u: f64 = normal.sample(&mut rng);
let v: f64 = normal.sample(&mut rng);
drift + scale * (u.abs() - v.abs())
})
.collect();
let walk: Vec<f64> = steps
.iter()
.scan(start, |state, &x| {
*state += x;
Some(*state)
})
.collect();
std::iter::once(start).chain(walk).collect()
}
mod tests {
#[test]
fn test_laplace_random_walk() {
let start = 100.0;
let num_steps = 1000;
let scale = 0.1;
let drift = 0.0;
let seed = Some(42);
let walk = super::laplace_random_walk(start, num_steps, scale, drift, seed);
let plot = crate::internal_utils::simplified_snapshots::SnapshotP::build(&walk).draw();
insta::assert_snapshot!(plot, @r###"
▂▃▄▃ 103.50
▃ █████▆▁▆▇▄
▅█▅▆██████████▃ ▃▆▄▄
▄▄███████████████▅▅▆▂ ▂████
▅▅█████████████████████▅▇█████
███████████████████████████████
▂ ▂ ▅▄▁▄ ▁███████████████████████████████
▆██▃▁ ▂▁ ▅█▇▄ ▁ █████▁ ▅ ▃▅████████████████████████████████
▂▃ ▃ ▄█████▇ ▆▆▇██▇▆████▆▅▆█▇██████▇█▇ ▂▁██████████████████████████████████
██▃▅█▇▆ ▃ ███████▇ ▇█▅█████████████████████████▆████████████████████████████████████
█████████▇▃ ▁ ▇████████▄█████████████████████████████████████████████████████████████████
███████████▇█▇▇███████████████████████████████████████████████████████████████████████████98.73
"###);
}
}