Skip to main content

qfall_math/integer/mat_poly_over_z/sample/
uniform.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 sampling algorithms for uniform distributions.
10
11use crate::{
12    error::MathError,
13    integer::{MatPolyOverZ, PolyOverZ, Z},
14    macros::seeded::seedable_function,
15    traits::{MatrixDimensions, MatrixSetEntry},
16    utils::index::evaluate_index,
17    utils::sample::uniform::SamplerRng,
18};
19use std::fmt::Display;
20
21impl MatPolyOverZ {
22    seedable_function!(
23        /// Outputs a [`MatPolyOverZ`] instance with polynomials as entries,
24        /// whose coefficients were chosen uniform at random in `[lower_bound, upper_bound)`.
25        ///
26        /// The internally used uniform at random chosen bytes are generated
27        #[unseeded]
28        /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is
29        #[seeded]
30        /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is
31        #[optionally_seeded]
32        /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng)
33        #[optionally_seeded]
34        /// if `seed` is `None`. Both use ChaCha12 and are
35        /// considered cryptographically secure.
36        ///
37        /// Parameters:
38        /// - `num_rows`: specifies the number of rows the new matrix should have
39        /// - `num_cols`: specifies the number of columns the new matrix should have
40        /// - `max_degree`: specifies the maximum length of all polynomials in the matrix,
41        ///   i.e. the maximum number of coefficients any polynomial in the matrix can have
42        /// - `lower_bound`: specifies the included lower bound of the
43        ///   interval over which is sampled
44        /// - `upper_bound`: specifies the excluded upper bound of the
45        ///   interval over which is sampled
46        #[seeded]
47        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
48        #[optionally_seeded]
49        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
50        #[optionally_seeded]
51        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
52        ///
53        /// Returns a new [`MatPolyOverZ`] instance with polynomials as entries,
54        /// whose coefficients were chosen uniformly at random in
55        /// `[lower_bound, upper_bound)` or a [`MathError`]
56        /// if the interval was chosen too small or the `max_degree` of the polynomials
57        /// is negative or too large to fit into [`i64`].
58        ///
59        /// # Examples
60        /// ```
61        /// use qfall_math::integer::MatPolyOverZ;
62        ///
63        #[unseeded]
64        /// let matrix = MatPolyOverZ::sample_uniform(3, 3, 5, 17, 26).unwrap();
65        #[seeded]
66        /// let matrix = MatPolyOverZ::sample_uniform_seeded(3, 3, 5, 17, 26, [42; 32]).unwrap();
67        /// ```
68        ///
69        /// # Errors and Failures
70        /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
71        ///   if the given `upper_bound` isn't at least larger than `lower_bound`.
72        /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if
73        ///   the `max_degree` is negative or it does not fit into an [`i64`].
74        ///
75        /// # Panics ...
76        /// - if the provided number of rows and columns are not suited to create a matrix.
77        ///   For further information see [`MatPolyOverZ::new`].
78        pub(crate) fn sample_uniform(
79            num_rows: impl TryInto<i64> + Display,
80            num_cols: impl TryInto<i64> + Display,
81            max_degree: impl TryInto<i64> + Display,
82            lower_bound: impl Into<Z>,
83            upper_bound: impl Into<Z>,
84            seed: Option<[u8; 32]>,
85        ) -> Result<Self, MathError> {
86            // derives an independent seed for every call of a seedable function below,
87            // see the documentation of `seedable_function` in `src/macros/seeded.rs`
88            let mut rng = SamplerRng::new(seed);
89
90            let lower_bound: Z = lower_bound.into();
91            let upper_bound: Z = upper_bound.into();
92            let max_degree = evaluate_index(max_degree)?;
93            let mut matrix = MatPolyOverZ::new(num_rows, num_cols);
94
95            for row in 0..matrix.get_num_rows() {
96                for col in 0..matrix.get_num_columns() {
97                    let sample = PolyOverZ::sample_uniform_optionally_seeded(
98                        max_degree,
99                        &lower_bound,
100                        &upper_bound,
101                        rng.derive_seed(),
102                    )?;
103                    unsafe { matrix.set_entry_unchecked(row, col, sample) };
104                }
105            }
106
107            Ok(matrix)
108        }
109    );
110}
111
112#[cfg(test)]
113mod test_sample_uniform {
114    use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry};
115    use crate::utils::sample::test_seed;
116    use crate::{
117        integer::{MatPolyOverZ, Z},
118        integer_mod_q::Modulus,
119    };
120
121    /// Checks whether the boundaries of the interval are kept for small intervals.
122    #[test]
123    fn boundaries_kept_small() {
124        for seeded in [false, true] {
125            let lower_bound = Z::from(17);
126            let upper_bound = Z::from(32);
127            for _ in 0..32 {
128                let matrix = MatPolyOverZ::sample_uniform_optionally_seeded(
129                    1,
130                    1,
131                    0,
132                    &lower_bound,
133                    &upper_bound,
134                    seeded.then(test_seed),
135                )
136                .unwrap();
137                let sample = matrix.get_entry(0, 0).unwrap();
138                let coeff = sample.get_coeff(0).unwrap();
139
140                assert!(lower_bound <= coeff);
141                assert!(coeff < upper_bound);
142            }
143        }
144    }
145
146    /// Checks whether the boundaries of the interval are kept for large intervals.
147    #[test]
148    fn boundaries_kept_large() {
149        for seeded in [false, true] {
150            let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX);
151            let upper_bound = Z::from(i64::MIN);
152            for _ in 0..256 {
153                let matrix = MatPolyOverZ::sample_uniform_optionally_seeded(
154                    1,
155                    1,
156                    0,
157                    &lower_bound,
158                    &upper_bound,
159                    seeded.then(test_seed),
160                )
161                .unwrap();
162                let sample = matrix.get_entry(0, 0).unwrap();
163                let coeff = sample.get_coeff(0).unwrap();
164
165                assert!(lower_bound <= coeff);
166                assert!(coeff < upper_bound);
167            }
168        }
169    }
170
171    /// Checks whether the number of coefficients is correct.
172    #[test]
173    fn nr_coeffs() {
174        for seeded in [false, true] {
175            let degrees = [1, 3, 7, 15, 32, 120];
176            for degree in degrees {
177                let matrix = MatPolyOverZ::sample_uniform_optionally_seeded(
178                    1,
179                    1,
180                    degree,
181                    1,
182                    15,
183                    seeded.then(test_seed),
184                )
185                .unwrap();
186                let poly = matrix.get_entry(0, 0).unwrap();
187
188                assert_eq!(degree, poly.get_degree());
189            }
190        }
191    }
192
193    /// Checks whether matrices with at least one dimension chosen smaller than `1`
194    /// or too large for an [`i64`] results in an error.
195    #[test]
196    fn false_size() {
197        for seeded in [false, true] {
198            let result = std::panic::catch_unwind(|| {
199                let lower_bound = Z::from(-15);
200                let upper_bound = Z::from(15);
201
202                let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
203                    0,
204                    3,
205                    1,
206                    &lower_bound,
207                    &upper_bound,
208                    seeded.then(test_seed),
209                );
210            });
211            assert!(result.is_err());
212        }
213    }
214
215    /// Checks whether providing an invalid interval results in an error.
216    #[test]
217    fn invalid_interval() {
218        for seeded in [false, true] {
219            let lb_0 = Z::from(i64::MIN);
220            let lb_1 = Z::from(i64::MIN);
221            let lb_2 = Z::ZERO;
222            let upper_bound = Z::from(i64::MIN);
223
224            let mat_0 = MatPolyOverZ::sample_uniform_optionally_seeded(
225                3,
226                3,
227                0,
228                &lb_0,
229                &upper_bound,
230                seeded.then(test_seed),
231            );
232            let mat_1 = MatPolyOverZ::sample_uniform_optionally_seeded(
233                4,
234                1,
235                0,
236                &lb_1,
237                &upper_bound,
238                seeded.then(test_seed),
239            );
240            let mat_2 = MatPolyOverZ::sample_uniform_optionally_seeded(
241                1,
242                5,
243                0,
244                &lb_2,
245                &upper_bound,
246                seeded.then(test_seed),
247            );
248
249            assert!(mat_0.is_err());
250            assert!(mat_1.is_err());
251            assert!(mat_2.is_err());
252        }
253    }
254
255    /// Checks whether providing a length smaller than `0` results in an error.
256    #[test]
257    fn invalid_max_degree() {
258        for seeded in [false, true] {
259            let lower_bound = Z::from(0);
260            let upper_bound = Z::from(15);
261
262            let res_0 = MatPolyOverZ::sample_uniform_optionally_seeded(
263                1,
264                1,
265                -1,
266                &lower_bound,
267                &upper_bound,
268                seeded.then(test_seed),
269            );
270            let res_1 = MatPolyOverZ::sample_uniform_optionally_seeded(
271                1,
272                1,
273                i64::MIN,
274                &lower_bound,
275                &upper_bound,
276                seeded.then(test_seed),
277            );
278
279            assert!(res_0.is_err());
280            assert!(res_1.is_err());
281        }
282    }
283
284    /// Checks whether `sample_uniform` is available for all types
285    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
286    #[test]
287    fn availability() {
288        for seeded in [false, true] {
289            let modulus = Modulus::from(7);
290            let z = Z::from(7);
291
292            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
293                1,
294                1,
295                0u8,
296                0u16,
297                7u8,
298                seeded.then(test_seed),
299            );
300            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
301                1,
302                1,
303                0u16,
304                0u32,
305                7u16,
306                seeded.then(test_seed),
307            );
308            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
309                1,
310                1,
311                0u32,
312                0u64,
313                7u32,
314                seeded.then(test_seed),
315            );
316            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
317                1,
318                1,
319                0u64,
320                0i8,
321                7u64,
322                seeded.then(test_seed),
323            );
324            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
325                1,
326                1,
327                0i8,
328                0i16,
329                7i8,
330                seeded.then(test_seed),
331            );
332            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
333                1,
334                1,
335                0i16,
336                0i32,
337                7i16,
338                seeded.then(test_seed),
339            );
340            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
341                1,
342                1,
343                0i32,
344                0i64,
345                7i32,
346                seeded.then(test_seed),
347            );
348            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
349                1,
350                1,
351                0i64,
352                &Z::ZERO,
353                7i64,
354                seeded.then(test_seed),
355            );
356            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
357                1,
358                1,
359                0,
360                0u8,
361                &modulus,
362                seeded.then(test_seed),
363            );
364            let _ = MatPolyOverZ::sample_uniform_optionally_seeded(
365                1,
366                1,
367                Z::ZERO,
368                0,
369                &z,
370                seeded.then(test_seed),
371            );
372
373            // explicitly checks the existence of the unseeded and the seeded function
374            if seeded {
375                let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u8, 0u16, 7u8, test_seed());
376            } else {
377                let _ = MatPolyOverZ::sample_uniform(1, 1, 0u8, 0u16, 7u8);
378            }
379        }
380    }
381
382    /// Checks whether the size of uniformly random sampled matrices
383    /// fits the specified dimensions.
384    #[test]
385    fn matrix_size() {
386        for seeded in [false, true] {
387            let lower_bound = Z::from(-15);
388            let upper_bound = Z::from(15);
389
390            let mat_0 = MatPolyOverZ::sample_uniform_optionally_seeded(
391                3,
392                3,
393                0,
394                &lower_bound,
395                &upper_bound,
396                seeded.then(test_seed),
397            )
398            .unwrap();
399            let mat_1 = MatPolyOverZ::sample_uniform_optionally_seeded(
400                4,
401                1,
402                0,
403                &lower_bound,
404                &upper_bound,
405                seeded.then(test_seed),
406            )
407            .unwrap();
408            let mat_2 = MatPolyOverZ::sample_uniform_optionally_seeded(
409                1,
410                5,
411                0,
412                &lower_bound,
413                &upper_bound,
414                seeded.then(test_seed),
415            )
416            .unwrap();
417            let mat_3 = MatPolyOverZ::sample_uniform_optionally_seeded(
418                15,
419                20,
420                0,
421                &lower_bound,
422                &upper_bound,
423                seeded.then(test_seed),
424            )
425            .unwrap();
426
427            assert_eq!(3, mat_0.get_num_rows());
428            assert_eq!(3, mat_0.get_num_columns());
429            assert_eq!(4, mat_1.get_num_rows());
430            assert_eq!(1, mat_1.get_num_columns());
431            assert_eq!(1, mat_2.get_num_rows());
432            assert_eq!(5, mat_2.get_num_columns());
433            assert_eq!(15, mat_3.get_num_rows());
434            assert_eq!(20, mat_3.get_num_columns());
435        }
436    }
437
438    /// Checks whether the same seed results in the same sample.
439    #[test]
440    fn same_seed_same_sample() {
441        use crate::integer::MatPolyOverZ;
442        let sample_0 = MatPolyOverZ::sample_uniform_seeded(3, 3, 5, 17, 26, [42; 32]).unwrap();
443        let sample_1 = MatPolyOverZ::sample_uniform_seeded(3, 3, 5, 17, 26, [42; 32]).unwrap();
444
445        assert_eq!(sample_0, sample_1);
446    }
447
448    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
449    #[test]
450    fn known_answer() {
451        use crate::integer::MatPolyOverZ;
452        let sample = MatPolyOverZ::sample_uniform_seeded(3, 3, 5, 17, 26, [42; 32]).unwrap();
453
454        assert_eq!(
455            "[[6  18 19 20 20 18 22, 6  18 25 25 21 20 19, 6  20 20 23 22 22 24],[6  23 19 22 17 23 23, 6  19 20 19 24 21 22, 6  17 22 23 24 25 25],[6  22 24 24 17 21 24, 6  20 17 25 19 17 20, 6  24 18 22 17 24 18]]",
456            sample.to_string()
457        );
458    }
459}