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