qfall_math/integer/poly_over_z/sample/binomial.rs
1// Copyright 2023 Jan 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 macros::seeded::seedable_function,
15 rational::Q,
16 traits::SetCoefficient,
17 utils::{index::evaluate_index, sample::binomial::BinomialSampler},
18};
19use std::fmt::Display;
20
21impl PolyOverZ {
22 seedable_function!(
23 /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and
24 /// coefficients chosen according to the binomial distribution
25 /// parameterized by `n` and `p`.
26 ///
27 /// Parameters:
28 /// - `max_degree`: specifies the length of the polynomial,
29 /// i.e. the number of coefficients
30 /// - `n`: specifies the number of trials
31 /// - `p`: specifies the probability of success
32 #[seeded]
33 /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
34 #[optionally_seeded]
35 /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
36 #[optionally_seeded]
37 /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
38 ///
39 /// Returns a fresh [`PolyOverZ`] instance with each value sampled
40 /// according to the binomial distribution or a [`MathError`]
41 /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`],
42 /// or `max_degree` is negative or does not into an [`i64`].
43 ///
44 /// # Examples
45 /// ```
46 /// use qfall_math::integer::PolyOverZ;
47 ///
48 #[unseeded]
49 /// let sample = PolyOverZ::sample_binomial(2, 2, 0.5).unwrap();
50 #[seeded]
51 /// let sample = PolyOverZ::sample_binomial_seeded(2, 2, 0.5, [42; 32]).unwrap();
52 /// ```
53 ///
54 /// # Errors and Failures
55 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
56 /// if `n < 0`.
57 /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
58 /// if `p ∉ (0,1)`.
59 /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError)
60 /// if `n` does not fit into an [`i64`].
61 /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if
62 /// the `max_degree` is negative or it does not fit into an [`i64`].
63 pub(crate) fn sample_binomial(
64 max_degree: impl TryInto<i64> + Display,
65 n: impl Into<Z>,
66 p: impl Into<Q>,
67 seed: Option<[u8; 32]>,
68 ) -> Result<Self, MathError> {
69 Self::sample_binomial_with_offset_optionally_seeded(max_degree, 0, n, p, seed)
70 }
71 );
72
73 seedable_function!(
74 /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and
75 /// coefficients chosen according to the binomial distribution
76 /// parameterized by `n` and `p` with given `offset`.
77 ///
78 /// Parameters:
79 /// - `max_degree`: specifies the length of the polynomial,
80 /// i.e. the number of coefficients
81 /// - `offset`: specifies an offset applied to each sample
82 /// collected from the binomial distribution
83 /// - `n`: specifies the number of trials
84 /// - `p`: specifies the probability of success
85 #[seeded]
86 /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
87 #[optionally_seeded]
88 /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
89 #[optionally_seeded]
90 /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
91 ///
92 /// Returns a fresh [`PolyOverZ`] instance with each value sampled
93 /// according to the binomial distribution or a [`MathError`]
94 /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`],
95 /// or `max_degree` is negative or does not into an [`i64`].
96 ///
97 /// # Examples
98 /// ```
99 /// use qfall_math::integer::PolyOverZ;
100 ///
101 #[unseeded]
102 /// let sample = PolyOverZ::sample_binomial_with_offset(2, -1, 2, 0.5).unwrap();
103 #[seeded]
104 /// let sample = PolyOverZ::sample_binomial_with_offset_seeded(2, -1, 2, 0.5, [42; 32]).unwrap();
105 /// ```
106 ///
107 /// # Errors and Failures
108 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
109 /// if `n < 0`.
110 /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
111 /// if `p ∉ (0,1)`.
112 /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError)
113 /// if `n` does not fit into an [`i64`].
114 /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if
115 /// the `max_degree` is negative or it does not fit into an [`i64`].
116 pub(crate) fn sample_binomial_with_offset(
117 max_degree: impl TryInto<i64> + Display,
118 offset: impl Into<Z>,
119 n: impl Into<Z>,
120 p: impl Into<Q>,
121 seed: Option<[u8; 32]>,
122 ) -> Result<Self, MathError> {
123 let max_degree = evaluate_index(max_degree)?;
124 let offset: Z = offset.into();
125
126 let mut poly_z = PolyOverZ::default();
127
128 let mut bin_sampler = BinomialSampler::init(n, p, seed)?;
129
130 for index in 0..=max_degree {
131 let mut sample = bin_sampler.sample();
132 sample += &offset;
133 unsafe { poly_z.set_coeff_unchecked(index, sample) };
134 }
135
136 Ok(poly_z)
137 }
138 );
139}
140
141#[cfg(test)]
142mod test_sample_binomial {
143 use super::{PolyOverZ, Q, Z};
144 use crate::traits::GetCoefficient;
145 use crate::utils::sample::test_seed;
146
147 // As all major tests regarding an appropriate binomial distribution,
148 // whether the correct interval is kept, and if the errors are thrown correctly,
149 // are performed in the `utils` module, we omit these tests here.
150
151 /// Checks whether the boundaries of the interval are kept.
152 #[test]
153 fn boundaries_kept() {
154 for seeded in [false, true] {
155 for _ in 0..8 {
156 let poly =
157 PolyOverZ::sample_binomial_optionally_seeded(0, 2, 0.5, seeded.then(test_seed))
158 .unwrap();
159 let sample = poly.get_coeff(0).unwrap();
160 assert!(Z::ZERO <= sample);
161 assert!(sample <= 2);
162 }
163 }
164 }
165
166 /// Checks whether the number of coefficients is correct.
167 #[test]
168 fn nr_coeffs() {
169 for seeded in [false, true] {
170 let degrees = [1, 3, 7, 15, 32, 120];
171 for degree in degrees {
172 let res = PolyOverZ::sample_binomial_optionally_seeded(
173 degree,
174 256,
175 0.99999,
176 seeded.then(test_seed),
177 )
178 .unwrap();
179
180 assert_eq!(
181 degree,
182 res.get_degree(),
183 "This test can fail with probability close to 0."
184 );
185 }
186 }
187 }
188
189 /// Checks whether providing a length smaller than `0` results in an error.
190 #[test]
191 fn invalid_max_degree() {
192 for seeded in [false, true] {
193 let res_0 =
194 PolyOverZ::sample_binomial_optionally_seeded(-1, 2, 0.5, seeded.then(test_seed));
195 let res_1 = PolyOverZ::sample_binomial_optionally_seeded(
196 i64::MIN,
197 2,
198 0.5,
199 seeded.then(test_seed),
200 );
201
202 assert!(res_0.is_err());
203 assert!(res_1.is_err());
204 }
205 }
206
207 /// Checks whether `sample_binomial` is available for all types
208 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
209 /// and [`Into<Q>`], i.e. u8, u16, i8, i16, f32, f64, ...
210 #[test]
211 fn availability() {
212 for seeded in [false, true] {
213 let _ =
214 PolyOverZ::sample_binomial_optionally_seeded(1, 1u16, 7u8, seeded.then(test_seed));
215 let _ =
216 PolyOverZ::sample_binomial_optionally_seeded(1, 1u32, 7u16, seeded.then(test_seed));
217 let _ =
218 PolyOverZ::sample_binomial_optionally_seeded(1, 1u64, 7u32, seeded.then(test_seed));
219 let _ =
220 PolyOverZ::sample_binomial_optionally_seeded(1, 1i8, 7u64, seeded.then(test_seed));
221 let _ =
222 PolyOverZ::sample_binomial_optionally_seeded(1, 1i16, 7i8, seeded.then(test_seed));
223 let _ =
224 PolyOverZ::sample_binomial_optionally_seeded(1, 1i32, 7i16, seeded.then(test_seed));
225 let _ =
226 PolyOverZ::sample_binomial_optionally_seeded(1, 1i64, 7i32, seeded.then(test_seed));
227 let _ = PolyOverZ::sample_binomial_optionally_seeded(
228 1,
229 Z::ONE,
230 7i64,
231 seeded.then(test_seed),
232 );
233 let _ = PolyOverZ::sample_binomial_optionally_seeded(
234 1,
235 1u8,
236 0.5f32,
237 seeded.then(test_seed),
238 );
239 let _ = PolyOverZ::sample_binomial_optionally_seeded(
240 1,
241 1u8,
242 0.5f64,
243 seeded.then(test_seed),
244 );
245 let _ = PolyOverZ::sample_binomial_optionally_seeded(
246 1,
247 1,
248 Q::from((1, 2)),
249 seeded.then(test_seed),
250 );
251
252 // explicitly checks the existence of the unseeded and the seeded function
253 if seeded {
254 let _ = PolyOverZ::sample_binomial_seeded(1, 1u16, 7u8, test_seed());
255 } else {
256 let _ = PolyOverZ::sample_binomial(1, 1u16, 7u8);
257 }
258 }
259 }
260
261 /// Checks whether the same seed results in the same sample.
262 #[test]
263 fn same_seed_same_sample() {
264 use crate::integer::PolyOverZ;
265 let sample_0 = PolyOverZ::sample_binomial_seeded(2, 2, 0.5, [42; 32]).unwrap();
266 let sample_1 = PolyOverZ::sample_binomial_seeded(2, 2, 0.5, [42; 32]).unwrap();
267
268 assert_eq!(sample_0, sample_1);
269 }
270
271 /// Checks whether the seeded variant outputs the known sample for a fixed seed.
272 #[test]
273 fn known_answer() {
274 use crate::integer::PolyOverZ;
275 let sample = PolyOverZ::sample_binomial_seeded(2, 2, 0.5, [42; 32]).unwrap();
276
277 assert_eq!("3 0 2 1", sample.to_string());
278 }
279}
280
281#[cfg(test)]
282mod test_sample_binomial_with_offset {
283 use super::{PolyOverZ, Q, Z};
284 use crate::traits::GetCoefficient;
285 use crate::utils::sample::test_seed;
286
287 // As all major tests regarding an appropriate binomial distribution,
288 // whether the correct interval is kept, and if the errors are thrown correctly,
289 // are performed in the `utils` module, we omit these tests here.
290
291 /// Checks whether the boundaries of the interval are kept.
292 #[test]
293 fn boundaries_kept() {
294 for seeded in [false, true] {
295 for _ in 0..8 {
296 let poly = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
297 0,
298 -1,
299 2,
300 0.5,
301 seeded.then(test_seed),
302 )
303 .unwrap();
304 let sample = poly.get_coeff(0).unwrap();
305 assert!(Z::MINUS_ONE <= sample);
306 assert!(sample <= Z::ONE);
307 }
308 }
309 }
310
311 /// Checks whether the number of coefficients is correct.
312 #[test]
313 fn nr_coeffs() {
314 for seeded in [false, true] {
315 let degrees = [1, 3, 7, 15, 32, 120];
316 for degree in degrees {
317 let res = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
318 degree,
319 1,
320 2,
321 0.5,
322 seeded.then(test_seed),
323 )
324 .unwrap();
325
326 assert_eq!(degree, res.get_degree());
327 }
328 }
329 }
330
331 /// Checks whether providing a length smaller than `0` results in an error.
332 #[test]
333 fn invalid_max_degree() {
334 for seeded in [false, true] {
335 let res_0 = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
336 -1,
337 -1,
338 2,
339 0.5,
340 seeded.then(test_seed),
341 );
342 let res_1 = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
343 i64::MIN,
344 -1,
345 2,
346 0.5,
347 seeded.then(test_seed),
348 );
349
350 assert!(res_0.is_err());
351 assert!(res_1.is_err());
352 }
353 }
354
355 /// Checks whether `sample_binomial_with_offset` is available for all types
356 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
357 /// and [`Into<Q>`], i.e. u8, u16, i8, i16, f32, f64, ...
358 #[test]
359 fn availability() {
360 for seeded in [false, true] {
361 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
362 1,
363 0,
364 1u16,
365 7u8,
366 seeded.then(test_seed),
367 );
368 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
369 1,
370 0,
371 1u32,
372 7u16,
373 seeded.then(test_seed),
374 );
375 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
376 1,
377 0,
378 1u64,
379 7u32,
380 seeded.then(test_seed),
381 );
382 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
383 1,
384 0,
385 1i8,
386 7u64,
387 seeded.then(test_seed),
388 );
389 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
390 1,
391 0,
392 1i16,
393 7i8,
394 seeded.then(test_seed),
395 );
396 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
397 1,
398 0,
399 1i32,
400 7i16,
401 seeded.then(test_seed),
402 );
403 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
404 1,
405 0,
406 1i64,
407 7i32,
408 seeded.then(test_seed),
409 );
410 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
411 1,
412 0,
413 Z::ONE,
414 7i64,
415 seeded.then(test_seed),
416 );
417 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
418 1,
419 0,
420 1u8,
421 0.5f32,
422 seeded.then(test_seed),
423 );
424 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
425 1,
426 0,
427 1u8,
428 0.5f64,
429 seeded.then(test_seed),
430 );
431 let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded(
432 1,
433 0,
434 1,
435 Q::from((1, 2)),
436 seeded.then(test_seed),
437 );
438
439 // explicitly checks the existence of the unseeded and the seeded function
440 if seeded {
441 let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u16, 7u8, test_seed());
442 } else {
443 let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u16, 7u8);
444 }
445 }
446 }
447
448 /// Checks whether the same seed results in the same sample.
449 #[test]
450 fn same_seed_same_sample() {
451 use crate::integer::PolyOverZ;
452 let sample_0 =
453 PolyOverZ::sample_binomial_with_offset_seeded(2, -1, 2, 0.5, [42; 32]).unwrap();
454 let sample_1 =
455 PolyOverZ::sample_binomial_with_offset_seeded(2, -1, 2, 0.5, [42; 32]).unwrap();
456
457 assert_eq!(sample_0, sample_1);
458 }
459
460 /// Checks whether the seeded variant outputs the known sample for a fixed seed.
461 #[test]
462 fn known_answer() {
463 use crate::integer::PolyOverZ;
464 let sample =
465 PolyOverZ::sample_binomial_with_offset_seeded(2, -1, 2, 0.5, [42; 32]).unwrap();
466
467 assert_eq!("2 -1 1", sample.to_string());
468 }
469}