1use std::any::TypeId;
2use std::cell::RefMut;
3use std::hash::Hasher;
4
5use log::trace;
6
7use crate::hashing::{hash_str, DeterministicHasher};
8use crate::rand::distr::uniform::{SampleRange, SampleUniform};
9use crate::rand::distr::weighted::{Weight, WeightedIndex};
10use crate::rand::distr::Distribution;
11use crate::rand::{Rng, RngExt, SeedableRng};
12use crate::random::{RngHolder, RngPlugin};
13use crate::{Context, ContextBase, RngId};
14
15fn get_rng<R: RngId + 'static>(context: &impl ContextBase) -> RefMut<R::RngType> {
19 let data_container = context.get_data(RngPlugin);
20
21 let rng_holders = data_container.rng_holders.try_borrow_mut().unwrap();
22 RefMut::map(rng_holders, |holders| {
23 holders
24 .entry(TypeId::of::<R>())
25 .or_insert_with(|| {
27 trace!(
28 "creating new RNG (seed={}) for type id {:?}",
29 data_container.base_seed,
30 TypeId::of::<R>()
31 );
32 let base_seed = data_container.base_seed;
33 let seed_offset = hash_str(R::get_name());
34 RngHolder {
35 rng: Box::new(R::RngType::seed_from_u64(
36 base_seed.wrapping_add(seed_offset),
37 )),
38 }
39 })
40 .rng
41 .downcast_mut::<R::RngType>()
42 .unwrap()
43 })
44}
45
46pub trait ContextRandomExt: ContextBase {
49 fn init_random(&mut self, base_seed: u64) {
52 trace!("initializing random module");
53 let data_container = self.get_data_mut(RngPlugin);
54 data_container.base_seed = base_seed;
55
56 let mut rng_map = data_container.rng_holders.try_borrow_mut().unwrap();
58 rng_map.clear();
59 }
60
61 #[must_use]
66 fn sample<R: RngId + 'static, T>(
67 &self,
68 _rng_type: R,
69 sampler: impl FnOnce(&mut R::RngType) -> T,
70 ) -> T {
71 let mut rng = get_rng::<R>(self);
72 sampler(&mut rng)
73 }
74
75 #[must_use]
84 fn debug_rng_state<R: RngId + 'static>(&self, _rng_id: R) -> u64
85 where
86 R::RngType: Clone + Rng,
87 {
88 let rng = get_rng::<R>(self);
89 let mut rng_snapshot = (*rng).clone();
90 let mut hasher = DeterministicHasher::default();
91
92 for _ in 0..4 {
93 hasher.write(&rng_snapshot.next_u64().to_le_bytes());
94 }
95
96 hasher.finish()
97 }
98
99 #[must_use]
104 fn sample_distr<R: RngId + 'static, T>(
105 &self,
106 _rng_type: R,
107 distribution: impl Distribution<T>,
108 ) -> T
109 where
110 R::RngType: Rng,
111 {
112 let mut rng = get_rng::<R>(self);
113 distribution.sample::<R::RngType>(&mut rng)
114 }
115
116 #[must_use]
120 fn sample_range<R: RngId + 'static, S, T>(&self, rng_id: R, range: S) -> T
121 where
122 R::RngType: Rng,
123 S: SampleRange<T>,
124 T: SampleUniform,
125 {
126 self.sample(rng_id, |rng| rng.random_range(range))
127 }
128
129 #[must_use]
155 fn sample_bool<R: RngId + 'static>(&self, rng_id: R, p: impl Into<f64>) -> bool
156 where
157 R::RngType: Rng,
158 {
159 let p = p.into();
160 self.sample(rng_id, |rng| rng.random_bool(p))
161 }
162
163 #[must_use]
168 fn sample_weighted<R: RngId + 'static, T>(&self, _rng_id: R, weights: &[T]) -> usize
169 where
170 R::RngType: Rng,
171 T: Clone
172 + Default
173 + SampleUniform
174 + for<'a> std::ops::AddAssign<&'a T>
175 + PartialOrd
176 + Weight,
177 {
178 let index = WeightedIndex::new(weights).unwrap();
179 let mut rng = get_rng::<R>(self);
180 index.sample(&mut *rng)
181 }
182}
183
184impl ContextRandomExt for Context {}
185
186#[cfg(test)]
187mod test {
188 use crate::context::Context;
189 use crate::rand::distr::weighted::WeightedIndex;
190 use crate::rand::distr::Distribution;
191 use crate::rand::Rng;
192 use crate::random::context_ext::ContextRandomExt;
193 use crate::{define_data_plugin, define_rng};
194
195 define_rng!(FooRng);
196 define_rng!(BarRng);
197
198 struct Probability(f64);
199
200 impl From<Probability> for f64 {
201 fn from(probability: Probability) -> Self {
202 probability.0
203 }
204 }
205
206 #[test]
207 fn get_rng_basic() {
208 let mut context = Context::new();
209 context.init_random(42);
210
211 assert_ne!(
212 context.sample(FooRng, Rng::next_u64),
213 context.sample(FooRng, Rng::next_u64)
214 );
215 }
216
217 #[test]
218 fn multiple_rng_types() {
219 let mut context = Context::new();
220 context.init_random(42);
221
222 assert_ne!(
223 context.sample(FooRng, Rng::next_u64),
224 context.sample(BarRng, Rng::next_u64)
225 );
226 }
227
228 #[test]
229 fn reset_seed() {
230 let mut context = Context::new();
231 context.init_random(42);
232
233 let run_0 = context.sample(FooRng, Rng::next_u64);
234 let run_1 = context.sample(FooRng, Rng::next_u64);
235
236 context.init_random(42);
238 assert_eq!(run_0, context.sample(FooRng, Rng::next_u64));
239 assert_eq!(run_1, context.sample(FooRng, Rng::next_u64));
240
241 context.init_random(88);
243 assert_ne!(run_0, context.sample(FooRng, Rng::next_u64));
244 assert_ne!(run_1, context.sample(FooRng, Rng::next_u64));
245 }
246
247 #[test]
248 fn debug_rng_state_matches_for_same_seed_and_progress() {
249 let mut context_0 = Context::new();
250 context_0.init_random(42);
251 let mut context_1 = Context::new();
252 context_1.init_random(42);
253
254 for _ in 0..3 {
255 let _ = context_0.sample(FooRng, Rng::next_u64);
256 let _ = context_1.sample(FooRng, Rng::next_u64);
257 }
258
259 assert_eq!(
260 context_0.debug_rng_state(FooRng),
261 context_1.debug_rng_state(FooRng)
262 );
263 }
264
265 #[test]
266 fn debug_rng_state_changes_with_rng_progress() {
267 let mut context = Context::new();
268 context.init_random(42);
269
270 let initial = context.debug_rng_state(FooRng);
271 let _ = context.sample(FooRng, Rng::next_u64);
272
273 assert_ne!(initial, context.debug_rng_state(FooRng));
274 }
275
276 #[test]
277 fn debug_rng_state_is_stable_without_sampling() {
278 let mut context = Context::new();
279 context.init_random(42);
280
281 assert_eq!(
282 context.debug_rng_state(FooRng),
283 context.debug_rng_state(FooRng)
284 );
285 }
286
287 #[test]
288 fn debug_rng_state_does_not_affect_next_sample() {
289 let mut with_debug = Context::new();
290 with_debug.init_random(42);
291 let mut without_debug = Context::new();
292 without_debug.init_random(42);
293
294 let _ = with_debug.debug_rng_state(FooRng);
295
296 assert_eq!(
297 with_debug.sample(FooRng, Rng::next_u64),
298 without_debug.sample(FooRng, Rng::next_u64)
299 );
300 }
301
302 #[test]
303 fn debug_rng_state_resets_with_seed() {
304 let mut context = Context::new();
305 context.init_random(42);
306
307 let initial = context.debug_rng_state(FooRng);
308 let _ = context.sample(FooRng, Rng::next_u64);
309 assert_ne!(initial, context.debug_rng_state(FooRng));
310
311 context.init_random(42);
312 assert_eq!(initial, context.debug_rng_state(FooRng));
313 }
314
315 #[test]
316 fn debug_rng_state_is_independent_by_rng_id() {
317 let mut context_0 = Context::new();
318 context_0.init_random(42);
319 let mut context_1 = Context::new();
320 context_1.init_random(42);
321
322 let foo_initial = context_0.debug_rng_state(FooRng);
323 let bar_initial = context_0.debug_rng_state(BarRng);
324 assert_ne!(foo_initial, bar_initial);
325
326 let _ = context_0.sample(FooRng, Rng::next_u64);
327 let _ = context_1.sample(BarRng, Rng::next_u64);
328
329 assert_ne!(context_0.debug_rng_state(FooRng), foo_initial);
330 assert_eq!(context_0.debug_rng_state(BarRng), bar_initial);
331 assert_eq!(context_1.debug_rng_state(FooRng), foo_initial);
332 assert_ne!(context_1.debug_rng_state(BarRng), bar_initial);
333 }
334
335 define_data_plugin!(
336 SamplerData,
337 WeightedIndex<f64>,
338 WeightedIndex::new(vec![1.0]).unwrap()
339 );
340
341 #[test]
342 fn sampler_function_closure_capture() {
343 let mut context = Context::new();
344 context.init_random(42);
345
346 *context.get_data_mut(SamplerData) = WeightedIndex::new(vec![1.0, 2.0]).unwrap();
349
350 let parameters = context.get_data(SamplerData);
351 let n_samples = 3000;
352 let mut zero_counter = 0;
353 for _ in 0..n_samples {
354 let sample = context.sample(FooRng, |rng| parameters.sample(rng));
355 if sample == 0 {
356 zero_counter += 1;
357 }
358 }
359 assert!((zero_counter - 1000_i32).abs() < 100);
361 }
362
363 #[test]
364 fn sample_distribution() {
365 let mut context = Context::new();
366 context.init_random(42);
367
368 *context.get_data_mut(SamplerData) = WeightedIndex::new(vec![1.0, 2.0]).unwrap();
371
372 let parameters = context.get_data(SamplerData);
373 let n_samples = 3000;
374 let mut zero_counter = 0;
375 for _ in 0..n_samples {
376 let sample = context.sample_distr(FooRng, parameters);
377 if sample == 0 {
378 zero_counter += 1;
379 }
380 }
381 assert!((zero_counter - 1000_i32).abs() < 100);
383 }
384
385 #[test]
386 fn sample_range() {
387 let mut context = Context::new();
388 context.init_random(42);
389 let result = context.sample_range(FooRng, 0..10);
390 assert!((0..10).contains(&result));
391 }
392
393 #[test]
394 fn sample_bool() {
395 let mut context = Context::new();
396 context.init_random(42);
397 let _r: bool = context.sample_bool(FooRng, 0.5);
398 }
399
400 #[test]
401 fn sample_bool_accepts_into_f64_without_extra_turbofish() {
402 let mut context = Context::new();
403 context.init_random(42);
404
405 assert!(!context.sample_bool::<FooRng>(FooRng, Probability(0.0)));
406 assert!(context.sample_bool::<FooRng>(FooRng, Probability(1.0)));
407 }
408
409 #[test]
410 #[should_panic(expected = "outside range [0.0, 1.0]")]
411 fn sample_bool_rejects_wrapped_invalid_probability() {
412 let mut context = Context::new();
413 context.init_random(42);
414
415 let _ = context.sample_bool(FooRng, Probability(1.1));
416 }
417
418 #[test]
419 fn sample_weighted() {
420 let mut context = Context::new();
421 context.init_random(42);
422 let r: usize = context.sample_weighted(FooRng, &[0.1, 0.3, 0.4]);
423 assert!(r < 3);
424 }
425}