Skip to main content

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    #[must_use]
33    pub fn new(seed: u64) -> Self {
34        // Set the thread-local RNG seed
35        set_sim_seed(seed);
36
37        Self {
38            _marker: std::marker::PhantomData,
39        }
40    }
41}
42
43impl RandomProvider for SimRandomProvider {
44    fn random<T>(&self) -> T
45    where
46        StandardUniform: Distribution<T>,
47    {
48        sim_random()
49    }
50
51    fn random_range<T>(&self, range: Range<T>) -> T
52    where
53        T: SampleUniform + PartialOrd,
54    {
55        sim_random_range(range)
56    }
57
58    fn random_ratio(&self) -> f64 {
59        sim_random::<f64>()
60    }
61
62    fn random_bool(&self, probability: f64) -> bool {
63        debug_assert!(
64            (0.0..=1.0).contains(&probability),
65            "Probability must be between 0.0 and 1.0, got {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.to_bits(), value2_1.to_bits());
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_count} true values out of 100"
143        );
144    }
145
146    #[test]
147    #[should_panic(expected = "Probability must be between 0.0 and 1.0")]
148    fn test_random_bool_invalid_probability() {
149        let provider = SimRandomProvider::new(999);
150        provider.random_bool(1.5); // Should panic
151    }
152}