Skip to main content

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