runmat_runtime/builtins/stats/random/
stochastic_evolution.rs

1//! Host helper for the Monte Carlo evolution loop when GPU acceleration is
2//! unavailable.
3
4use crate::builtins::common::random;
5use runmat_builtins::Tensor;
6
7pub fn stochastic_evolution_host(
8    tensor: &mut Tensor,
9    drift: f64,
10    scale: f64,
11    steps: u32,
12) -> Result<(), String> {
13    if tensor.data.is_empty() || steps == 0 {
14        return Ok(());
15    }
16
17    let len = tensor.data.len();
18    for _ in 0..steps {
19        let samples = random::generate_normal(len, "stochastic_evolution_host")?;
20        for (value, noise) in tensor.data.iter_mut().zip(samples.iter()) {
21            let term = drift + scale * noise;
22            *value *= term.exp();
23        }
24    }
25
26    Ok(())
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use crate::builtins::common::random;
33
34    #[test]
35    fn cpu_fallback_handles_zero_scale() {
36        let _guard = random::test_lock().lock().unwrap();
37        random::reset_rng();
38        let mut tensor = Tensor::new(vec![1.0, 2.0], vec![2, 1]).expect("tensor");
39        stochastic_evolution_host(&mut tensor, 0.1, 0.0, 3).expect("evolve");
40        let expected = (0..2)
41            .map(|i| (i as f64 + 1.0) * (0.1f64 * 3.0).exp())
42            .collect::<Vec<_>>();
43        assert_eq!(tensor.shape, vec![2, 1]);
44        for (got, exp) in tensor.data.iter().zip(expected.iter()) {
45            assert!((got - exp).abs() < 1e-12, "got {got} expected {exp}");
46        }
47    }
48}