Skip to main content

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}