qfall_math/integer/poly_over_z/sample/discrete_gauss.rs
1// Copyright 2023 Jan Niklas Siemer
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 discrete Gaussian distribution.
11
12use crate::{
13 error::MathError,
14 integer::PolyOverZ,
15 macros::seeded::seedable_function,
16 rational::Q,
17 traits::SetCoefficient,
18 utils::{
19 index::evaluate_index,
20 sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT},
21 },
22};
23use std::fmt::Display;
24
25impl PolyOverZ {
26 seedable_function!(
27 /// Initializes a new [`PolyOverZ`] with maximum degree `max_degree`
28 /// and with each entry sampled independently according to the
29 /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss).
30 ///
31 /// Parameters:
32 /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have
33 /// - `center`: specifies the positions of the center with peak probability
34 /// - `s`: specifies the Gaussian parameter, which is proportional
35 /// to the standard deviation `sigma * sqrt(2 * pi) = s`
36 #[seeded]
37 /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
38 #[optionally_seeded]
39 /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
40 #[optionally_seeded]
41 /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
42 ///
43 /// Returns a fresh [`PolyOverZ`] instance of maximum degree `max_degree`
44 /// with coefficients chosen independently according the discrete Gaussian distribution or
45 /// a [`MathError`] if `s < 0`.
46 ///
47 /// # Examples
48 /// ```
49 /// use qfall_math::integer::PolyOverZ;
50 ///
51 #[unseeded]
52 /// let sample = PolyOverZ::sample_discrete_gauss(2, 0, 1).unwrap();
53 #[seeded]
54 /// let sample = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
55 /// ```
56 ///
57 /// # Errors and Failures
58 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
59 /// if `s < 0`.
60 ///
61 /// # Panics ...
62 /// - if `max_degree` is negative, or does not fit into an [`i64`].
63 pub(crate) fn sample_discrete_gauss(
64 max_degree: impl TryInto<i64> + Display,
65 center: impl Into<Q>,
66 s: impl Into<Q>,
67 seed: Option<[u8; 32]>,
68 ) -> Result<Self, MathError> {
69 let max_degree = evaluate_index(max_degree).unwrap();
70
71 let center = center.into();
72 let s = s.into();
73 let mut poly = PolyOverZ::default();
74
75 let mut dgis = DiscreteGaussianIntegerSampler::init(
76 ¢er,
77 &s,
78 unsafe { TAILCUT },
79 LookupTableSetting::FillOnTheFly,
80 seed,
81 )?;
82
83 for index in 0..=max_degree {
84 let sample = dgis.sample_z();
85 unsafe { poly.set_coeff_unchecked(index, &sample) };
86 }
87 Ok(poly)
88 }
89 );
90}
91
92#[cfg(test)]
93mod test_sample_discrete_gauss {
94 use crate::utils::sample::test_seed;
95 use crate::{integer::PolyOverZ, rational::Q};
96
97 /// Checks whether `sample_discrete_gauss` is available for all types
98 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
99 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
100 #[test]
101 fn availability() {
102 for seeded in [false, true] {
103 let center = Q::ZERO;
104 let s = Q::ONE;
105
106 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
107 1u8,
108 0f32,
109 1u8,
110 seeded.then(test_seed),
111 );
112 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
113 1u16,
114 0f64,
115 1u16,
116 seeded.then(test_seed),
117 );
118 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
119 1u32,
120 0f32,
121 1u32,
122 seeded.then(test_seed),
123 );
124 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
125 1u64,
126 0f64,
127 1u64,
128 seeded.then(test_seed),
129 );
130 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
131 1i8,
132 0f32,
133 1i8,
134 seeded.then(test_seed),
135 );
136 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
137 1i8,
138 0f32,
139 1i16,
140 seeded.then(test_seed),
141 );
142 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
143 1i16,
144 0f32,
145 1i32,
146 seeded.then(test_seed),
147 );
148 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
149 1i32,
150 0f64,
151 1i64,
152 seeded.then(test_seed),
153 );
154 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
155 1i64,
156 center,
157 s,
158 seeded.then(test_seed),
159 );
160 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
161 1u8,
162 0f32,
163 1f64,
164 seeded.then(test_seed),
165 );
166
167 // explicitly checks the existence of the unseeded and the seeded function
168 if seeded {
169 let _ = PolyOverZ::sample_discrete_gauss_seeded(1u8, 0f32, 1u8, test_seed());
170 } else {
171 let _ = PolyOverZ::sample_discrete_gauss(1u8, 0f32, 1u8);
172 }
173 }
174 }
175
176 /// Checks whether the number of coefficients is correct.
177 #[test]
178 fn nr_coeffs() {
179 for seeded in [false, true] {
180 let degrees = [1, 3, 7, 15, 32, 120];
181 for degree in degrees {
182 let res = PolyOverZ::sample_discrete_gauss_optionally_seeded(
183 degree,
184 i64::MAX,
185 1,
186 seeded.then(test_seed),
187 )
188 .unwrap();
189
190 assert_eq!(
191 res.get_degree(),
192 degree,
193 "Could fail with negligible probability."
194 );
195 }
196 }
197 }
198
199 /// Checks whether the maximum degree needs to be at least 0.
200 #[test]
201 fn invalid_max_degree() {
202 for seeded in [false, true] {
203 let result = std::panic::catch_unwind(|| {
204 let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
205 -1,
206 0,
207 1,
208 seeded.then(test_seed),
209 )
210 .unwrap();
211 });
212 assert!(result.is_err());
213 }
214 }
215
216 /// Checks whether the same seed results in the same sample.
217 #[test]
218 fn same_seed_same_sample() {
219 use crate::integer::PolyOverZ;
220 let sample_0 = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
221 let sample_1 = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
222
223 assert_eq!(sample_0, sample_1);
224 }
225
226 /// Checks whether the seeded variant outputs the known sample for a fixed seed.
227 #[test]
228 fn known_answer() {
229 use crate::integer::PolyOverZ;
230 let sample = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
231
232 assert_eq!("1 1", sample.to_string());
233 }
234}