use crate::{
error::MathError,
macros::seeded::seedable_function,
rational::{PolyOverQ, Q},
traits::SetCoefficient,
utils::index::evaluate_index,
utils::sample::uniform::SamplerRng,
};
use probability::{
prelude::{Gaussian, Sample},
source,
};
use rand::Rng;
use std::fmt::Display;
impl PolyOverQ {
seedable_function!(
#[seeded]
#[optionally_seeded]
#[optionally_seeded]
#[unseeded]
#[seeded]
pub(crate) fn sample_gauss(
max_degree: impl TryInto<i64> + Display,
center: impl Into<Q>,
sigma: impl Into<f64>,
seed: Option<[u8; 32]>,
) -> Result<Self, MathError> {
let max_degree = evaluate_index(max_degree).unwrap();
let center = center.into();
let sigma: f64 = sigma.into();
let mut poly = PolyOverQ::default();
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);
for index in 0..=max_degree {
let mut sample = Q::from(sampler.sample(&mut source));
sample += ¢er;
unsafe { poly.set_coeff_unchecked(index, &sample) };
}
Ok(poly)
}
);
}
#[cfg(test)]
mod test_sample_gauss {
use crate::utils::sample::test_seed;
use crate::{
integer::Z,
rational::{PolyOverQ, Q},
};
#[test]
fn availability() {
for seeded in [false, true] {
let center_q = Q::ZERO;
let center_z = Z::ZERO;
let _ =
PolyOverQ::sample_gauss_optionally_seeded(1u8, 16u8, 0f32, seeded.then(test_seed));
let _ = PolyOverQ::sample_gauss_optionally_seeded(
1u16,
16u16,
0f64,
seeded.then(test_seed),
);
let _ = PolyOverQ::sample_gauss_optionally_seeded(
1u32,
16u32,
0f32,
seeded.then(test_seed),
);
let _ = PolyOverQ::sample_gauss_optionally_seeded(
1u64,
16u64,
0f64,
seeded.then(test_seed),
);
let _ =
PolyOverQ::sample_gauss_optionally_seeded(1i8, 16u8, 0f32, seeded.then(test_seed));
let _ = PolyOverQ::sample_gauss_optionally_seeded(
1i16,
16i16,
0f32,
seeded.then(test_seed),
);
let _ = PolyOverQ::sample_gauss_optionally_seeded(
1i32,
16i32,
0f32,
seeded.then(test_seed),
);
let _ = PolyOverQ::sample_gauss_optionally_seeded(
1i64,
16i64,
0f64,
seeded.then(test_seed),
);
let _ = PolyOverQ::sample_gauss_optionally_seeded(
1u8,
center_q,
0f32,
seeded.then(test_seed),
);
let _ = PolyOverQ::sample_gauss_optionally_seeded(
1u8,
center_z,
0f64,
seeded.then(test_seed),
);
if seeded {
let _ = PolyOverQ::sample_gauss_seeded(1u8, 16u8, 0f32, test_seed());
} else {
let _ = PolyOverQ::sample_gauss(1u8, 16u8, 0f32);
}
}
}
#[test]
fn nr_coeffs() {
for seeded in [false, true] {
let degrees = [1, 3, 7, 15, 32, 120];
for degree in degrees {
let res = PolyOverQ::sample_gauss_optionally_seeded(
degree,
i64::MAX,
1,
seeded.then(test_seed),
)
.unwrap();
assert_eq!(
res.get_degree(),
degree,
"Could fail with negligible probability."
);
}
}
}
#[test]
fn invalid_sigma() {
for seeded in [false, true] {
assert!(
PolyOverQ::sample_gauss_optionally_seeded(1, 0, 0, seeded.then(test_seed)).is_err()
);
assert!(
PolyOverQ::sample_gauss_optionally_seeded(1, 0, -1, seeded.then(test_seed))
.is_err()
);
}
}
#[test]
fn invalid_max_degree() {
for seeded in [false, true] {
let result = std::panic::catch_unwind(|| {
let _ = PolyOverQ::sample_gauss_optionally_seeded(-1, 0, 1, seeded.then(test_seed))
.unwrap();
});
assert!(result.is_err());
}
}
#[test]
fn same_seed_same_sample() {
use crate::rational::PolyOverQ;
let sample_0 = PolyOverQ::sample_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
let sample_1 = PolyOverQ::sample_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
assert_eq!(sample_0, sample_1);
}
#[test]
fn known_answer() {
use crate::rational::PolyOverQ;
let sample = PolyOverQ::sample_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
assert_eq!(
"3 2896371026169371/2251799813685248 5860948106209927/4503599627370496 -1555108755990479/1125899906842624",
sample.to_string()
);
}
}