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}