qfall_math/integer/poly_over_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 according to the binomial distribution.
10
11use crate::{
12 error::MathError,
13 integer::{PolyOverZ, Z},
14 rational::Q,
15 traits::SetCoefficient,
16 utils::{index::evaluate_index, sample::binomial::BinomialSampler},
17};
18use std::fmt::Display;
19
20impl PolyOverZ {
21 /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and
22 /// coefficients chosen according to the binomial distribution
23 /// parameterized by `n` and `p`.
24 ///
25 /// Parameters:
26 /// - `max_degree`: specifies the length of the polynomial,
27 /// i.e. the number of coefficients
28 /// - `n`: specifies the number of trials
29 /// - `p`: specifies the probability of success
30 ///
31 /// Returns a fresh [`PolyOverZ`] instance with each value sampled
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 `max_degree` is negative or does not into an [`i64`].
35 ///
36 /// # Examples
37 /// ```
38 /// use qfall_math::integer::PolyOverZ;
39 ///
40 /// let sample = PolyOverZ::sample_binomial(2, 2, 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 /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if
51 /// the `max_degree` is negative or it does not fit into an [`i64`].
52 pub fn sample_binomial(
53 max_degree: impl TryInto<i64> + Display,
54 n: impl Into<Z>,
55 p: impl Into<Q>,
56 ) -> Result<Self, MathError> {
57 Self::sample_binomial_with_offset(max_degree, 0, n, p)
58 }
59
60 /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and
61 /// coefficients chosen according to the binomial distribution
62 /// parameterized by `n` and `p` with given `offset`.
63 ///
64 /// Parameters:
65 /// - `max_degree`: specifies the length of the polynomial,
66 /// i.e. the number of coefficients
67 /// - `offset`: specifies an offset applied to each sample
68 /// collected from the binomial distribution
69 /// - `n`: specifies the number of trials
70 /// - `p`: specifies the probability of success
71 ///
72 /// Returns a fresh [`PolyOverZ`] instance with each value sampled
73 /// according to the binomial distribution or a [`MathError`]
74 /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`],
75 /// or `max_degree` is negative or does not into an [`i64`].
76 ///
77 /// # Examples
78 /// ```
79 /// use qfall_math::integer::PolyOverZ;
80 ///
81 /// let sample = PolyOverZ::sample_binomial_with_offset(2, -1, 2, 0.5).unwrap();
82 /// ```
83 ///
84 /// # Errors and Failures
85 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
86 /// if `n < 0`.
87 /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
88 /// if `p ∉ (0,1)`.
89 /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError)
90 /// if `n` does not fit into an [`i64`].
91 /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if
92 /// the `max_degree` is negative or it does not fit into an [`i64`].
93 pub fn sample_binomial_with_offset(
94 max_degree: impl TryInto<i64> + Display,
95 offset: impl Into<Z>,
96 n: impl Into<Z>,
97 p: impl Into<Q>,
98 ) -> Result<Self, MathError> {
99 let max_degree = evaluate_index(max_degree)?;
100 let offset: Z = offset.into();
101
102 let mut poly_z = PolyOverZ::default();
103
104 let mut bin_sampler = BinomialSampler::init(n, p)?;
105
106 for index in 0..=max_degree {
107 let mut sample = bin_sampler.sample();
108 sample += &offset;
109 unsafe { poly_z.set_coeff_unchecked(index, sample) };
110 }
111
112 Ok(poly_z)
113 }
114}
115
116#[cfg(test)]
117mod test_sample_binomial {
118 use super::{PolyOverZ, Q, Z};
119 use crate::traits::GetCoefficient;
120
121 // As all major tests regarding an appropriate binomial distribution,
122 // whether the correct interval is kept, and if the errors are thrown correctly,
123 // are performed in the `utils` module, we omit these tests here.
124
125 /// Checks whether the boundaries of the interval are kept.
126 #[test]
127 fn boundaries_kept() {
128 for _ in 0..8 {
129 let poly = PolyOverZ::sample_binomial(0, 2, 0.5).unwrap();
130 let sample = poly.get_coeff(0).unwrap();
131 assert!(Z::ZERO <= sample);
132 assert!(sample <= 2);
133 }
134 }
135
136 /// Checks whether the number of coefficients is correct.
137 #[test]
138 fn nr_coeffs() {
139 let degrees = [1, 3, 7, 15, 32, 120];
140 for degree in degrees {
141 let res = PolyOverZ::sample_binomial(degree, 256, 0.99999).unwrap();
142
143 assert_eq!(
144 degree,
145 res.get_degree(),
146 "This test can fail with probability close to 0."
147 );
148 }
149 }
150
151 /// Checks whether providing a length smaller than `0` results in an error.
152 #[test]
153 fn invalid_max_degree() {
154 let res_0 = PolyOverZ::sample_binomial(-1, 2, 0.5);
155 let res_1 = PolyOverZ::sample_binomial(i64::MIN, 2, 0.5);
156
157 assert!(res_0.is_err());
158 assert!(res_1.is_err());
159 }
160
161 /// Checks whether `sample_binomial` is available for all types
162 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
163 /// and [`Into<Q>`], i.e. u8, u16, i8, i16, f32, f64, ...
164 #[test]
165 fn availability() {
166 let _ = PolyOverZ::sample_binomial(1, 1u16, 7u8);
167 let _ = PolyOverZ::sample_binomial(1, 1u32, 7u16);
168 let _ = PolyOverZ::sample_binomial(1, 1u64, 7u32);
169 let _ = PolyOverZ::sample_binomial(1, 1i8, 7u64);
170 let _ = PolyOverZ::sample_binomial(1, 1i16, 7i8);
171 let _ = PolyOverZ::sample_binomial(1, 1i32, 7i16);
172 let _ = PolyOverZ::sample_binomial(1, 1i64, 7i32);
173 let _ = PolyOverZ::sample_binomial(1, Z::ONE, 7i64);
174 let _ = PolyOverZ::sample_binomial(1, 1u8, 0.5f32);
175 let _ = PolyOverZ::sample_binomial(1, 1u8, 0.5f64);
176 let _ = PolyOverZ::sample_binomial(1, 1, Q::from((1, 2)));
177 }
178}
179
180#[cfg(test)]
181mod test_sample_binomial_with_offset {
182 use super::{PolyOverZ, Q, Z};
183 use crate::traits::GetCoefficient;
184
185 // As all major tests regarding an appropriate binomial distribution,
186 // whether the correct interval is kept, and if the errors are thrown correctly,
187 // are performed in the `utils` module, we omit these tests here.
188
189 /// Checks whether the boundaries of the interval are kept.
190 #[test]
191 fn boundaries_kept() {
192 for _ in 0..8 {
193 let poly = PolyOverZ::sample_binomial_with_offset(0, -1, 2, 0.5).unwrap();
194 let sample = poly.get_coeff(0).unwrap();
195 assert!(Z::MINUS_ONE <= sample);
196 assert!(sample <= Z::ONE);
197 }
198 }
199
200 /// Checks whether the number of coefficients is correct.
201 #[test]
202 fn nr_coeffs() {
203 let degrees = [1, 3, 7, 15, 32, 120];
204 for degree in degrees {
205 let res = PolyOverZ::sample_binomial_with_offset(degree, 1, 2, 0.5).unwrap();
206
207 assert_eq!(degree, res.get_degree());
208 }
209 }
210
211 /// Checks whether providing a length smaller than `0` results in an error.
212 #[test]
213 fn invalid_max_degree() {
214 let res_0 = PolyOverZ::sample_binomial_with_offset(-1, -1, 2, 0.5);
215 let res_1 = PolyOverZ::sample_binomial_with_offset(i64::MIN, -1, 2, 0.5);
216
217 assert!(res_0.is_err());
218 assert!(res_1.is_err());
219 }
220
221 /// Checks whether `sample_binomial_with_offset` is available for all types
222 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
223 /// and [`Into<Q>`], i.e. u8, u16, i8, i16, f32, f64, ...
224 #[test]
225 fn availability() {
226 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u16, 7u8);
227 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u32, 7u16);
228 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u64, 7u32);
229 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1i8, 7u64);
230 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1i16, 7i8);
231 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1i32, 7i16);
232 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1i64, 7i32);
233 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, Z::ONE, 7i64);
234 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u8, 0.5f32);
235 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u8, 0.5f64);
236 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1, Q::from((1, 2)));
237 }
238}