Skip to main content

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}