qfall_math/integer/mat_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::MatZ,
14    rational::{MatQ, Q},
15    traits::{MatrixDimensions, MatrixSetEntry},
16    utils::sample::discrete_gauss::{
17        DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT, sample_d,
18        sample_d_precomputed_gso,
19    },
20};
21use std::fmt::Display;
22
23impl MatZ {
24    /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry
25    /// sampled independently according to the discrete Gaussian distribution,
26    /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss).
27    ///
28    /// Parameters:
29    /// - `num_rows`: specifies the number of rows the new matrix should have
30    /// - `num_cols`: specifies the number of columns the new matrix should have
31    /// - `center`: specifies the positions of the center with peak probability
32    /// - `s`: specifies the Gaussian parameter, which is proportional
33    ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
34    ///
35    /// Returns a matrix with each entry sampled independently from the
36    /// specified discrete Gaussian distribution or an error if `s < 0`.
37    ///
38    /// # Examples
39    /// ```
40    /// use qfall_math::integer::MatZ;
41    ///
42    /// let sample = MatZ::sample_discrete_gauss(3, 1, 0, 1.25f32).unwrap();
43    /// ```
44    ///
45    /// # Errors and Failures
46    /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
47    ///   if `s < 0`.
48    ///
49    /// # Panics ...
50    /// - if the provided number of rows and columns are not suited to create a matrix.
51    ///   For further information see [`MatZ::new`].
52    pub fn sample_discrete_gauss(
53        num_rows: impl TryInto<i64> + Display,
54        num_cols: impl TryInto<i64> + Display,
55        center: impl Into<Q>,
56        s: impl Into<Q>,
57    ) -> Result<MatZ, MathError> {
58        let mut out = Self::new(num_rows, num_cols);
59
60        let mut dgis = DiscreteGaussianIntegerSampler::init(
61            center,
62            s,
63            unsafe { TAILCUT },
64            LookupTableSetting::FillOnTheFly,
65        )?;
66
67        for row in 0..out.get_num_rows() {
68            for col in 0..out.get_num_columns() {
69                let sample = dgis.sample_z();
70                unsafe { out.set_entry_unchecked(row, col, sample) };
71            }
72        }
73
74        Ok(out)
75    }
76
77    /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
78    ///
79    /// We do not check whether `basis` is actually a basis. Hence, the callee is
80    /// responsible for making sure that `basis` provides a suitable basis.
81    ///
82    /// Parameters:
83    /// - `basis`: specifies a basis for the lattice from which is sampled
84    /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples
85    /// - `center`: specifies the positions of the center with peak probability
86    /// - `s`: specifies the Gaussian parameter, which is proportional
87    ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
88    ///
89    /// Returns a lattice vector sampled according to the discrete Gaussian distribution
90    /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ,
91    /// or if `center` is not a column vector.
92    ///
93    /// # Examples
94    /// ```
95    /// use qfall_math::{integer::{MatZ, Z}, rational::{MatQ, Q}};
96    /// let basis = MatZ::identity(5, 5);
97    /// let center = MatQ::new(5, 1);
98    ///
99    /// let sample = MatZ::sample_d(&basis, &center, 1.25f32).unwrap();
100    /// ```
101    ///
102    /// # Errors and Failures
103    /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
104    ///   if `s < 0`.
105    /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
106    ///   if the number of rows of the `basis` and `center` differ.
107    /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
108    ///   if `center` is not a column vector.
109    ///
110    /// This function implements SampleD according to:
111    /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
112    ///   Trapdoors for hard lattices and new cryptographic constructions.
113    ///   In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
114    ///   <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
115    pub fn sample_d(basis: &MatZ, center: &MatQ, s: impl Into<Q>) -> Result<Self, MathError> {
116        let s: Q = s.into();
117
118        sample_d(basis, center, &s)
119    }
120
121    /// Samples a non-spherical discrete Gaussian depending on your choice of
122    /// `sigma_sqrt` using the standard basis and center `0`.
123    ///
124    /// Parameters:
125    /// - `sigma_sqrt`: specifies the positive definite Gaussian covariance matrix
126    ///   with which the *intermediate* continuous Gaussian is sampled before
127    ///   the randomized rounding is applied. Here `sigma_sqrt = sqrt(sigma^2 - r^2*I)`
128    ///   where sigma is the target covariance matrix. The root can be computed using
129    ///   the [`MatQ::cholesky_decomposition`].
130    /// - `r`: specifies the rounding parameter for [`MatQ::randomized_rounding`].
131    ///
132    /// Returns a lattice vector sampled according to the discrete Gaussian distribution.
133    ///
134    /// # Examples
135    /// ```
136    /// use qfall_math::integer::MatZ;
137    /// use qfall_math::rational::{Q, MatQ};
138    /// use std::str::FromStr;
139    /// use crate::qfall_math::traits::Pow;
140    ///
141    /// let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap();
142    /// let r = Q::from(4);
143    ///
144    /// let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2);
145    ///
146    /// let sample = MatZ::sample_d_common_non_spherical(&sigma_sqrt.cholesky_decomposition(), r).unwrap();
147    /// ```
148    ///
149    /// # Errors and Failures
150    /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
151    ///   if the `r < 0`.
152    /// - Returns a [`MathError`] of type [`NoSquareMatrix`](MathError::NoSquareMatrix)
153    ///   if the matrix is not symmetric.
154    ///
155    /// This function implements SampleD according to Algorithm 1. in \[2\].
156    /// - \[2\] Peikert, Chris.
157    ///   "An efficient and parallel Gaussian sampler for lattices.
158    ///   In Annual Cryptology Conference, pp. 80-97. Berlin, Heidelberg: Springer
159    ///   Berlin Heidelberg, 2010.
160    ///   <https://link.springer.com/chapter/10.1007/978-3-642-14623-7_5>
161    pub fn sample_d_common_non_spherical(
162        sigma_sqrt: &MatQ,
163        r: impl Into<Q>,
164    ) -> Result<Self, MathError> {
165        if !sigma_sqrt.is_square() {
166            return Err(MathError::NoSquareMatrix("The covariance matrix has to be square, otherwise no discrete Gaussian can be defined.".to_string()));
167        }
168        let r = r.into();
169
170        // sample a continuous Gaussian centered around `0` in every dimension with
171        // gaussian parameter `1`.
172        let d_1 = MatQ::sample_gauss_same_center(sigma_sqrt.get_num_columns(), 1, 0, 1)?;
173
174        // compute a continuous Gaussian centered around `0` in every dimension with
175        // covariance matrix `b_2` (the cholesky decomposition we computed)
176        let x_2 = sigma_sqrt * d_1;
177
178        // perform randomized rounding
179        x_2.randomized_rounding(r)
180    }
181
182    /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
183    ///
184    /// We do not check whether `basis` is actually a basis or whether `basis_gso` is
185    /// actually the gso of `basis`. Hence, the callee is responsible for making sure
186    /// that `basis` provides a suitable basis and `basis_gso` is a corresponding GSO.
187    ///
188    /// Parameters:
189    /// - `basis`: specifies a basis for the lattice from which is sampled
190    /// - `basis_gso`: specifies the precomputed gso for `basis`
191    /// - `center`: specifies the positions of the center with peak probability
192    /// - `s`: specifies the Gaussian parameter, which is proportional
193    ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
194    ///
195    /// Returns a lattice vector sampled according to the discrete Gaussian distribution
196    /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ,
197    /// or if `center` is not a column vector.
198    ///
199    /// # Examples
200    /// ```
201    /// use qfall_math::{integer::{MatZ, Z}, rational::{MatQ, Q}};
202    /// let basis = MatZ::identity(5, 5);
203    /// let center = MatQ::new(5, 1);
204    /// let basis_gso = MatQ::from(&basis).gso();
205    ///
206    /// let sample = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1.25f32).unwrap();
207    /// ```
208    ///
209    /// # Errors and Failures
210    /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
211    ///   if `s < 0`.
212    /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
213    ///   if the number of rows of the `basis` and `center` differ.
214    /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
215    ///   if `center` is not a column vector.
216    ///
217    /// # Panics ...
218    /// - if the number of rows/columns of `basis_gso` and `basis` mismatch.
219    ///
220    /// This function implements SampleD according to:
221    /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
222    ///   Trapdoors for hard lattices and new cryptographic constructions.
223    ///   In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
224    ///   <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
225    pub fn sample_d_precomputed_gso(
226        basis: &MatZ,
227        basis_gso: &MatQ,
228        center: &MatQ,
229        s: impl Into<Q>,
230    ) -> Result<Self, MathError> {
231        let s: Q = s.into();
232
233        sample_d_precomputed_gso(basis, basis_gso, center, &s)
234    }
235}
236
237#[cfg(test)]
238mod test_sample_discrete_gauss {
239    use crate::{
240        integer::{MatZ, Z},
241        rational::Q,
242    };
243
244    // This function only allows for a broader availability, which is tested here.
245
246    /// Checks whether `sample_discrete_gauss` is available for all types
247    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
248    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
249    #[test]
250    fn availability() {
251        let n = Z::from(1024);
252        let center = Q::ZERO;
253        let s = Q::ONE;
254
255        let _ = MatZ::sample_discrete_gauss(2u64, 3i8, &center, 1u16);
256        let _ = MatZ::sample_discrete_gauss(3u8, 2i16, &center, 1u8);
257        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 1u32);
258        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 1u64);
259        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 1i64);
260        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 1i32);
261        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 1i16);
262        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 1i8);
263        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 1i64);
264        let _ = MatZ::sample_discrete_gauss(1, 1, &center, &n);
265        let _ = MatZ::sample_discrete_gauss(1, 1, &center, &s);
266        let _ = MatZ::sample_discrete_gauss(1, 1, 1, &s);
267        let _ = MatZ::sample_discrete_gauss(1, 1, 2.25, &s);
268        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 1.25f64);
269        let _ = MatZ::sample_discrete_gauss(1, 1, &center, 15.75f32);
270    }
271}
272
273#[cfg(test)]
274mod test_sample_d {
275    use crate::{
276        integer::{MatZ, Z},
277        rational::{MatQ, Q},
278    };
279
280    // Appropriate inputs were tested in utils and thus omitted here.
281    // This function only allows for a broader availability, which is tested here.
282
283    /// Checks whether `sample_d` is available for all types
284    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
285    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
286    #[test]
287    fn availability() {
288        let basis = MatZ::identity(5, 5);
289        let n = Z::from(1024);
290        let center = MatQ::new(5, 1);
291        let s = Q::ONE;
292
293        let _ = MatZ::sample_d(&basis, &center, 1u16);
294        let _ = MatZ::sample_d(&basis, &center, 1u8);
295        let _ = MatZ::sample_d(&basis, &center, 1u32);
296        let _ = MatZ::sample_d(&basis, &center, 1u64);
297        let _ = MatZ::sample_d(&basis, &center, 1i64);
298        let _ = MatZ::sample_d(&basis, &center, 1i32);
299        let _ = MatZ::sample_d(&basis, &center, 1i16);
300        let _ = MatZ::sample_d(&basis, &center, 1i8);
301        let _ = MatZ::sample_d(&basis, &center, 1i64);
302        let _ = MatZ::sample_d(&basis, &center, &n);
303        let _ = MatZ::sample_d(&basis, &center, &s);
304        let _ = MatZ::sample_d(&basis, &center, 1.25f64);
305        let _ = MatZ::sample_d(&basis, &center, 15.75f32);
306    }
307
308    /// Checks whether `sample_d_precomputed_gso` is available for all types
309    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
310    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
311    #[test]
312    fn availability_prec_gso() {
313        let basis = MatZ::identity(5, 5);
314        let n = Z::from(1024);
315        let center = MatQ::new(5, 1);
316        let s = Q::ONE;
317        let basis_gso = MatQ::from(&basis);
318
319        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1u16);
320        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1u8);
321        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1u32);
322        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1u64);
323        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1i64);
324        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1i32);
325        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1i16);
326        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1i8);
327        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1i64);
328        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, &n);
329        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, &s);
330        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1.25f64);
331        let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 15.75f32);
332    }
333}
334
335#[cfg(test)]
336mod test_sample_d_common_non_spherical {
337    use crate::{
338        integer::{MatZ, Z},
339        rational::{MatQ, Q},
340        traits::{MatrixDimensions, Pow},
341    };
342    use std::str::FromStr;
343
344    /// Checks whether `sample_d_common_non_spherical` is available for all types
345    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
346    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
347    /// or [`Into<MatQ>`], i.e. MatQ, MatZ
348    #[test]
349    fn availability() {
350        let r = Q::from(8);
351        let covariance_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap();
352        let covariance_matrix =
353            (covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2)).cholesky_decomposition();
354
355        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8).unwrap();
356
357        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u16).unwrap();
358        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u32).unwrap();
359        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u64).unwrap();
360        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i8).unwrap();
361        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i16).unwrap();
362        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i32).unwrap();
363        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i64).unwrap();
364        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, Q::from(8)).unwrap();
365        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, Z::from(8)).unwrap();
366        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8f32).unwrap();
367        let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8f64).unwrap();
368    }
369
370    /// Checks whether the function panics if a non-square matrix is provided.
371    /// anymore
372    #[test]
373    fn not_square() {
374        let covariance_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap();
375
376        assert!(MatZ::sample_d_common_non_spherical(&covariance_matrix, 8).is_err());
377    }
378
379    /// Checks whether the function returns an error if `r` is too small.
380    #[test]
381    fn too_small_parameters() {
382        let covariance_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap();
383
384        assert!(MatZ::sample_d_common_non_spherical(&covariance_matrix, -1).is_err());
385    }
386
387    /// Checks whether the dimension of the output matches the provided covariance matrix
388    #[test]
389    fn correct_dimensions() {
390        let covariance_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap();
391        let covariance_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap();
392
393        let sample_1 = MatZ::sample_d_common_non_spherical(&covariance_matrix_1, 8).unwrap();
394        let sample_2 = MatZ::sample_d_common_non_spherical(&covariance_matrix_2, 8).unwrap();
395
396        assert_eq!(2, sample_1.get_num_rows());
397        assert!(sample_1.is_column_vector());
398        assert_eq!(3, sample_2.get_num_rows());
399        assert!(sample_2.is_column_vector());
400    }
401}