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}