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}