qfall_math/integer_mod_q/poly_over_zq/sample/binomial.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
10//! according to the binomial distribution.
11
12use crate::{
13 error::MathError,
14 integer::Z,
15 integer_mod_q::{Modulus, PolyOverZq},
16 macros::seeded::seedable_function,
17 rational::Q,
18 traits::SetCoefficient,
19 utils::{index::evaluate_index, sample::binomial::BinomialSampler},
20};
21use std::fmt::Display;
22
23impl PolyOverZq {
24 seedable_function!(
25 /// Generates a [`PolyOverZq`] instance of maximum degree `max_degree` and
26 /// coefficients chosen according to the binomial distribution
27 /// parameterized by `n` and `p`.
28 ///
29 /// Parameters:
30 /// - `max_degree`: specifies the length of the polynomial,
31 /// i.e. the number of coefficients
32 /// - `modulus`: specifies the [`Modulus`] of the new [`PolyOverZq`] instance
33 /// - `n`: specifies the number of trials
34 /// - `p`: specifies the probability of success
35 #[seeded]
36 /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
37 #[optionally_seeded]
38 /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
39 #[optionally_seeded]
40 /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
41 ///
42 /// Returns a fresh [`PolyOverZq`] instance with each value sampled
43 /// according to the binomial distribution or a [`MathError`]
44 /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`],
45 /// or `max_degree` is negative or does not into an [`i64`].
46 ///
47 /// # Examples
48 /// ```
49 /// use qfall_math::integer_mod_q::PolyOverZq;
50 ///
51 #[unseeded]
52 /// let sample = PolyOverZq::sample_binomial(2, 7, 2, 0.5).unwrap();
53 #[seeded]
54 /// let sample = PolyOverZq::sample_binomial_seeded(2, 7, 2, 0.5, [42; 32]).unwrap();
55 /// ```
56 ///
57 /// # Errors and Failures
58 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
59 /// if `n < 0`.
60 /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
61 /// if `p ∉ (0,1)`.
62 /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError)
63 /// if `n` does not fit into an [`i64`].
64 /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if
65 /// the `max_degree` is negative or it does not fit into an [`i64`].
66 pub(crate) fn sample_binomial(
67 max_degree: impl TryInto<i64> + Display,
68 modulus: impl Into<Modulus>,
69 n: impl Into<Z>,
70 p: impl Into<Q>,
71 seed: Option<[u8; 32]>,
72 ) -> Result<Self, MathError> {
73 Self::sample_binomial_with_offset_optionally_seeded(max_degree, 0, modulus, n, p, seed)
74 }
75 );
76
77 seedable_function!(
78 /// Generates a [`PolyOverZq`] instance of maximum degree `max_degree` and
79 /// coefficients chosen according to the binomial distribution
80 /// parameterized by `n` and `p` with given `offset`.
81 ///
82 /// Parameters:
83 /// - `max_degree`: specifies the length of the polynomial,
84 /// i.e. the number of coefficients
85 /// - `offset`: specifies an offset applied to each sample
86 /// collected from the binomial distribution
87 /// - `modulus`: specifies the [`Modulus`] of the new [`PolyOverZq`] instance
88 /// - `n`: specifies the number of trials
89 /// - `p`: specifies the probability of success
90 #[seeded]
91 /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
92 #[optionally_seeded]
93 /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
94 #[optionally_seeded]
95 /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
96 ///
97 /// Returns a fresh [`PolyOverZq`] instance with each value sampled
98 /// according to the binomial distribution or a [`MathError`]
99 /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`],
100 /// or `max_degree` is negative or does not into an [`i64`].
101 ///
102 /// # Examples
103 /// ```
104 /// use qfall_math::integer_mod_q::PolyOverZq;
105 ///
106 #[unseeded]
107 /// let sample = PolyOverZq::sample_binomial_with_offset(2, -1, 7, 2, 0.5).unwrap();
108 #[seeded]
109 /// let sample = PolyOverZq::sample_binomial_with_offset_seeded(2, -1, 7, 2, 0.5, [42; 32]).unwrap();
110 /// ```
111 ///
112 /// # Errors and Failures
113 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
114 /// if `n < 0`.
115 /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
116 /// if `p ∉ (0,1)`.
117 /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError)
118 /// if `n` does not fit into an [`i64`].
119 /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if
120 /// the `max_degree` is negative or it does not fit into an [`i64`].
121 ///
122 /// # Panics ...
123 /// - if `modulus` is smaller than `2`.
124 pub(crate) fn sample_binomial_with_offset(
125 max_degree: impl TryInto<i64> + Display,
126 offset: impl Into<Z>,
127 modulus: impl Into<Modulus>,
128 n: impl Into<Z>,
129 p: impl Into<Q>,
130 seed: Option<[u8; 32]>,
131 ) -> Result<Self, MathError> {
132 let max_degree = evaluate_index(max_degree)?;
133 let offset: Z = offset.into();
134 let modulus: Modulus = modulus.into();
135 let mut bin_sampler = BinomialSampler::init(n, p, seed)?;
136
137 let mut poly_z = PolyOverZq::from(&modulus);
138
139 for index in 0..=max_degree {
140 let mut sample = bin_sampler.sample();
141 sample += &offset;
142 unsafe { poly_z.set_coeff_unchecked(index, sample) };
143 }
144
145 Ok(poly_z)
146 }
147 );
148}
149
150#[cfg(test)]
151mod test_sample_binomial {
152 use super::{PolyOverZq, Q, Z};
153 use crate::traits::GetCoefficient;
154 use crate::utils::sample::test_seed;
155
156 // As all major tests regarding an appropriate binomial distribution,
157 // whether the correct interval is kept, and if the errors are thrown correctly,
158 // are performed in the `utils` module, we omit these tests here.
159
160 /// Checks whether the boundaries of the interval are kept.
161 #[test]
162 fn boundaries_kept() {
163 for seeded in [false, true] {
164 for _ in 0..8 {
165 let poly = PolyOverZq::sample_binomial_optionally_seeded(
166 0,
167 7,
168 2,
169 0.5,
170 seeded.then(test_seed),
171 )
172 .unwrap();
173 let sample: Z = poly.get_coeff(0).unwrap();
174 assert!(Z::ZERO <= sample || sample <= 2);
175 }
176 }
177 }
178
179 /// Checks whether the number of coefficients is correct.
180 #[test]
181 fn nr_coeffs() {
182 for seeded in [false, true] {
183 let degrees = [1, 3, 7, 15, 32, 120];
184 for degree in degrees {
185 let res = PolyOverZq::sample_binomial_optionally_seeded(
186 degree,
187 u64::MAX,
188 256,
189 0.99999,
190 seeded.then(test_seed),
191 )
192 .unwrap();
193
194 assert_eq!(
195 degree,
196 res.get_degree(),
197 "This test can fail with probability close to 0."
198 );
199 }
200 }
201 }
202
203 /// Checks whether providing a length smaller than `0` results in an error.
204 #[test]
205 fn invalid_max_degree() {
206 for seeded in [false, true] {
207 let res_0 = PolyOverZq::sample_binomial_optionally_seeded(
208 -1,
209 7,
210 2,
211 0.5,
212 seeded.then(test_seed),
213 );
214 let res_1 = PolyOverZq::sample_binomial_optionally_seeded(
215 i64::MIN,
216 7,
217 2,
218 0.5,
219 seeded.then(test_seed),
220 );
221
222 assert!(res_0.is_err());
223 assert!(res_1.is_err());
224 }
225 }
226
227 /// Checks whether `sample_binomial` is available for all types
228 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
229 /// and [`Into<Q>`], i.e. u8, u16, i8, i16, f32, f64, ...
230 #[test]
231 fn availability() {
232 for seeded in [false, true] {
233 let _ = PolyOverZq::sample_binomial_optionally_seeded(
234 1,
235 7u8,
236 1u16,
237 7u8,
238 seeded.then(test_seed),
239 );
240 let _ = PolyOverZq::sample_binomial_optionally_seeded(
241 1,
242 7u16,
243 1u32,
244 7u16,
245 seeded.then(test_seed),
246 );
247 let _ = PolyOverZq::sample_binomial_optionally_seeded(
248 1,
249 7u32,
250 1u64,
251 7u32,
252 seeded.then(test_seed),
253 );
254 let _ = PolyOverZq::sample_binomial_optionally_seeded(
255 1,
256 7u64,
257 1i8,
258 7u64,
259 seeded.then(test_seed),
260 );
261 let _ = PolyOverZq::sample_binomial_optionally_seeded(
262 1,
263 7i8,
264 1i16,
265 7i8,
266 seeded.then(test_seed),
267 );
268 let _ = PolyOverZq::sample_binomial_optionally_seeded(
269 1,
270 7i16,
271 1i32,
272 7i16,
273 seeded.then(test_seed),
274 );
275 let _ = PolyOverZq::sample_binomial_optionally_seeded(
276 1,
277 7i32,
278 1i64,
279 7i32,
280 seeded.then(test_seed),
281 );
282 let _ = PolyOverZq::sample_binomial_optionally_seeded(
283 1,
284 7i64,
285 Z::ONE,
286 7i64,
287 seeded.then(test_seed),
288 );
289 let _ = PolyOverZq::sample_binomial_optionally_seeded(
290 1,
291 7,
292 1u8,
293 0.5f32,
294 seeded.then(test_seed),
295 );
296 let _ = PolyOverZq::sample_binomial_optionally_seeded(
297 1,
298 7,
299 1u8,
300 0.5f64,
301 seeded.then(test_seed),
302 );
303 let _ = PolyOverZq::sample_binomial_optionally_seeded(
304 1,
305 7,
306 1,
307 Q::from((1, 2)),
308 seeded.then(test_seed),
309 );
310
311 // explicitly checks the existence of the unseeded and the seeded function
312 if seeded {
313 let _ = PolyOverZq::sample_binomial_seeded(1, 7u8, 1u16, 7u8, test_seed());
314 } else {
315 let _ = PolyOverZq::sample_binomial(1, 7u8, 1u16, 7u8);
316 }
317 }
318 }
319
320 /// Checks whether the same seed results in the same sample.
321 #[test]
322 fn same_seed_same_sample() {
323 use crate::integer_mod_q::PolyOverZq;
324 let sample_0 = PolyOverZq::sample_binomial_seeded(2, 7, 2, 0.5, [42; 32]).unwrap();
325 let sample_1 = PolyOverZq::sample_binomial_seeded(2, 7, 2, 0.5, [42; 32]).unwrap();
326
327 assert_eq!(sample_0, sample_1);
328 }
329
330 /// Checks whether the seeded variant outputs the known sample for a fixed seed.
331 #[test]
332 fn known_answer() {
333 use crate::integer_mod_q::PolyOverZq;
334 let sample = PolyOverZq::sample_binomial_seeded(2, 7, 2, 0.5, [42; 32]).unwrap();
335
336 assert_eq!("3 0 2 1 mod 7", sample.to_string());
337 }
338}
339
340#[cfg(test)]
341mod test_sample_binomial_with_offset {
342 use super::{PolyOverZq, Q, Z};
343 use crate::traits::GetCoefficient;
344 use crate::utils::sample::test_seed;
345
346 // As all major tests regarding an appropriate binomial distribution,
347 // whether the correct interval is kept, and if the errors are thrown correctly,
348 // are performed in the `utils` module, we omit these tests here.
349
350 /// Checks whether the boundaries of the interval are kept.
351 #[test]
352 fn boundaries_kept() {
353 for seeded in [false, true] {
354 for _ in 0..8 {
355 let poly = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
356 0,
357 -1,
358 7,
359 2,
360 0.5,
361 seeded.then(test_seed),
362 )
363 .unwrap();
364 let sample: Z = poly.get_coeff(0).unwrap();
365 assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE);
366 }
367 }
368 }
369
370 /// Checks whether the number of coefficients is correct.
371 #[test]
372 fn nr_coeffs() {
373 for seeded in [false, true] {
374 let degrees = [1, 3, 7, 15, 32, 120];
375 for degree in degrees {
376 let res = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
377 degree,
378 1,
379 7,
380 2,
381 0.5,
382 seeded.then(test_seed),
383 )
384 .unwrap();
385
386 assert_eq!(degree, res.get_degree());
387 }
388 }
389 }
390
391 /// Checks whether providing a length smaller than `0` results in an error.
392 #[test]
393 fn invalid_max_degree() {
394 for seeded in [false, true] {
395 let res_0 = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
396 -1,
397 -1,
398 7,
399 2,
400 0.5,
401 seeded.then(test_seed),
402 );
403 let res_1 = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
404 i64::MIN,
405 -1,
406 7,
407 2,
408 0.5,
409 seeded.then(test_seed),
410 );
411
412 assert!(res_0.is_err());
413 assert!(res_1.is_err());
414 }
415 }
416
417 /// Checks whether `sample_binomial_with_offset` is available for all types
418 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
419 /// and [`Into<Q>`], i.e. u8, u16, i8, i16, f32, f64, ...
420 #[test]
421 fn availability() {
422 for seeded in [false, true] {
423 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
424 1,
425 0,
426 7u8,
427 1u16,
428 7u8,
429 seeded.then(test_seed),
430 );
431 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
432 1,
433 0,
434 7u16,
435 1u32,
436 7u16,
437 seeded.then(test_seed),
438 );
439 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
440 1,
441 0,
442 7u32,
443 1u64,
444 7u32,
445 seeded.then(test_seed),
446 );
447 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
448 1,
449 0,
450 7u64,
451 1i8,
452 7u64,
453 seeded.then(test_seed),
454 );
455 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
456 1,
457 0,
458 7i8,
459 1i16,
460 7i8,
461 seeded.then(test_seed),
462 );
463 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
464 1,
465 0,
466 7i16,
467 1i32,
468 7i16,
469 seeded.then(test_seed),
470 );
471 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
472 1,
473 0,
474 7i32,
475 1i64,
476 7i32,
477 seeded.then(test_seed),
478 );
479 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
480 1,
481 0,
482 7i64,
483 Z::ONE,
484 7i64,
485 seeded.then(test_seed),
486 );
487 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
488 1,
489 0,
490 7,
491 1u8,
492 0.5f32,
493 seeded.then(test_seed),
494 );
495 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
496 1,
497 0,
498 7,
499 1u8,
500 0.5f64,
501 seeded.then(test_seed),
502 );
503 let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded(
504 1,
505 0,
506 7,
507 1,
508 Q::from((1, 2)),
509 seeded.then(test_seed),
510 );
511
512 // explicitly checks the existence of the unseeded and the seeded function
513 if seeded {
514 let _ = PolyOverZq::sample_binomial_with_offset_seeded(
515 1,
516 0,
517 7u8,
518 1u16,
519 7u8,
520 test_seed(),
521 );
522 } else {
523 let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7u8, 1u16, 7u8);
524 }
525 }
526 }
527
528 /// Checks whether the same seed results in the same sample.
529 #[test]
530 fn same_seed_same_sample() {
531 use crate::integer_mod_q::PolyOverZq;
532 let sample_0 =
533 PolyOverZq::sample_binomial_with_offset_seeded(2, -1, 7, 2, 0.5, [42; 32]).unwrap();
534 let sample_1 =
535 PolyOverZq::sample_binomial_with_offset_seeded(2, -1, 7, 2, 0.5, [42; 32]).unwrap();
536
537 assert_eq!(sample_0, sample_1);
538 }
539
540 /// Checks whether the seeded variant outputs the known sample for a fixed seed.
541 #[test]
542 fn known_answer() {
543 use crate::integer_mod_q::PolyOverZq;
544 let sample =
545 PolyOverZq::sample_binomial_with_offset_seeded(2, -1, 7, 2, 0.5, [42; 32]).unwrap();
546
547 assert_eq!("2 6 1 mod 7", sample.to_string());
548 }
549}