moonpool_sim/providers/
random.rs

1//! Simulation random provider implementation.
2
3use moonpool_core::RandomProvider;
4use rand::distr::{Distribution, StandardUniform, uniform::SampleUniform};
5use std::ops::Range;
6
7use crate::sim::rng::{set_sim_seed, sim_random, sim_random_range};
8
9/// Random provider for simulation that uses the thread-local deterministic RNG.
10///
11/// This provider wraps the existing thread-local RNG infrastructure in
12/// `crate::sim::rng` to provide deterministic random number generation within
13/// the simulation environment.
14///
15/// The provider sets the thread-local seed during construction and then
16/// delegates all random generation to the existing `sim_random()` functions.
17#[derive(Clone, Debug)]
18pub struct SimRandomProvider {
19    // No internal state - uses thread-local RNG from crate::sim::rng
20    _marker: std::marker::PhantomData<()>,
21}
22
23impl SimRandomProvider {
24    /// Create a new simulation random provider with the specified seed.
25    ///
26    /// This sets the thread-local RNG seed using `set_sim_seed()` and
27    /// creates a provider that will use that seeded RNG for all operations.
28    ///
29    /// # Arguments
30    ///
31    /// * `seed` - The seed value for deterministic random generation
32    pub fn new(seed: u64) -> Self {
33        // Set the thread-local RNG seed
34        set_sim_seed(seed);
35
36        Self {
37            _marker: std::marker::PhantomData,
38        }
39    }
40}
41
42impl RandomProvider for SimRandomProvider {
43    fn random<T>(&self) -> T
44    where
45        StandardUniform: Distribution<T>,
46    {
47        sim_random()
48    }
49
50    fn random_range<T>(&self, range: Range<T>) -> T
51    where
52        T: SampleUniform + PartialOrd,
53    {
54        sim_random_range(range)
55    }
56
57    fn random_ratio(&self) -> f64 {
58        sim_random::<f64>()
59    }
60
61    fn random_bool(&self, probability: f64) -> bool {
62        debug_assert!(
63            (0.0..=1.0).contains(&probability),
64            "Probability must be between 0.0 and 1.0, got {}",
65            probability
66        );
67        sim_random::<f64>() < probability
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_deterministic_randomness() {
77        // Two providers with same seed should produce same values
78        let provider1 = SimRandomProvider::new(42);
79        let value1_1: f64 = provider1.random();
80        let value1_2: u32 = provider1.random();
81
82        let provider2 = SimRandomProvider::new(42);
83        let value2_1: f64 = provider2.random();
84        let value2_2: u32 = provider2.random();
85
86        assert_eq!(value1_1, value2_1);
87        assert_eq!(value1_2, value2_2);
88    }
89
90    #[test]
91    fn test_random_range() {
92        let provider = SimRandomProvider::new(123);
93
94        // Test integer range
95        for _ in 0..100 {
96            let value = provider.random_range(10..20);
97            assert!(value >= 10);
98            assert!(value < 20);
99        }
100
101        // Test f64 range
102        for _ in 0..100 {
103            let value = provider.random_range(0.0..1.0);
104            assert!(value >= 0.0);
105            assert!(value < 1.0);
106        }
107    }
108
109    #[test]
110    fn test_random_ratio() {
111        let provider = SimRandomProvider::new(456);
112
113        for _ in 0..100 {
114            let ratio = provider.random_ratio();
115            assert!(ratio >= 0.0);
116            assert!(ratio < 1.0);
117        }
118    }
119
120    #[test]
121    fn test_random_bool() {
122        let provider = SimRandomProvider::new(789);
123
124        // Test probability 0.0 - should always be false
125        for _ in 0..10 {
126            assert!(!provider.random_bool(0.0));
127        }
128
129        // Test probability 1.0 - should always be true
130        for _ in 0..10 {
131            assert!(provider.random_bool(1.0));
132        }
133
134        // Test probability 0.5 - should have some variance
135        let results: Vec<bool> = (0..100).map(|_| provider.random_bool(0.5)).collect();
136        let true_count = results.iter().filter(|&&x| x).count();
137
138        // With 100 samples and 50% probability, we should get roughly 40-60 true values
139        // This is a statistical test so it could occasionally fail due to randomness
140        assert!(
141            true_count > 30 && true_count < 70,
142            "Got {} true values out of 100",
143            true_count
144        );
145    }
146
147    #[test]
148    #[should_panic(expected = "Probability must be between 0.0 and 1.0")]
149    fn test_random_bool_invalid_probability() {
150        let provider = SimRandomProvider::new(999);
151        provider.random_bool(1.5); // Should panic
152    }
153}