qfall_math/integer/z/sample/discrete_gauss.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 discrete Gaussian distribution.
10
11use crate::{
12 error::MathError,
13 integer::Z,
14 rational::Q,
15 utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT},
16};
17
18impl Z {
19 /// Chooses a [`Z`] instance according to the discrete Gaussian distribution
20 /// in `[center - ⌈6 * s⌉ , center + ⌊6 * s⌋ ]`.
21 ///
22 /// This function samples discrete Gaussians according to the definition of
23 /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35).
24 ///
25 /// Parameters:
26 /// - `n`: specifies the range from which is sampled
27 /// - `center`: specifies the position of the center with peak probability
28 /// - `s`: specifies the Gaussian parameter, which is proportional
29 /// to the standard deviation `sigma * sqrt(2 * pi) = s`
30 ///
31 /// Returns new [`Z`] sample chosen according to the specified discrete Gaussian
32 /// distribution or a [`MathError`] if the specified parameters were not chosen
33 /// appropriately, i.e. `s < 0`.
34 ///
35 /// # Examples
36 /// ```
37 /// use qfall_math::integer::Z;
38 ///
39 /// let sample = Z::sample_discrete_gauss(0, 1).unwrap();
40 /// ```
41 ///
42 /// # Errors and Failures
43 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
44 /// if `s < 0`.
45 ///
46 /// This function implements SampleZ according to:
47 /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
48 /// Trapdoors for hard lattices and new cryptographic constructions.
49 /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
50 /// <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
51 pub fn sample_discrete_gauss(center: impl Into<Q>, s: impl Into<Q>) -> Result<Self, MathError> {
52 let center: Q = center.into();
53 let s: Q = s.into();
54
55 let mut dgis = DiscreteGaussianIntegerSampler::init(
56 ¢er,
57 &s,
58 unsafe { TAILCUT },
59 LookupTableSetting::NoLookup,
60 )?;
61
62 Ok(dgis.sample_z())
63 }
64}
65
66#[cfg(test)]
67mod test_sample_discrete_gauss {
68 use crate::{integer::Z, rational::Q};
69
70 // Appropriate interval and invalid inputs were tested in
71 // utils::sample::discrete_gauss::test_sample_z and are thus omitted here.
72
73 /// Checks whether `sample_discrete_gauss` is available for all types
74 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
75 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
76 #[test]
77 fn availability() {
78 let z = Z::from(2);
79 let q = Q::from(2);
80
81 let _ = Z::sample_discrete_gauss(7u8, 1u16);
82 let _ = Z::sample_discrete_gauss(7u16, 1u8);
83 let _ = Z::sample_discrete_gauss(7u32, 1u32);
84 let _ = Z::sample_discrete_gauss(7u64, 1u64);
85 let _ = Z::sample_discrete_gauss(7i8, 1i64);
86 let _ = Z::sample_discrete_gauss(7i16, 1i32);
87 let _ = Z::sample_discrete_gauss(7i32, 1i16);
88 let _ = Z::sample_discrete_gauss(7i64, 1i8);
89 let _ = Z::sample_discrete_gauss(&q, 1i64);
90 let _ = Z::sample_discrete_gauss(0i8, &z);
91 let _ = Z::sample_discrete_gauss(&z, &q);
92 let _ = Z::sample_discrete_gauss(1f32, 1f64);
93 let _ = Z::sample_discrete_gauss(1f64, 1f32);
94 }
95
96 /// Roughly checks the collected samples are distributed
97 /// according to the discrete Gaussian distribution.
98 ///
99 /// This test could possibly fail for a truly uniform distribution
100 /// with probability smaller than 1/1000.
101 #[test]
102 fn distribution() {
103 let mut counts = [0; 20];
104 // count sampled instances
105 for _ in 0..200 {
106 let sample = Z::sample_discrete_gauss(10, 2).unwrap();
107 let sample_int = i64::try_from(&sample).unwrap() as usize;
108 counts[sample_int] += 1;
109 }
110
111 let expl_text = String::from("This test can fail with probability close to 0.
112 It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution.
113 If this happens, rerun the tests several times and check whether this issue comes up again.");
114
115 // Check that the sampled occurrences roughly look
116 // like a discrete Gaussian distribution
117 assert!(counts[10] > 70, "{expl_text}");
118 assert!(counts[10] < 130, "{expl_text}");
119 assert!(counts[9] > 20, "{expl_text}");
120 assert!(counts[9] < 70, "{expl_text}");
121 assert!(counts[11] > 20, "{expl_text}");
122 assert!(counts[11] < 70, "{expl_text}");
123 assert!(counts[8] < 20, "{expl_text}");
124 assert!(counts[12] < 20, "{expl_text}");
125 for count in counts.iter().take(8) {
126 assert!(count < &10, "{expl_text}");
127 }
128 for count in counts.iter().skip(13) {
129 assert!(count < &10, "{expl_text}");
130 }
131 }
132}