Skip to main content

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