numpy/random.rs
1//! Safe interface for NumPy's random [`BitGenerator`][bg].
2//!
3//! Using the patterns described in [“Extending `numpy.random`”][ext],
4//! you can generate random numbers without being attached to the interpreter runtime by:
5//! - [spawning][`PyBitGeneratorMethods::spawn`] fresh [`BitGenerator`]s
6//! from a [`PyBitGenerator`] you received from Python
7//! - creating a fresh [`BitGenerator`] [from numpy][`BitGenerator::new`]:
8//!
9//! ```
10//! use pyo3::prelude::*;
11//! use numpy::random::BitGenerator;
12//!
13//! let mut bitgen = Python::attach(|py| {
14//! BitGenerator::new(py, Default::default())
15//! })?;
16//! let random_number = bitgen.next_u64();
17//! # Ok::<(), PyErr>(())
18//! ```
19//!
20//! If you write a pyo3 extension, you would extract
21//! a [`numpy.random.BitGenerator`] into a <code>[Bound]<'_, [PyBitGenerator]></code>:
22//!
23//! [`numpy.random.BitGenerator`]: https://numpy.org/doc/stable/reference/random/bit_generators/generated/numpy.random.BitGenerator.html
24//!
25//! ```
26//! # use pyo3::prelude::*;
27//! use numpy::random::{BitGenerator, PyBitGenerator, PyBitGeneratorMethods as _};
28//! # fn default_bit_gen<'py>(py: Python<'py>) -> PyResult<Bound<'py, PyBitGenerator>> {
29//! # Ok(BitGenerator::new(py, Default::default())?.into_shared().into_bound(py))
30//! # }
31//!
32//! #[pyfunction]
33//! fn make_random_number(bitgen: Bound<PyBitGenerator>) -> PyResult<u64> {
34//! // spawn an owned child, then use it without being attached to the interpreter runtime
35//! Ok(bitgen.spawn_one()?.next_u64())
36//! }
37//!
38//! Python::attach(|py| -> PyResult<_> {
39//! let bitgen: Bound<PyBitGenerator> = default_bit_gen(py)?;
40//! let random_number = make_random_number(bitgen)?;
41//! println!("{random_number}");
42//! Ok(())
43//! })?;
44//! # Ok::<(), PyErr>(())
45//! ```
46//!
47//! With the `rand` crate installed, you can also use its `Rng` APIs on any generator,
48//! since [`BitGenerator`] implements [`rand_core::RngCore`].
49//!
50//! ```
51//! use pyo3::prelude::*;
52//! use rand::Rng as _;
53//! use numpy::random::{BitGenerator, BitGeneratorKind::SFC64};
54//!
55//! let mut bitgen = Python::attach(|py| BitGenerator::new(py, SFC64))?;
56//! if bitgen.random_ratio(1, 1_000_000) {
57//! println!("a sure thing");
58//! };
59//! # Ok::<(), PyErr>(())
60//! ```
61//!
62//! Using `spawn`, you can create multiple [`BitGenerator`]s to generate random numbers truly in parallel,
63//! all without being attached to the interpreter runtime:
64//!
65//! ```
66//! # use pyo3::prelude::*;
67//! # use rand::Rng as _;
68//! use numpy::{PyArray2, PyArrayMethods as _};
69//! # use numpy::random::{BitGenerator, PyBitGenerator, PyBitGeneratorMethods as _};
70//! # fn default_bit_gen<'py>(py: Python<'py>) -> PyResult<Bound<'py, PyBitGenerator>> {
71//! # Ok(BitGenerator::new(py, Default::default())?.into_shared().into_bound(py))
72//! # }
73//!
74//! Python::attach(|py| -> PyResult<_> {
75//! let bitgen: Bound<PyBitGenerator> = default_bit_gen(py)?;
76//! let children = bitgen.spawn(4)?;
77//! let mut arr = PyArray2::<u32>::zeros(py, (4, 300), false).readwrite();
78//! let mut ndarr = arr.as_array_mut(); // ndarray for more convenience
79//! py.detach(|| std::thread::scope(|s| {
80//! for (mut chunk, mut child) in ndarr.rows_mut().into_iter().zip(children) {
81//! s.spawn(move || {
82//! for x in chunk.iter_mut() {
83//! *x = child.random_range(10..200);
84//! }
85//! });
86//! }
87//! }));
88//! println!("Now filled: {arr:?}");
89//! Ok(())
90//! })?;
91//! # Ok::<(), PyErr>(())
92//! ```
93//!
94//! [bg]: https://numpy.org/doc/stable//reference/random/bit_generators/generated/numpy.random.BitGenerator.html
95//! [ext]: https://numpy.org/doc/stable/reference/random/extending.html
96
97use std::ptr::NonNull;
98
99use pyo3::{
100 exceptions::PyRuntimeError,
101 ffi, intern,
102 prelude::*,
103 sync::PyOnceLock,
104 types::{DerefToPyAny, PyCapsule, PyType},
105 PyTypeInfo,
106};
107
108use crate::npyffi::bitgen_t;
109
110mod sealed {
111 pub trait Sealed {}
112}
113
114use sealed::Sealed;
115
116/// Methods for [`PyBitGenerator`].
117pub trait PyBitGeneratorMethods: Sealed {
118 /// Spawn `n_children` independent child [`BitGenerator`]s.
119 ///
120 /// This is the way to obtain generators for multiple threads: each child has its own,
121 /// independent state, so no synchronization is needed between them.
122 fn spawn(&self, n_children: usize) -> PyResult<Vec<BitGenerator>>;
123
124 /// Spawn a single owned child [`BitGenerator`].
125 fn spawn_one(&self) -> PyResult<BitGenerator> {
126 let mut children = self.spawn(1)?;
127 children
128 .pop()
129 .ok_or_else(|| PyRuntimeError::new_err("spawn(1) returned no children"))
130 }
131}
132
133/// Wrapper for [`np.random.BitGenerator`][bg].
134///
135/// See also [`PyBitGeneratorMethods`].
136///
137/// [bg]: https://numpy.org/doc/stable//reference/random/bit_generators/generated/numpy.random.BitGenerator.html
138#[repr(transparent)]
139pub struct PyBitGenerator(PyAny);
140
141impl DerefToPyAny for PyBitGenerator {}
142
143unsafe impl PyTypeInfo for PyBitGenerator {
144 const NAME: &'static str = "PyBitGenerator";
145 const MODULE: Option<&'static str> = Some("numpy.random");
146
147 fn type_object_raw<'py>(py: Python<'py>) -> *mut ffi::PyTypeObject {
148 static CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
149 let cls = CLS
150 .import(py, "numpy.random", "BitGenerator")
151 .expect("Failed to get BitGenerator type object");
152 cls.as_type_ptr()
153 }
154}
155
156impl<'py> PyBitGeneratorMethods for Bound<'py, PyBitGenerator> {
157 fn spawn(&self, n_children: usize) -> PyResult<Vec<BitGenerator>> {
158 let py = self.py();
159 self.call_method1(intern!(py, "spawn"), (n_children,))?
160 .try_iter()?
161 // SAFETY: each child is freshly spawned and only handed to us, so it’s exclusively ours.
162 .map(|child| unsafe { BitGenerator::from_py(child?.cast_into::<PyBitGenerator>()?) })
163 .collect()
164 }
165}
166
167impl Sealed for Bound<'_, PyBitGenerator> {}
168
169/// Which of numpy’s bit generator algorithms [`BitGenerator::new`] should create.
170#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub enum BitGeneratorKind {
172 /// Mersenne Twister (MT19937)
173 MT19937,
174 /// Permuted congruential generator (64-bit, PCG-64)
175 #[default]
176 PCG64,
177 /// Permuted congruential generator (64-bit, PCG-64 DXSM
178 PCG64DXSM,
179 /// Philox counter-based RNG
180 Philox,
181 /// SFC64 Small Fast Chaotic PRNG
182 SFC64,
183}
184
185impl BitGeneratorKind {
186 /// Returns an iterator over the values of [`BitGeneratorKind`].
187 pub fn iter() -> std::iter::Copied<std::slice::Iter<'static, BitGeneratorKind>> {
188 use BitGeneratorKind::*;
189 static KINDS: [BitGeneratorKind; 5] = [MT19937, PCG64, PCG64DXSM, Philox, SFC64];
190 KINDS.iter().copied()
191 }
192}
193
194impl From<BitGeneratorKind> for &'static str {
195 fn from(value: BitGeneratorKind) -> &'static str {
196 match value {
197 BitGeneratorKind::MT19937 => "MT19937",
198 BitGeneratorKind::PCG64 => "PCG64",
199 BitGeneratorKind::PCG64DXSM => "PCG64DXSM",
200 BitGeneratorKind::Philox => "Philox",
201 BitGeneratorKind::SFC64 => "SFC64",
202 }
203 }
204}
205
206/// A numpy `BitGenerator` usable without being attached to the interpreter runtime,
207/// with exclusive access to its state.
208///
209/// [`spawn`][PyBitGeneratorMethods::spawn] hands out independent, owned ones.
210pub struct BitGenerator {
211 raw: NonNull<bitgen_t>,
212 /// Keeps `raw` alive: the capsule’s pointer lives in memory owned by the `BitGenerator`, which
213 /// has no back-reference of its own, so only keeping it alive keeps that memory valid.
214 _bit_generator: Py<PyBitGenerator>,
215}
216
217// SAFETY: `raw` is only ever accessed through `&mut self`, so it can’t be used in parallel, and we
218// keep its `bitgen_t` alive via `_bit_generator`. Every `BitGenerator` owns its `bitgen_t`
219// exclusively (it is freshly created or a fresh `spawn` child), so nothing else can touch
220// its state.
221unsafe impl Send for BitGenerator {}
222
223impl BitGenerator {
224 /// Creates a fresh [`BitGenerator`] backed by numpy’s implementation.
225 ///
226 /// ```
227 /// use pyo3::prelude::*;
228 /// use numpy::random::{BitGenerator, BitGeneratorKind};
229 ///
230 /// let mut bitgen = Python::attach(|py| BitGenerator::new(py, Default::default()))?;
231 /// println!("{}", bitgen.next_u32());
232 /// # Ok::<(), PyErr>(())
233 /// ```
234 pub fn new(py: Python<'_>, kind: BitGeneratorKind) -> PyResult<Self> {
235 let bitgen = py
236 .import("numpy.random")?
237 .call_method0::<&str>(kind.into())?
238 .cast_into::<PyBitGenerator>()?;
239 // SAFETY: `bitgen` is freshly created and not handed out elsewhere.
240 unsafe { Self::from_py(bitgen) }
241 }
242
243 /// Extracts the raw `bitgen_t` pointer from `bit_generator`’s capsule.
244 ///
245 /// # Safety
246 ///
247 /// The caller must ensure the result has exclusive access to the `bitgen_t` for its whole
248 /// lifetime, i.e. `bit_generator` is freshly created and not handed out elsewhere.
249 unsafe fn from_py(bit_generator: Bound<'_, PyBitGenerator>) -> PyResult<Self> {
250 let py = bit_generator.py();
251 let capsule = bit_generator
252 .getattr(intern!(py, "capsule"))?
253 .cast_into::<PyCapsule>()?;
254 let raw = capsule
255 .pointer_checked(Some(c"BitGenerator"))
256 .map_err(|_| PyRuntimeError::new_err("Invalid BitGenerator capsule"))?;
257 Ok(BitGenerator {
258 raw: raw.cast(),
259 _bit_generator: bit_generator.unbind(),
260 })
261 }
262
263 /// Returns the underlying [`PyBitGenerator`].
264 pub fn into_shared(self) -> Py<PyBitGenerator> {
265 self._bit_generator
266 }
267
268 /// Returns the next random unsigned 64 bit integer.
269 pub fn next_u64(&mut self) -> u64 {
270 unsafe {
271 let bitgen = self.raw.as_ptr();
272 debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
273 ((*bitgen).next_uint64)((*bitgen).state)
274 }
275 }
276 /// Returns the next random unsigned 32 bit integer.
277 pub fn next_u32(&mut self) -> u32 {
278 unsafe {
279 let bitgen = self.raw.as_ptr();
280 debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
281 ((*bitgen).next_uint32)((*bitgen).state)
282 }
283 }
284 /// Returns the next random double.
285 pub fn next_double(&mut self) -> f64 {
286 unsafe {
287 let bitgen = self.raw.as_ptr();
288 debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
289 ((*bitgen).next_double)((*bitgen).state)
290 }
291 }
292 /// Returns the next raw value (can be used for testing).
293 pub fn next_raw(&mut self) -> u64 {
294 unsafe {
295 let bitgen = self.raw.as_ptr();
296 debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
297 ((*bitgen).next_raw)((*bitgen).state)
298 }
299 }
300}
301
302#[cfg(feature = "rand_core")]
303impl rand_core::RngCore for BitGenerator {
304 fn next_u32(&mut self) -> u32 {
305 BitGenerator::next_u32(self)
306 }
307 fn next_u64(&mut self) -> u64 {
308 BitGenerator::next_u64(self)
309 }
310 fn fill_bytes(&mut self, dst: &mut [u8]) {
311 rand_core::impls::fill_bytes_via_next(self, dst)
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 fn get_shared<'py>(py: Python<'py>) -> PyResult<Bound<'py, PyBitGenerator>> {
320 let bitgen = py
321 .import("numpy.random")?
322 .call_method1("PCG64", (42,))?
323 .cast_into::<PyBitGenerator>()?;
324 Ok(bitgen)
325 }
326
327 fn get_owned<'py>(py: Python<'py>) -> PyResult<BitGenerator> {
328 let bitgen = get_shared(py)?;
329 // SAFETY: `bitgen` is freshly created and not handed out elsewhere.
330 unsafe { BitGenerator::from_py(bitgen) }
331 }
332
333 #[test]
334 fn from_kind() -> PyResult<()> {
335 Python::attach(|py| {
336 for kind in BitGeneratorKind::iter() {
337 let name: &str = kind.into();
338 let type_name = BitGenerator::new(py, kind)?
339 .into_shared()
340 .bind(py)
341 .get_type()
342 .name()?;
343 assert_eq!(type_name, name);
344 }
345 Ok(())
346 })
347 }
348
349 /// Simple single-threaded use of an owned generator.
350 #[test]
351 fn base_api() -> PyResult<()> {
352 Python::attach(|py| {
353 let double = get_owned(py)?.next_double();
354 assert_eq!(double, 0.7739560485559633);
355
356 let u32_owned = get_owned(py)?.next_u32();
357 assert_eq!(u32_owned, 383329928);
358
359 let u64_owned = get_owned(py)?.next_u64();
360 assert_eq!(u64_owned, 14276969152011380360);
361
362 let raw_owned = get_owned(py)?.next_raw();
363 assert_eq!(raw_owned, u64_owned);
364
365 Ok(())
366 })
367 }
368
369 /// Test that the `rand::Rng` APIs work
370 #[cfg(feature = "rand_core")]
371 #[test]
372 fn rand() -> PyResult<()> {
373 use rand::Rng as _;
374
375 Python::attach(|py| {
376 let seq_owned: Vec<bool> = get_owned(py)?
377 .sample_iter(rand::distr::Bernoulli::new(0.5).unwrap())
378 .take(10)
379 .collect();
380 let seq_expected = vec![
381 false, true, false, false, true, false, false, false, true, true,
382 ];
383 assert_eq!(&seq_owned, &seq_expected);
384 Ok(())
385 })
386 }
387
388 #[test]
389 fn spawn_one() -> PyResult<()> {
390 let mut bitgen = Python::attach(|py| get_shared(py)?.spawn_one())?;
391 assert_eq!(bitgen.next_u32(), 2136330838);
392 Ok(())
393 }
394
395 /// Spawned children are independent and owned,
396 /// so they can be used (and dropped) from their own threads without locking.
397 #[test]
398 fn spawn_produces_independent_generators() -> PyResult<()> {
399 Python::attach(|py| {
400 let children = get_shared(py)?.spawn(2)?;
401 assert_eq!(children.len(), 2);
402
403 let values = py.detach(|| {
404 std::thread::scope(|s| {
405 children
406 .into_iter()
407 .map(|mut child| s.spawn(move || child.next_u64()))
408 .collect::<Vec<_>>()
409 .into_iter()
410 .map(|handle| handle.join().unwrap())
411 .collect::<Vec<_>>()
412 })
413 });
414
415 assert_eq!(values, vec![16910944855483863638, 8623682774590505111]);
416 Ok(())
417 })
418 }
419}