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    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}