qfall_math/rational/poly_over_q/sample/gauss.rs
1// Copyright © 2024 Marcel Luca Schmidt
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! This module contains algorithms for sampling according
10//! to the Gaussian distribution.
11
12use crate::{
13 error::MathError,
14 rational::{PolyOverQ, Q},
15 traits::SetCoefficient,
16 utils::index::evaluate_index,
17};
18use probability::{
19 prelude::{Gaussian, Sample},
20 source,
21};
22use rand::RngCore;
23use std::fmt::Display;
24
25impl PolyOverQ {
26 /// Initializes a new [`PolyOverQ`] with maximum degree `max_degree`
27 /// and with each coefficient sampled independently according to the
28 /// Gaussian distribution, using [`Q::sample_gauss`].
29 ///
30 /// Parameters:
31 /// - `max_degree`: specifies the included maximal degree the created [`PolyOverQ`] should have
32 /// - `center`: specifies the center for each coefficient of the polynomial
33 /// - `sigma`: specifies the standard deviation
34 ///
35 /// Returns a fresh [`PolyOverQ`] instance of maximum degree `max_degree`
36 /// with coefficients chosen independently according the Gaussian
37 /// distribution or a [`MathError`] if the specified parameters were not chosen
38 /// appropriately (`sigma > 0`).
39 ///
40 /// # Examples
41 /// ```
42 /// use qfall_math::rational::PolyOverQ;
43 ///
44 /// let sample = PolyOverQ::sample_gauss(2, 0, 1).unwrap();
45 /// ```
46 ///
47 /// # Errors and Failures
48 /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive)
49 /// if `sigma <= 0`.
50 ///
51 /// # Panics ...
52 /// - if `max_degree` is negative, or does not fit into an [`i64`].
53 pub fn sample_gauss(
54 max_degree: impl TryInto<i64> + Display,
55 center: impl Into<Q>,
56 sigma: impl Into<f64>,
57 ) -> Result<Self, MathError> {
58 let max_degree = evaluate_index(max_degree).unwrap();
59
60 let center = center.into();
61 let sigma: f64 = sigma.into();
62 let mut poly = PolyOverQ::default();
63
64 if sigma <= 0.0 {
65 return Err(MathError::NonPositive(format!(
66 "The sigma has to be positive and not zero, but the provided value is {sigma}."
67 )));
68 }
69 let mut rng = rand::rng();
70 let mut source = source::default(rng.next_u64());
71
72 // Instead of sampling with a center of c, we sample with center 0 and add the
73 // center later. These are equivalent and this way we can sample in larger ranges
74 let sampler = Gaussian::new(0.0, sigma);
75
76 for index in 0..=max_degree {
77 let mut sample = Q::from(sampler.sample(&mut source));
78 sample += ¢er;
79 unsafe { poly.set_coeff_unchecked(index, &sample) };
80 }
81 Ok(poly)
82 }
83}
84
85#[cfg(test)]
86mod test_sample_gauss {
87 use crate::{
88 integer::Z,
89 rational::{PolyOverQ, Q},
90 };
91
92 /// Checks whether `sample_gauss` is available for all types
93 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
94 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
95 #[test]
96 fn availability() {
97 let center_q = Q::ZERO;
98 let center_z = Z::ZERO;
99
100 let _ = PolyOverQ::sample_gauss(1u8, 16u8, 0f32);
101 let _ = PolyOverQ::sample_gauss(1u16, 16u16, 0f64);
102 let _ = PolyOverQ::sample_gauss(1u32, 16u32, 0f32);
103 let _ = PolyOverQ::sample_gauss(1u64, 16u64, 0f64);
104 let _ = PolyOverQ::sample_gauss(1i8, 16u8, 0f32);
105 let _ = PolyOverQ::sample_gauss(1i16, 16i16, 0f32);
106 let _ = PolyOverQ::sample_gauss(1i32, 16i32, 0f32);
107 let _ = PolyOverQ::sample_gauss(1i64, 16i64, 0f64);
108 let _ = PolyOverQ::sample_gauss(1u8, center_q, 0f32);
109 let _ = PolyOverQ::sample_gauss(1u8, center_z, 0f64);
110 }
111
112 /// Checks whether the number of coefficients is correct.
113 #[test]
114 fn nr_coeffs() {
115 let degrees = [1, 3, 7, 15, 32, 120];
116 for degree in degrees {
117 let res = PolyOverQ::sample_gauss(degree, i64::MAX, 1).unwrap();
118
119 assert_eq!(
120 res.get_degree(),
121 degree,
122 "Could fail with negligible probability."
123 );
124 }
125 }
126
127 /// Checks is as error is returned, if sigma is less than 1.
128 #[test]
129 fn invalid_sigma() {
130 assert!(PolyOverQ::sample_gauss(1, 0, 0).is_err());
131 assert!(PolyOverQ::sample_gauss(1, 0, -1).is_err());
132 }
133
134 /// Checks whether the maximum degree needs to be at least 0.
135 #[test]
136 #[should_panic]
137 fn invalid_max_degree() {
138 let _ = PolyOverQ::sample_gauss(-1, 0, 1).unwrap();
139 }
140}