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}