use std::ptr::NonNull;
use pyo3::{
PyTypeInfo,
exceptions::PyRuntimeError,
ffi, intern,
prelude::*,
sync::PyOnceLock,
types::{DerefToPyAny, PyCapsule, PyType},
};
use crate::npyffi::bitgen_t;
mod sealed {
pub trait Sealed {}
}
use sealed::Sealed;
pub trait PyBitGeneratorMethods: Sealed {
fn spawn(&self, n_children: usize) -> PyResult<Vec<BitGenerator>>;
fn spawn_one(&self) -> PyResult<BitGenerator> {
let mut children = self.spawn(1)?;
children
.pop()
.ok_or_else(|| PyRuntimeError::new_err("spawn(1) returned no children"))
}
}
#[repr(transparent)]
pub struct PyBitGenerator(PyAny);
impl DerefToPyAny for PyBitGenerator {}
unsafe impl PyTypeInfo for PyBitGenerator {
fn type_object_raw<'py>(py: Python<'py>) -> *mut ffi::PyTypeObject {
static CLS: PyOnceLock<Py<PyType>> = PyOnceLock::new();
let cls = CLS
.import(py, "numpy.random", "BitGenerator")
.expect("Failed to get BitGenerator type object");
cls.as_type_ptr()
}
}
impl<'py> PyBitGeneratorMethods for Bound<'py, PyBitGenerator> {
fn spawn(&self, n_children: usize) -> PyResult<Vec<BitGenerator>> {
let py = self.py();
self.call_method1(intern!(py, "spawn"), (n_children,))?
.try_iter()?
.map(|child| unsafe { BitGenerator::from_py(child?.cast_into::<PyBitGenerator>()?) })
.collect()
}
}
impl Sealed for Bound<'_, PyBitGenerator> {}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BitGeneratorKind {
MT19937,
#[default]
PCG64,
PCG64DXSM,
Philox,
SFC64,
}
impl BitGeneratorKind {
pub fn iter() -> std::iter::Copied<std::slice::Iter<'static, BitGeneratorKind>> {
use BitGeneratorKind::*;
static KINDS: [BitGeneratorKind; 5] = [MT19937, PCG64, PCG64DXSM, Philox, SFC64];
KINDS.iter().copied()
}
}
impl From<BitGeneratorKind> for &'static str {
fn from(value: BitGeneratorKind) -> &'static str {
match value {
BitGeneratorKind::MT19937 => "MT19937",
BitGeneratorKind::PCG64 => "PCG64",
BitGeneratorKind::PCG64DXSM => "PCG64DXSM",
BitGeneratorKind::Philox => "Philox",
BitGeneratorKind::SFC64 => "SFC64",
}
}
}
pub struct BitGenerator {
raw: NonNull<bitgen_t>,
_bit_generator: Py<PyBitGenerator>,
}
unsafe impl Send for BitGenerator {}
impl BitGenerator {
pub fn new(py: Python<'_>, kind: BitGeneratorKind) -> PyResult<Self> {
let bitgen = py
.import("numpy.random")?
.call_method0::<&str>(kind.into())?
.cast_into::<PyBitGenerator>()?;
unsafe { Self::from_py(bitgen) }
}
unsafe fn from_py(bit_generator: Bound<'_, PyBitGenerator>) -> PyResult<Self> {
let py = bit_generator.py();
let capsule = bit_generator
.getattr(intern!(py, "capsule"))?
.cast_into::<PyCapsule>()?;
let raw = capsule
.pointer_checked(Some(c"BitGenerator"))
.map_err(|_| PyRuntimeError::new_err("Invalid BitGenerator capsule"))?;
Ok(BitGenerator {
raw: raw.cast(),
_bit_generator: bit_generator.unbind(),
})
}
pub fn into_shared(self) -> Py<PyBitGenerator> {
self._bit_generator
}
pub fn next_u64(&mut self) -> u64 {
unsafe {
let bitgen = self.raw.as_ptr();
debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
((*bitgen).next_uint64)((*bitgen).state)
}
}
pub fn next_u32(&mut self) -> u32 {
unsafe {
let bitgen = self.raw.as_ptr();
debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
((*bitgen).next_uint32)((*bitgen).state)
}
}
pub fn next_double(&mut self) -> f64 {
unsafe {
let bitgen = self.raw.as_ptr();
debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
((*bitgen).next_double)((*bitgen).state)
}
}
pub fn next_raw(&mut self) -> u64 {
unsafe {
let bitgen = self.raw.as_ptr();
debug_assert_ne!((*bitgen).state, std::ptr::null_mut());
((*bitgen).next_raw)((*bitgen).state)
}
}
}
#[cfg(feature = "rand_core")]
impl rand_core::RngCore for BitGenerator {
fn next_u32(&mut self) -> u32 {
BitGenerator::next_u32(self)
}
fn next_u64(&mut self) -> u64 {
BitGenerator::next_u64(self)
}
fn fill_bytes(&mut self, dst: &mut [u8]) {
rand_core::impls::fill_bytes_via_next(self, dst)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn get_shared<'py>(py: Python<'py>) -> PyResult<Bound<'py, PyBitGenerator>> {
let bitgen = py
.import("numpy.random")?
.call_method1("PCG64", (42,))?
.cast_into::<PyBitGenerator>()?;
Ok(bitgen)
}
fn get_owned<'py>(py: Python<'py>) -> PyResult<BitGenerator> {
let bitgen = get_shared(py)?;
unsafe { BitGenerator::from_py(bitgen) }
}
#[test]
fn from_kind() -> PyResult<()> {
Python::attach(|py| {
for kind in BitGeneratorKind::iter() {
let name: &str = kind.into();
let type_name = BitGenerator::new(py, kind)?
.into_shared()
.bind(py)
.get_type()
.name()?;
assert_eq!(type_name, name);
}
Ok(())
})
}
#[test]
fn base_api() -> PyResult<()> {
Python::attach(|py| {
let double = get_owned(py)?.next_double();
assert_eq!(double, 0.7739560485559633);
let u32_owned = get_owned(py)?.next_u32();
assert_eq!(u32_owned, 383329928);
let u64_owned = get_owned(py)?.next_u64();
assert_eq!(u64_owned, 14276969152011380360);
let raw_owned = get_owned(py)?.next_raw();
assert_eq!(raw_owned, u64_owned);
Ok(())
})
}
#[cfg(feature = "rand_core")]
#[test]
fn rand() -> PyResult<()> {
use rand::Rng as _;
Python::attach(|py| {
let seq_owned: Vec<bool> = get_owned(py)?
.sample_iter(rand::distr::Bernoulli::new(0.5).unwrap())
.take(10)
.collect();
let seq_expected = vec![
false, true, false, false, true, false, false, false, true, true,
];
assert_eq!(&seq_owned, &seq_expected);
Ok(())
})
}
#[test]
fn spawn_one() -> PyResult<()> {
let mut bitgen = Python::attach(|py| get_shared(py)?.spawn_one())?;
assert_eq!(bitgen.next_u32(), 2136330838);
Ok(())
}
#[test]
fn spawn_produces_independent_generators() -> PyResult<()> {
Python::attach(|py| {
let children = get_shared(py)?.spawn(2)?;
assert_eq!(children.len(), 2);
let values = py.detach(|| {
std::thread::scope(|s| {
children
.into_iter()
.map(|mut child| s.spawn(move || child.next_u64()))
.collect::<Vec<_>>()
.into_iter()
.map(|handle| handle.join().unwrap())
.collect::<Vec<_>>()
})
});
assert_eq!(values, vec![16910944855483863638, 8623682774590505111]);
Ok(())
})
}
}