use crate::array::Array;
use crate::python::array::PyArray;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use scirs2_core::random::rngs::{StdRng, ThreadRng};
use scirs2_core::random::{Normal, Random, Uniform};
fn generator_from_seed(seed: Option<u64>) -> Random<StdRng> {
let s = seed.unwrap_or_else(|| scirs2_core::random::thread_rng().random::<u64>());
Random::<ThreadRng>::seed(s)
}
#[pyclass(name = "Generator")]
pub struct PyGenerator {
rng: Random<StdRng>,
}
impl PyGenerator {
fn sample_scalar_or_array(
&mut self,
py: Python<'_>,
size: Option<Vec<usize>>,
mut f: impl FnMut(&mut Random<StdRng>) -> f64,
) -> PyResult<Py<PyAny>> {
match size {
None => Ok(f(&mut self.rng).into_pyobject(py)?.into_any().unbind()),
Some(shape) => {
let total: usize = shape.iter().product();
let data: Vec<f64> = (0..total).map(|_| f(&mut self.rng)).collect();
let arr = Array::from_vec_shape(data, &shape)?;
let py_arr = PyArray { inner: arr };
py_arr.into_pyobject(py).map(|b| b.into_any().unbind())
}
}
}
fn permuted_copy(&mut self, arr: &Array<f64>) -> PyResult<Array<f64>> {
let shape = arr.shape();
if shape.is_empty() {
return Err(PyValueError::new_err(
"permutation/shuffle require an array with at least 1 dimension",
));
}
let n = shape[0];
let row_size: usize = shape[1..].iter().product::<usize>().max(1);
let data = arr.to_vec();
let mut order: Vec<usize> = (0..n).collect();
self.rng.shuffle(&mut order);
let mut result = Vec::with_capacity(data.len());
for &i in &order {
result.extend_from_slice(&data[i * row_size..(i + 1) * row_size]);
}
Ok(Array::from_vec_shape(result, &shape)?)
}
}
#[pymethods]
impl PyGenerator {
#[new]
#[pyo3(signature = (seed=None))]
fn new(seed: Option<u64>) -> Self {
PyGenerator {
rng: generator_from_seed(seed),
}
}
fn seed(&mut self, seed: u64) {
self.rng = Random::<ThreadRng>::seed(seed);
}
#[pyo3(signature = (low=0.0, high=1.0, size=None))]
fn uniform(
&mut self,
py: Python<'_>,
low: f64,
high: f64,
size: Option<Vec<usize>>,
) -> PyResult<Py<PyAny>> {
if !matches!(low.partial_cmp(&high), Some(std::cmp::Ordering::Less)) {
return Err(PyValueError::new_err("uniform requires low < high"));
}
let dist = Uniform::new(low, high)
.map_err(|e| PyValueError::new_err(format!("Invalid uniform range: {e}")))?;
self.sample_scalar_or_array(py, size, |rng| rng.sample(dist))
}
#[pyo3(signature = (loc=0.0, scale=1.0, size=None))]
fn normal(
&mut self,
py: Python<'_>,
loc: f64,
scale: f64,
size: Option<Vec<usize>>,
) -> PyResult<Py<PyAny>> {
let dist = Normal::new(loc, scale)
.map_err(|e| PyValueError::new_err(format!("Invalid normal parameters: {e}")))?;
self.sample_scalar_or_array(py, size, |rng| rng.sample(dist))
}
#[pyo3(signature = (size=None))]
fn standard_normal(&mut self, py: Python<'_>, size: Option<Vec<usize>>) -> PyResult<Py<PyAny>> {
self.normal(py, 0.0, 1.0, size)
}
#[pyo3(signature = (size=None))]
fn random(&mut self, py: Python<'_>, size: Option<Vec<usize>>) -> PyResult<Py<PyAny>> {
self.sample_scalar_or_array(py, size, |rng| rng.random_f64())
}
#[pyo3(signature = (low, high=None, size=None))]
fn integers(
&mut self,
py: Python<'_>,
low: i64,
high: Option<i64>,
size: Option<Vec<usize>>,
) -> PyResult<Py<PyAny>> {
let (lo, hi) = match high {
Some(h) => (low, h),
None => (0, low),
};
if lo >= hi {
return Err(PyValueError::new_err(
"integers requires low < high (or, with high omitted, low > 0)",
));
}
self.sample_scalar_or_array(py, size, |rng| rng.random_range(lo..hi) as f64)
}
fn permutation(&mut self, x: &Bound<'_, PyAny>) -> PyResult<PyArray> {
if let Ok(n) = x.extract::<usize>() {
let mut idx: Vec<f64> = (0..n).map(|i| i as f64).collect();
self.rng.shuffle(&mut idx);
return Ok(PyArray {
inner: Array::from_vec(idx),
});
}
if let Ok(arr) = x.extract::<PyArray>() {
let inner = self.permuted_copy(&arr.inner)?;
return Ok(PyArray { inner });
}
Err(PyValueError::new_err(
"permutation expects a non-negative int or an Array",
))
}
fn shuffle(&mut self, x: &mut PyArray) -> PyResult<()> {
x.inner = self.permuted_copy(&x.inner)?;
Ok(())
}
}
#[pyfunction]
#[pyo3(signature = (seed=None))]
fn default_rng(seed: Option<u64>) -> PyGenerator {
PyGenerator {
rng: generator_from_seed(seed),
}
}
#[pyfunction]
fn randn(size: Vec<usize>) -> PyResult<PyArray> {
let dist = Normal::new(0.0, 1.0).map_err(|e| {
PyValueError::new_err(format!("Failed to create normal distribution: {}", e))
})?;
let total_size: usize = size.iter().product();
let mut rng = scirs2_core::random::thread_rng();
let data: Vec<f64> = (0..total_size).map(|_| rng.sample(dist)).collect();
Ok(PyArray {
inner: Array::from_vec_shape(data, &size)?,
})
}
#[pyfunction]
fn rand(size: Vec<usize>) -> PyResult<PyArray> {
let total_size: usize = size.iter().product();
let mut rng = scirs2_core::random::thread_rng();
let data: Vec<f64> = (0..total_size).map(|_| rng.random::<f64>()).collect();
Ok(PyArray {
inner: Array::from_vec_shape(data, &size)?,
})
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
let random_module = PyModule::new(m.py(), "random")?;
random_module.add_class::<PyGenerator>()?;
random_module.add_function(wrap_pyfunction!(default_rng, m)?)?;
random_module.add_function(wrap_pyfunction!(randn, m)?)?;
random_module.add_function(wrap_pyfunction!(rand, m)?)?;
m.add_submodule(&random_module)?;
Ok(())
}