use crate::{error::MathError, integer::Z};
use flint3_sys::{fmpz_addmul_ui, fmpz_set_ui};
use rand::{
Rng, RngExt, SeedableRng, TryCryptoRng, TryRng,
rngs::{StdRng, ThreadRng},
};
use std::{cell::RefCell, convert::Infallible, rc::Rc};
#[derive(Debug, Clone)]
pub enum SamplerRng {
Thread(ThreadRng),
Seeded(Rc<RefCell<StdRng>>),
}
impl SamplerRng {
pub(crate) fn new(seed: Option<[u8; 32]>) -> Self {
match seed {
Some(seed) => Self::Seeded(Rc::new(RefCell::new(StdRng::from_seed(seed)))),
None => Self::Thread(rand::rng()),
}
}
pub(crate) fn derive_seed(&mut self) -> Option<[u8; 32]> {
match self {
Self::Thread(_) => None,
Self::Seeded(_) => Some(self.random()),
}
}
}
impl Default for SamplerRng {
fn default() -> Self {
Self::new(None)
}
}
impl TryRng for SamplerRng {
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
match self {
Self::Thread(rng) => rng.try_next_u32(),
Self::Seeded(rng) => rng.borrow_mut().try_next_u32(),
}
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
match self {
Self::Thread(rng) => rng.try_next_u64(),
Self::Seeded(rng) => rng.borrow_mut().try_next_u64(),
}
}
fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
match self {
Self::Thread(rng) => rng.try_fill_bytes(dst),
Self::Seeded(rng) => rng.borrow_mut().try_fill_bytes(dst),
}
}
}
impl TryCryptoRng for SamplerRng {}
pub struct UniformIntegerSampler {
pub interval_size: Z,
pub two_pow_32: u64,
pub nr_iterations: u32,
pub upper_modulo: u32,
pub rng: SamplerRng,
}
impl UniformIntegerSampler {
pub fn init(interval_size: &Z, seed: Option<[u8; 32]>) -> Result<Self, MathError> {
Self::init_with_rng(interval_size, SamplerRng::new(seed))
}
pub(crate) fn init_with_rng(interval_size: &Z, rng: SamplerRng) -> Result<Self, MathError> {
if interval_size < &Z::ONE {
return Err(MathError::InvalidInterval(format!(
"An invalid interval size {interval_size} was provided."
)));
}
let two_pow_32 = u32::MAX as u64 + 1;
let bit_size = (interval_size - Z::ONE).bits() as u32;
let nr_iterations = bit_size / 32;
let upper_modulo = 2_u32.pow(bit_size % 32);
Ok(Self {
interval_size: interval_size.clone(),
two_pow_32,
nr_iterations,
upper_modulo,
rng,
})
}
pub fn sample(&mut self) -> Z {
if self.interval_size.is_one() {
return Z::ZERO;
}
let mut sample = self.sample_bits_uniform();
while sample >= self.interval_size {
sample = self.sample_bits_uniform();
}
sample
}
pub fn sample_bits_uniform(&mut self) -> Z {
let mut value = Z::from(self.rng.next_u32() % self.upper_modulo);
for _ in 0..self.nr_iterations {
let sample = self.rng.next_u32();
let mut res = Z::default();
unsafe {
fmpz_set_ui(&mut res.value, sample as u64);
fmpz_addmul_ui(&mut res.value, &value.value, self.two_pow_32);
};
value = res;
}
value
}
}
#[cfg(test)]
mod test_uis {
use super::{UniformIntegerSampler, Z};
use crate::utils::sample::test_seed;
use std::collections::HashSet;
#[test]
fn small_interval() {
for seeded in [false, true] {
let size_2 = Z::from(2);
let size_7 = Z::from(7);
let mut uis_2 = UniformIntegerSampler::init(&size_2, seeded.then(test_seed)).unwrap();
let mut uis_7 = UniformIntegerSampler::init(&size_7, seeded.then(test_seed)).unwrap();
for _ in 0..3 {
let sample_2 = uis_2.sample();
let sample_7 = uis_7.sample();
assert!(Z::ZERO <= sample_2);
assert!(sample_2 < size_2);
assert!(Z::ZERO <= sample_7);
assert!(sample_7 < size_7)
}
}
}
#[test]
fn large_interval() {
for seeded in [false, true] {
let size_0 = Z::from(u64::MAX);
let size_1 = Z::from(u64::MAX) * 2 + 1;
let mut uis_0 = UniformIntegerSampler::init(&size_0, seeded.then(test_seed)).unwrap();
let mut uis_1 = UniformIntegerSampler::init(&size_1, seeded.then(test_seed)).unwrap();
for _i in 0..u8::MAX {
let sample_0 = uis_0.sample();
let sample_1 = uis_1.sample();
assert!(Z::ZERO <= sample_0);
assert!(sample_0 < size_0);
assert!(Z::ZERO <= sample_1);
assert!(sample_1 < size_1);
}
}
}
#[test]
fn entire_interval() {
for seeded in [false, true] {
let interval_sizes = vec![6, 7, 16];
for interval_size in interval_sizes {
let interval = Z::from(interval_size);
let mut uis =
UniformIntegerSampler::init(&interval, seeded.then(test_seed)).unwrap();
let mut samples = HashSet::new();
for _ in 0..2_u32.pow(interval_size) {
samples.insert(uis.sample());
}
assert_eq!(
interval_size,
samples.len() as u32,
"This test may fail with low probability."
);
}
}
}
#[test]
fn invalid_interval() {
for seeded in [false, true] {
assert!(UniformIntegerSampler::init(&Z::ZERO, seeded.then(test_seed)).is_err());
assert!(UniformIntegerSampler::init(&Z::MINUS_ONE, seeded.then(test_seed)).is_err());
}
}
#[test]
fn sample_bits_uniform_necessary_nr_bytes() {
for seeded in [false, true] {
let size_0 = Z::from(8);
let size_1 = Z::from(256);
let size_2 = Z::from(u32::MAX) + Z::ONE;
let mut uis_0 = UniformIntegerSampler::init(&size_0, seeded.then(test_seed)).unwrap();
let mut uis_1 = UniformIntegerSampler::init(&size_1, seeded.then(test_seed)).unwrap();
let mut uis_2 = UniformIntegerSampler::init(&size_2, seeded.then(test_seed)).unwrap();
for _ in 0..u8::MAX {
let sample_0 = uis_0.sample_bits_uniform();
let sample_1 = uis_1.sample_bits_uniform();
let sample_2 = uis_2.sample_bits_uniform();
assert!(Z::ZERO <= sample_0);
assert!(sample_0 < size_0);
assert!(Z::ZERO <= sample_1);
assert!(sample_1 < size_1);
assert!(Z::ZERO <= sample_2);
assert!(sample_2 < size_2);
}
}
}
#[test]
fn same_seed_same_samples() {
let interval_sizes = [Z::from(2), Z::from(7), Z::from(u64::MAX) * 2 + 1];
for interval_size in interval_sizes {
let mut uis_0 = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap();
let mut uis_1 = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap();
for _ in 0..u8::MAX {
assert_eq!(uis_0.sample(), uis_1.sample());
}
}
}
#[test]
fn known_answer() {
let interval_size = Z::from(u64::MAX) * 2 + 1;
let mut uis = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap();
let samples: Vec<String> = (0..4).map(|_| uis.sample().to_string()).collect();
assert_eq!(
"22821106978489223123, 9739806954439942819, 36580328303367661063, 5398446775404852435",
samples.join(", ")
);
}
}