use std::cell::RefCell;
use std::sync::OnceLock;
use crate::ops::{add, divide, erfinv, minimum, multiply, slice, subtract};
use crate::primitive::Primitive;
use crate::{Array, DType, Error, Result, SliceArg, bf16, f16};
pub fn key(seed: u64) -> Array {
Array::from_slice(&[(seed >> 32) as u32, seed as u32], &[2]).expect("two elements")
}
fn time_seed() -> u64 {
static SEED: OnceLock<u64> = OnceLock::new();
*SEED.get_or_init(|| {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_millis() as u64)
})
}
thread_local!(static SEQUENCE: RefCell<Option<Array>> = const { RefCell::new(None) });
pub fn seed(seed: u64) {
SEQUENCE.set(Some(key(seed)));
}
fn next_key() -> Result<Array> {
let current = SEQUENCE.with_borrow_mut(|k| k.get_or_insert_with(|| key(time_seed())).clone());
let (keep, out) = split_pair(¤t)?;
SEQUENCE.set(Some(keep));
Ok(out)
}
fn key_or_next(key: Option<&Array>) -> Result<Array> {
match key {
Some(k) => Ok(k.clone()),
None => next_key(),
}
}
pub fn bits(shape: &[usize], width: usize, key: Option<&Array>) -> Result<Array> {
let key = key_or_next(key)?;
if key.dtype() != DType::U32 || key.shape() != [2] {
return Err(Error::InvalidArgument(format!(
"bits: expected a uint32 key of shape [2], got {} {:?}",
key.dtype(),
key.shape()
)));
}
let width = u8::try_from(width)
.map_err(|_| Error::InvalidArgument(format!("bits: width {width} not in {{1, 2, 4}}")))?;
let primitive = Primitive::RandomBits {
shape: shape.to_vec(),
width,
};
Ok(Array::from_op(primitive, vec![key])?.swap_remove(0))
}
pub fn split(key: &Array, num: usize) -> Result<Array> {
bits(&[num, 2], 4, Some(key))
}
pub fn split_pair(key: &Array) -> Result<(Array, Array)> {
let both = split(key, 2)?;
let row = |i: isize| {
let args = [SliceArg::new(Some(i), Some(i + 1), 1), SliceArg::full()];
slice(&both, &args)?.reshape(&[2])
};
Ok((row(0)?, row(1)?))
}
fn below_one(dtype: DType) -> Result<f32> {
Ok(match dtype {
DType::F32 => f32::from_bits(1.0f32.to_bits() - 1),
DType::F16 => f16::from_bits(f16::ONE.to_bits() - 1).to_f32(),
DType::BF16 => bf16::from_bits(bf16::ONE.to_bits() - 1).to_f32(),
_ => {
return Err(Error::UnsupportedDType {
op: "uniform",
dtype,
});
}
})
}
pub fn uniform(
low: f64,
high: f64,
shape: &[usize],
dtype: DType,
key: Option<&Array>,
) -> Result<Array> {
let upper = below_one(dtype)?;
let lo = Array::scalar(low).astype(dtype)?;
let hi = Array::scalar(high).astype(dtype)?;
let range = subtract(&hi, &lo)?;
let out = divide(&bits(shape, 4, key)?, Array::scalar(u32::MAX as f32))?;
let out = minimum(&out, Array::scalar(upper))?.astype(dtype)?;
add(multiply(&range, &out)?, &lo)
}
pub fn normal(
shape: &[usize],
dtype: DType,
loc: Option<f64>,
scale: Option<f64>,
key: Option<&Array>,
) -> Result<Array> {
if !matches!(dtype, DType::F16 | DType::BF16 | DType::F32 | DType::F64) {
return Err(Error::UnsupportedDType {
op: "normal",
dtype,
});
}
let low = f64::from(f32::from_bits((-1.0f32).to_bits() - 1));
let samples = uniform(low, 1.0, shape, DType::F32, key)?;
let mut applied = Array::scalar(2f64.sqrt()).astype(dtype)?;
if let Some(scale) = scale {
applied = multiply(&applied, Array::scalar(scale).astype(dtype)?)?;
}
let samples = multiply(&applied, erfinv(&samples)?.astype(dtype)?)?;
match loc {
Some(loc) => add(Array::scalar(loc).astype(dtype)?, &samples),
None => Ok(samples),
}
}
pub fn gumbel(shape: &[usize], dtype: DType, key: Option<&Array>) -> Result<Array> {
let u = uniform(0.0, 1.0, shape, dtype, key)?;
crate::ops::negative(&crate::ops::log(&crate::ops::negative(&crate::ops::log(
&u,
)?)?)?)
}
pub fn categorical(logits: &Array, axis: isize, key: Option<&Array>) -> Result<Array> {
let noise = gumbel(logits.shape(), logits.dtype(), key)?;
crate::ops::argmax(&add(&noise, logits)?, Some(axis), false)
}
pub fn randint(
low: i64,
high: i64,
shape: &[usize],
dtype: DType,
key: Option<&Array>,
) -> Result<Array> {
if dtype.category() == crate::Category::Float || dtype == DType::Complex64 {
return Err(Error::UnsupportedDType {
op: "randint",
dtype,
});
}
let u = uniform(low as f64, high as f64, shape, DType::F32, key)?;
let u = crate::ops::maximum(&u, Array::scalar(low as f32))?;
let t = u.astype(DType::I64)?;
let up = crate::ops::less(&u, &t.astype(DType::F32)?)?;
subtract(&t, up.astype(DType::I64)?)?.astype(dtype)
}
pub fn bernoulli(p: f64, shape: &[usize], key: Option<&Array>) -> Result<Array> {
crate::ops::less(&uniform(0.0, 1.0, shape, DType::F32, key)?, p)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernels::threefry2x32;
#[test]
fn threefry_known_answer() {
assert_eq!(threefry2x32((0, 0), (0, 0)), (0x6b20_0159, 0x99ba_4efe));
}
#[test]
fn keys_and_layout() {
assert_eq!(key(1 << 32 | 7).to_vec::<u32>().unwrap(), [1, 7]);
let k = key(3);
for (n, width) in [(5, 4), (3, 2), (7, 1), (0, 4)] {
let b = bits(&[n], width, Some(&k)).unwrap();
b.eval().unwrap();
assert_eq!(b.size(), n);
}
assert!(bits(&[2], 3, Some(&k)).is_err());
assert!(bits(&[2], 4, Some(&Array::scalar(1u32))).is_err());
}
#[test]
fn sequences_are_per_thread_and_reproducible() {
seed(42);
let a = uniform(0.0, 1.0, &[4], DType::F32, None)
.unwrap()
.to_vec::<f32>()
.unwrap();
seed(42);
let b = uniform(0.0, 1.0, &[4], DType::F32, None)
.unwrap()
.to_vec::<f32>()
.unwrap();
assert_eq!(a, b);
assert!(a.iter().all(|&x| (0.0..1.0).contains(&x)));
}
}