use crate::{
error::MathError, macros::seeded::seedable_function, rational::Q,
utils::sample::uniform::SamplerRng,
};
use probability::{
distribution::{Gaussian, Sample},
source,
};
use rand::Rng;
impl Q {
seedable_function!(
#[seeded]
#[optionally_seeded]
#[optionally_seeded]
#[unseeded]
#[seeded]
pub(crate) fn sample_gauss(
center: impl Into<Q>,
sigma: impl Into<f64>,
seed: Option<[u8; 32]>,
) -> Result<Q, MathError> {
let center = center.into();
let sigma = sigma.into();
if sigma <= 0.0 {
return Err(MathError::NonPositive(format!(
"The sigma has to be positive and not zero, but the provided value is {sigma}."
)));
}
let mut rng = SamplerRng::new(seed);
let mut source = source::default(rng.next_u64());
let sampler = Gaussian::new(0.0, sigma);
let sample = center + Q::from(sampler.sample(&mut source));
Ok(sample)
}
);
}
#[cfg(test)]
mod test_sample_gauss {
use crate::rational::Q;
use crate::utils::sample::test_seed;
#[test]
fn in_concentration_bound() {
for seeded in [false, true] {
let range = 3;
for (mu, sigma) in [(i64::MAX, 1), (0, 20), (i64::MIN, 100)] {
assert!(
range * sigma
>= (Q::from(mu)
- Q::sample_gauss_optionally_seeded(mu, sigma, seeded.then(test_seed))
.unwrap())
.abs()
)
}
}
}
#[test]
fn non_positive_sigma() {
for seeded in [false, true] {
for (mu, sigma) in [(0, 0), (0, -1)] {
assert!(
Q::sample_gauss_optionally_seeded(mu, sigma, seeded.then(test_seed)).is_err()
)
}
}
}
#[test]
fn same_seed_same_sample() {
use crate::rational::Q;
let sample_0 = Q::sample_gauss_seeded(0, 1, [42; 32]).unwrap();
let sample_1 = Q::sample_gauss_seeded(0, 1, [42; 32]).unwrap();
assert_eq!(sample_0, sample_1);
}
#[test]
fn known_answer() {
use crate::rational::Q;
let sample = Q::sample_gauss_seeded(0, 1, [42; 32]).unwrap();
assert_eq!("2896371026169371/2251799813685248", sample.to_string());
}
#[test]
fn availability() {
use crate::rational::Q;
let _ = Q::sample_gauss(0, 1);
let _ = Q::sample_gauss_seeded(0, 1, test_seed());
}
}