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 PyTypeInfo,
101 exceptions::PyRuntimeError,
102 ffi, intern,
103 prelude::*,
104 sync::PyOnceLock,
105 types::{DerefToPyAny, PyCapsule, PyType},
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 fn type_object_raw<'py>(py: Python<'py>) -> *mut ffi::PyTypeObject {
145 static CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
146 let cls = CLS
147 .import(py, "numpy.random", "BitGenerator")
148 .expect("Failed to get BitGenerator type object");
149 cls.as_type_ptr()
150 }
151}
152
153impl<'py> PyBitGeneratorMethods for Bound<'py, PyBitGenerator> {
154 fn spawn(&self, n_children: usize) -> PyResult<Vec<BitGenerator>> {
155 let py = self.py();
156 self.call_method1(intern!(py, "spawn"), (n_children,))?
157 .try_iter()?
158 // SAFETY: each child is freshly spawned and only handed to us, so it’s exclusively ours.
159 .map(|child| unsafe { BitGenerator::from_py(child?.cast_into::<PyBitGenerator>()?) })
160 .collect()
161 }
162}
163
164impl Sealed for Bound<'_, PyBitGenerator> {}
165
166/// Which of numpy’s bit generator algorithms [`BitGenerator::new`] should create.
167#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
168pub enum BitGeneratorKind {
169 /// Mersenne Twister (MT19937)
170 MT19937,
171 /// Permuted congruential generator (64-bit, PCG-64)
172 #[default]
173 PCG64,
174 /// Permuted congruential generator (64-bit, PCG-64 DXSM
175 PCG64DXSM,
176 /// Philox counter-based RNG
177 Philox,
178 /// SFC64 Small Fast Chaotic PRNG
179 SFC64,
180}
181
182impl BitGeneratorKind {
183 /// Returns an iterator over the values of [`BitGeneratorKind`].
184 pub fn iter() -> std::iter::Copied<std::slice::Iter<'static, BitGeneratorKind>> {
185 use BitGeneratorKind::*;
186 static KINDS: [BitGeneratorKind; 5] = [MT19937, PCG64, PCG64DXSM, Philox, SFC64];
187 KINDS.iter().copied()
188 }
189}
190
191impl From<BitGeneratorKind> for &'static str {
192 fn from(value: BitGeneratorKind) -> &'static str {
193 match value {
194 BitGeneratorKind::MT19937 => "MT19937",
195 BitGeneratorKind::PCG64 => "PCG64",
196 BitGeneratorKind::PCG64DXSM => "PCG64DXSM",
197 BitGeneratorKind::Philox => "Philox",
198 BitGeneratorKind::SFC64 => "SFC64",
199 }
200 }
201}
202
203/// A numpy `BitGenerator` usable without being attached to the interpreter runtime,
204/// with exclusive access to its state.
205///
206/// [`spawn`][PyBitGeneratorMethods::spawn] hands out independent, owned ones.
207pub struct BitGenerator {
208 raw: NonNull<bitgen_t>,
209 /// Keeps `raw` alive: the capsule’s pointer lives in memory owned by the `BitGenerator`, which
210 /// has no back-reference of its own, so only keeping it alive keeps that memory valid.
211 _bit_generator: Py<PyBitGenerator>,
212}
213
214// SAFETY: `raw` is only ever accessed through `&mut self`, so it can’t be used in parallel, and we
215// keep its `bitgen_t` alive via `_bit_generator`. Every `BitGenerator` owns its `bitgen_t`
216// exclusively (it is freshly created or a fresh `spawn` child), so nothing else can touch
217// its state.
218unsafe impl Send for BitGenerator {}
219
220impl BitGenerator {
221 /// Creates a fresh [`BitGenerator`] backed by numpy’s implementation.
222 ///
223 /// ```
224 /// use pyo3::prelude::*;
225 /// use numpy::random::{BitGenerator, BitGeneratorKind};
226 ///
227 /// let mut bitgen = Python::attach(|py| BitGenerator::new(py, Default::default()))?;
228 /// println!("{}", bitgen.next_u32());
229 /// # Ok::<(), PyErr>(())
230 /// ```
231 pub fn new(py: Python<'_>, kind: BitGeneratorKind) -> PyResult<Self> {
232 let bitgen = py
233 .import("numpy.random")?
234 .call_method0::<&str>(kind.into())?
235 .cast_into::<PyBitGenerator>()?;
236 // SAFETY: `bitgen` is freshly created and not handed out elsewhere.
237 unsafe { Self::from_py(bitgen) }
238 }
239
240 /// Extracts the raw `bitgen_t` pointer from `bit_generator`’s capsule.
241 ///
242 /// # Safety
243 ///
244 /// The caller must ensure the result has exclusive access to the `bitgen_t` for its whole
245 /// lifetime, i.e. `bit_generator` is freshly created and not handed out elsewhere.
246 unsafe fn from_py(bit_generator: Bound<'_, PyBitGenerator>) -> PyResult<Self> {
247 let py = bit_generator.py();
248 let capsule = bit_generator
249 .getattr(intern!(py, "capsule"))?
250 .cast_into::<PyCapsule>()?;
251 let raw = capsule
252 .pointer_checked(Some(c"BitGenerator"))
253 .map_err(|_| PyRuntimeError::new_err("Invalid BitGenerator capsule"))?;
254 Ok(BitGenerator {
255 raw: raw.cast(),
256 _bit_generator: bit_generator.unbind(),
257 })
258 }
259
260 /// Returns the underlying [`PyBitGenerator`].
261 pub fn into_shared(self) -> Py<PyBitGenerator> {
262 self._bit_generator
263 }
264
265 /// Returns the next random unsigned 64 bit integer.
266 pub fn next_u64(&mut self) -> u64 {
267 unsafe {
268 let bitgen = self.raw.as_ptr();
269 debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
270 ((*bitgen).next_uint64)((*bitgen).state)
271 }
272 }
273 /// Returns the next random unsigned 32 bit integer.
274 pub fn next_u32(&mut self) -> u32 {
275 unsafe {
276 let bitgen = self.raw.as_ptr();
277 debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
278 ((*bitgen).next_uint32)((*bitgen).state)
279 }
280 }
281 /// Returns the next random double.
282 pub fn next_double(&mut self) -> f64 {
283 unsafe {
284 let bitgen = self.raw.as_ptr();
285 debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
286 ((*bitgen).next_double)((*bitgen).state)
287 }
288 }
289 /// Returns the next raw value (can be used for testing).
290 pub fn next_raw(&mut self) -> u64 {
291 unsafe {
292 let bitgen = self.raw.as_ptr();
293 debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
294 ((*bitgen).next_raw)((*bitgen).state)
295 }
296 }
297}
298
299#[cfg(feature = "rand_core")]
300impl rand_core::RngCore for BitGenerator {
301 fn next_u32(&mut self) -> u32 {
302 BitGenerator::next_u32(self)
303 }
304 fn next_u64(&mut self) -> u64 {
305 BitGenerator::next_u64(self)
306 }
307 fn fill_bytes(&mut self, dst: &mut [u8]) {
308 rand_core::impls::fill_bytes_via_next(self, dst)
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 fn get_shared<'py>(py: Python<'py>) -> PyResult<Bound<'py, PyBitGenerator>> {
317 let bitgen = py
318 .import("numpy.random")?
319 .call_method1("PCG64", (42,))?
320 .cast_into::<PyBitGenerator>()?;
321 Ok(bitgen)
322 }
323
324 fn get_owned<'py>(py: Python<'py>) -> PyResult<BitGenerator> {
325 let bitgen = get_shared(py)?;
326 // SAFETY: `bitgen` is freshly created and not handed out elsewhere.
327 unsafe { BitGenerator::from_py(bitgen) }
328 }
329
330 #[test]
331 fn from_kind() -> PyResult<()> {
332 Python::attach(|py| {
333 for kind in BitGeneratorKind::iter() {
334 let name: &str = kind.into();
335 let type_name = BitGenerator::new(py, kind)?
336 .into_shared()
337 .bind(py)
338 .get_type()
339 .name()?;
340 assert_eq!(type_name, name);
341 }
342 Ok(())
343 })
344 }
345
346 /// Simple single-threaded use of an owned generator.
347 #[test]
348 fn base_api() -> PyResult<()> {
349 Python::attach(|py| {
350 let double = get_owned(py)?.next_double();
351 assert_eq!(double, 0.7739560485559633);
352
353 let u32_owned = get_owned(py)?.next_u32();
354 assert_eq!(u32_owned, 383329928);
355
356 let u64_owned = get_owned(py)?.next_u64();
357 assert_eq!(u64_owned, 14276969152011380360);
358
359 let raw_owned = get_owned(py)?.next_raw();
360 assert_eq!(raw_owned, u64_owned);
361
362 Ok(())
363 })
364 }
365
366 /// Test that the `rand::Rng` APIs work
367 #[cfg(feature = "rand_core")]
368 #[test]
369 fn rand() -> PyResult<()> {
370 use rand::Rng as _;
371
372 Python::attach(|py| {
373 let seq_owned: Vec<bool> = get_owned(py)?
374 .sample_iter(rand::distr::Bernoulli::new(0.5).unwrap())
375 .take(10)
376 .collect();
377 let seq_expected = vec![
378 false, true, false, false, true, false, false, false, true, true,
379 ];
380 assert_eq!(&seq_owned, &seq_expected);
381 Ok(())
382 })
383 }
384
385 #[test]
386 fn spawn_one() -> PyResult<()> {
387 let mut bitgen = Python::attach(|py| get_shared(py)?.spawn_one())?;
388 assert_eq!(bitgen.next_u32(), 2136330838);
389 Ok(())
390 }
391
392 /// Spawned children are independent and owned,
393 /// so they can be used (and dropped) from their own threads without locking.
394 #[test]
395 fn spawn_produces_independent_generators() -> PyResult<()> {
396 Python::attach(|py| {
397 let children = get_shared(py)?.spawn(2)?;
398 assert_eq!(children.len(), 2);
399
400 let values = py.detach(|| {
401 std::thread::scope(|s| {
402 children
403 .into_iter()
404 .map(|mut child| s.spawn(move || child.next_u64()))
405 .collect::<Vec<_>>()
406 .into_iter()
407 .map(|handle| handle.join().unwrap())
408 .collect::<Vec<_>>()
409 })
410 });
411
412 assert_eq!(values, vec![16910944855483863638, 8623682774590505111]);
413 Ok(())
414 })
415 }
416}