qfall_math/integer/mat_z/sample/
binomial.rs

1// Copyright © 2023 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    rational::Q,
16    traits::{MatrixDimensions, MatrixSetEntry},
17    utils::sample::binomial::BinomialSampler,
18};
19use std::fmt::Display;
20
21impl MatZ {
22    /// Outputs a [`MatZ`] instance with entries chosen according to the binomial
23    /// distribution parameterized by `n` and `p`.
24    ///
25    /// Parameters:
26    /// - `num_rows`: specifies the number of rows the new matrix should have
27    /// - `num_cols`: specifies the number of columns the new matrix should have
28    /// - `n`: specifies the number of trials
29    /// - `p`: specifies the probability of success
30    ///
31    /// Returns a new [`MatZ`] instance with entries chosen
32    /// according to the binomial distribution or a [`MathError`]
33    /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`],
34    /// or the dimensions of the matrix were chosen too small.
35    ///
36    /// # Examples
37    /// ```
38    /// use qfall_math::integer::MatZ;
39    ///
40    /// let sample = MatZ::sample_binomial(2, 2, 5, 0.5).unwrap();
41    /// ```
42    ///
43    /// # Errors and Failures
44    /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
45    ///   if `n < 0`.
46    /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
47    ///   if `p ∉ (0,1)`.
48    /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError)
49    ///   if `n` does not fit into an [`i64`].
50    ///
51    /// # Panics ...
52    /// - if the provided number of rows and columns are not suited to create a matrix.
53    ///   For further information see [`MatZ::new`].
54    pub fn sample_binomial(
55        num_rows: impl TryInto<i64> + Display,
56        num_cols: impl TryInto<i64> + Display,
57        n: impl Into<Z>,
58        p: impl Into<Q>,
59    ) -> Result<Self, MathError> {
60        Self::sample_binomial_with_offset(num_rows, num_cols, 0, n, p)
61    }
62
63    /// Outputs a [`MatZ`] instance with entries chosen according to the binomial
64    /// distribution parameterized by `n` and `p` with given `offset`.
65    ///
66    /// Parameters:
67    /// - `num_rows`: specifies the number of rows the new matrix should have
68    /// - `num_cols`: specifies the number of columns the new matrix should have
69    /// - `offset`: specifies an offset applied to each sample
70    ///   collected from the binomial distribution
71    /// - `n`: specifies the number of trials
72    /// - `p`: specifies the probability of success
73    ///
74    /// Returns a new [`MatZ`] instance with entries chosen
75    /// according to the binomial distribution or a [`MathError`]
76    /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`],
77    /// or the dimensions of the matrix were chosen too small.
78    ///
79    /// # Examples
80    /// ```
81    /// use qfall_math::integer::MatZ;
82    ///
83    /// let sample = MatZ::sample_binomial_with_offset(2, 2, -1, 2, 0.5).unwrap();
84    /// ```
85    ///
86    /// # Errors and Failures
87    /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
88    ///   if `n < 0`.
89    /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
90    ///   if `p ∉ (0,1)`.
91    /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError)
92    ///   if `n` does not fit into an [`i64`].
93    ///
94    /// # Panics ...
95    /// - if the provided number of rows and columns are not suited to create a matrix.
96    ///   For further information see [`MatZ::new`].
97    pub fn sample_binomial_with_offset(
98        num_rows: impl TryInto<i64> + Display,
99        num_cols: impl TryInto<i64> + Display,
100        offset: impl Into<Z>,
101        n: impl Into<Z>,
102        p: impl Into<Q>,
103    ) -> Result<Self, MathError> {
104        let offset: Z = offset.into();
105        let mut bin_sampler = BinomialSampler::init(n, p)?;
106        let mut matrix = MatZ::new(num_rows, num_cols);
107
108        for row in 0..matrix.get_num_rows() {
109            for col in 0..matrix.get_num_columns() {
110                let mut sample = bin_sampler.sample();
111                sample += &offset;
112                unsafe { matrix.set_entry_unchecked(row, col, sample) };
113            }
114        }
115
116        Ok(matrix)
117    }
118}
119
120#[cfg(test)]
121mod test_sample_binomial {
122    use super::{MatZ, Q, Z};
123    use crate::traits::{MatrixDimensions, MatrixGetEntry};
124
125    // As all major tests regarding an appropriate binomial distribution,
126    // whether the correct interval is kept, and if the errors are thrown correctly,
127    // are performed in the `utils` module, we omit these tests here.
128
129    /// Checks whether the boundaries of the interval are kept for small intervals.
130    #[test]
131    fn boundaries_kept_small() {
132        for _ in 0..8 {
133            let matrix = MatZ::sample_binomial(1, 1, 2, 0.5).unwrap();
134            let sample = matrix.get_entry(0, 0).unwrap();
135            assert!(Z::ZERO <= sample);
136            assert!(sample <= 2);
137        }
138    }
139
140    /// Checks whether matrices with at least one dimension chosen smaller than `1`
141    /// or too big for an [`i64`] results in an error.
142    #[should_panic]
143    #[test]
144    fn false_size() {
145        let _ = MatZ::sample_binomial(0, 3, 1, 0.5);
146    }
147
148    /// Checks whether `sample_binomial` is available for all types
149    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
150    /// and [`Into<Q>`], i.e. u8, u16, i8, i16, f32, f64, ...
151    #[test]
152    fn availability() {
153        let _ = MatZ::sample_binomial(1, 1, 1u16, 7u8);
154        let _ = MatZ::sample_binomial(1, 1, 1u32, 7u16);
155        let _ = MatZ::sample_binomial(1, 1, 1u64, 7u32);
156        let _ = MatZ::sample_binomial(1, 1, 1i8, 7u64);
157        let _ = MatZ::sample_binomial(1, 1, 1i16, 7i8);
158        let _ = MatZ::sample_binomial(1, 1, 1i32, 7i16);
159        let _ = MatZ::sample_binomial(1, 1, 1i64, 7i32);
160        let _ = MatZ::sample_binomial(1, 1, Z::ONE, 7i64);
161        let _ = MatZ::sample_binomial(1, 1, 1u8, 0.5f32);
162        let _ = MatZ::sample_binomial(1, 1, 1u8, 0.5f64);
163        let _ = MatZ::sample_binomial(1, 1, 1, Q::from((1, 2)));
164    }
165
166    /// Checks whether the size of uniformly random sampled matrices
167    /// fits the specified dimensions.
168    #[test]
169    fn matrix_size() {
170        let mat_0 = MatZ::sample_binomial(3, 3, 1, 0.5).unwrap();
171        let mat_1 = MatZ::sample_binomial(4, 1, 1, 0.5).unwrap();
172        let mat_2 = MatZ::sample_binomial(1, 5, 1, 0.5).unwrap();
173        let mat_3 = MatZ::sample_binomial(15, 20, 1, 0.5).unwrap();
174
175        assert_eq!(3, mat_0.get_num_rows());
176        assert_eq!(3, mat_0.get_num_columns());
177        assert_eq!(4, mat_1.get_num_rows());
178        assert_eq!(1, mat_1.get_num_columns());
179        assert_eq!(1, mat_2.get_num_rows());
180        assert_eq!(5, mat_2.get_num_columns());
181        assert_eq!(15, mat_3.get_num_rows());
182        assert_eq!(20, mat_3.get_num_columns());
183    }
184}
185
186#[cfg(test)]
187mod test_sample_binomial_with_offset {
188    use super::{MatZ, Q, Z};
189    use crate::traits::{MatrixDimensions, MatrixGetEntry};
190
191    // As all major tests regarding an appropriate binomial distribution,
192    // whether the correct interval is kept, and if the errors are thrown correctly,
193    // are performed in the `utils` module, we omit these tests here.
194
195    /// Checks whether the boundaries of the interval are kept for small intervals.
196    #[test]
197    fn boundaries_kept_small() {
198        for _ in 0..8 {
199            let matrix = MatZ::sample_binomial_with_offset(1, 1, -1, 2, 0.5).unwrap();
200            let sample = matrix.get_entry(0, 0).unwrap();
201            assert!(Z::MINUS_ONE <= sample);
202            assert!(sample <= Z::ONE);
203        }
204    }
205
206    /// Checks whether matrices with at least one dimension chosen smaller than `1`
207    /// or too big for an [`i64`] results in an error.
208    #[should_panic]
209    #[test]
210    fn false_size() {
211        let _ = MatZ::sample_binomial_with_offset(0, 3, 0, 1, 0.5);
212    }
213
214    /// Checks whether `sample_binomial_with_offset` is available for all types
215    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
216    /// and [`Into<Q>`], i.e. u8, u16, i8, i16, f32, f64, ...
217    #[test]
218    fn availability() {
219        let _ = MatZ::sample_binomial_with_offset(1, 1, 0u8, 1u16, 7u8);
220        let _ = MatZ::sample_binomial_with_offset(1, 1, 0u16, 1u32, 7u16);
221        let _ = MatZ::sample_binomial_with_offset(1, 1, 0u32, 1u64, 7u32);
222        let _ = MatZ::sample_binomial_with_offset(1, 1, 0u64, 1i8, 7u64);
223        let _ = MatZ::sample_binomial_with_offset(1, 1, 0i8, 1i16, 7i8);
224        let _ = MatZ::sample_binomial_with_offset(1, 1, 0i16, 1i32, 7i16);
225        let _ = MatZ::sample_binomial_with_offset(1, 1, 0i64, 1i64, 7i32);
226        let _ = MatZ::sample_binomial_with_offset(1, 1, 0, Z::ONE, 7i64);
227        let _ = MatZ::sample_binomial_with_offset(1, 1, 0, 1u8, 0.5f32);
228        let _ = MatZ::sample_binomial_with_offset(1, 1, 0, 1u8, 0.5f64);
229        let _ = MatZ::sample_binomial_with_offset(1, 1, 0, 1, Q::from((1, 2)));
230    }
231
232    /// Checks whether the size of uniformly random sampled matrices
233    /// fits the specified dimensions.
234    #[test]
235    fn matrix_size() {
236        let mat_0 = MatZ::sample_binomial_with_offset(3, 3, 0, 1, 0.5).unwrap();
237        let mat_1 = MatZ::sample_binomial_with_offset(4, 1, 0, 1, 0.5).unwrap();
238        let mat_2 = MatZ::sample_binomial_with_offset(1, 5, 0, 1, 0.5).unwrap();
239        let mat_3 = MatZ::sample_binomial_with_offset(15, 20, 0, 1, 0.5).unwrap();
240
241        assert_eq!(3, mat_0.get_num_rows());
242        assert_eq!(3, mat_0.get_num_columns());
243        assert_eq!(4, mat_1.get_num_rows());
244        assert_eq!(1, mat_1.get_num_columns());
245        assert_eq!(1, mat_2.get_num_rows());
246        assert_eq!(5, mat_2.get_num_columns());
247        assert_eq!(15, mat_3.get_num_rows());
248        assert_eq!(20, mat_3.get_num_columns());
249    }
250}