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