Skip to main content

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