Skip to main content

qfall_math/integer/poly_over_z/sample/
discrete_gauss.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
10//! to the discrete Gaussian distribution.
11
12use crate::{
13    error::MathError,
14    integer::PolyOverZ,
15    macros::seeded::seedable_function,
16    rational::Q,
17    traits::SetCoefficient,
18    utils::{
19        index::evaluate_index,
20        sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT},
21    },
22};
23use std::fmt::Display;
24
25impl PolyOverZ {
26    seedable_function!(
27        /// Initializes a new [`PolyOverZ`] with maximum degree `max_degree`
28        /// and with each entry sampled independently according to the
29        /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss).
30        ///
31        /// Parameters:
32        /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have
33        /// - `center`: specifies the positions of the center with peak probability
34        /// - `s`: specifies the Gaussian parameter, which is proportional
35        ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
36        #[seeded]
37        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
38        #[optionally_seeded]
39        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
40        #[optionally_seeded]
41        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
42        ///
43        /// Returns a fresh [`PolyOverZ`] instance of maximum degree `max_degree`
44        /// with coefficients chosen independently according the discrete Gaussian distribution or
45        /// a [`MathError`] if `s < 0`.
46        ///
47        /// # Examples
48        /// ```
49        /// use qfall_math::integer::PolyOverZ;
50        ///
51        #[unseeded]
52        /// let sample = PolyOverZ::sample_discrete_gauss(2, 0, 1).unwrap();
53        #[seeded]
54        /// let sample = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
55        /// ```
56        ///
57        /// # Errors and Failures
58        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
59        ///   if `s < 0`.
60        ///
61        /// # Panics ...
62        /// - if `max_degree` is negative, or does not fit into an [`i64`].
63        pub(crate) fn sample_discrete_gauss(
64            max_degree: impl TryInto<i64> + Display,
65            center: impl Into<Q>,
66            s: impl Into<Q>,
67            seed: Option<[u8; 32]>,
68        ) -> Result<Self, MathError> {
69            let max_degree = evaluate_index(max_degree).unwrap();
70
71            let center = center.into();
72            let s = s.into();
73            let mut poly = PolyOverZ::default();
74
75            let mut dgis = DiscreteGaussianIntegerSampler::init(
76                &center,
77                &s,
78                unsafe { TAILCUT },
79                LookupTableSetting::FillOnTheFly,
80                seed,
81            )?;
82
83            for index in 0..=max_degree {
84                let sample = dgis.sample_z();
85                unsafe { poly.set_coeff_unchecked(index, &sample) };
86            }
87            Ok(poly)
88        }
89    );
90}
91
92#[cfg(test)]
93mod test_sample_discrete_gauss {
94    use crate::utils::sample::test_seed;
95    use crate::{integer::PolyOverZ, rational::Q};
96
97    /// Checks whether `sample_discrete_gauss` is available for all types
98    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
99    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
100    #[test]
101    fn availability() {
102        for seeded in [false, true] {
103            let center = Q::ZERO;
104            let s = Q::ONE;
105
106            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
107                1u8,
108                0f32,
109                1u8,
110                seeded.then(test_seed),
111            );
112            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
113                1u16,
114                0f64,
115                1u16,
116                seeded.then(test_seed),
117            );
118            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
119                1u32,
120                0f32,
121                1u32,
122                seeded.then(test_seed),
123            );
124            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
125                1u64,
126                0f64,
127                1u64,
128                seeded.then(test_seed),
129            );
130            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
131                1i8,
132                0f32,
133                1i8,
134                seeded.then(test_seed),
135            );
136            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
137                1i8,
138                0f32,
139                1i16,
140                seeded.then(test_seed),
141            );
142            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
143                1i16,
144                0f32,
145                1i32,
146                seeded.then(test_seed),
147            );
148            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
149                1i32,
150                0f64,
151                1i64,
152                seeded.then(test_seed),
153            );
154            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
155                1i64,
156                center,
157                s,
158                seeded.then(test_seed),
159            );
160            let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
161                1u8,
162                0f32,
163                1f64,
164                seeded.then(test_seed),
165            );
166
167            // explicitly checks the existence of the unseeded and the seeded function
168            if seeded {
169                let _ = PolyOverZ::sample_discrete_gauss_seeded(1u8, 0f32, 1u8, test_seed());
170            } else {
171                let _ = PolyOverZ::sample_discrete_gauss(1u8, 0f32, 1u8);
172            }
173        }
174    }
175
176    /// Checks whether the number of coefficients is correct.
177    #[test]
178    fn nr_coeffs() {
179        for seeded in [false, true] {
180            let degrees = [1, 3, 7, 15, 32, 120];
181            for degree in degrees {
182                let res = PolyOverZ::sample_discrete_gauss_optionally_seeded(
183                    degree,
184                    i64::MAX,
185                    1,
186                    seeded.then(test_seed),
187                )
188                .unwrap();
189
190                assert_eq!(
191                    res.get_degree(),
192                    degree,
193                    "Could fail with negligible probability."
194                );
195            }
196        }
197    }
198
199    /// Checks whether the maximum degree needs to be at least 0.
200    #[test]
201    fn invalid_max_degree() {
202        for seeded in [false, true] {
203            let result = std::panic::catch_unwind(|| {
204                let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded(
205                    -1,
206                    0,
207                    1,
208                    seeded.then(test_seed),
209                )
210                .unwrap();
211            });
212            assert!(result.is_err());
213        }
214    }
215
216    /// Checks whether the same seed results in the same sample.
217    #[test]
218    fn same_seed_same_sample() {
219        use crate::integer::PolyOverZ;
220        let sample_0 = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
221        let sample_1 = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
222
223        assert_eq!(sample_0, sample_1);
224    }
225
226    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
227    #[test]
228    fn known_answer() {
229        use crate::integer::PolyOverZ;
230        let sample = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap();
231
232        assert_eq!("1  1", sample.to_string());
233    }
234}