Skip to main content

qfall_math/integer/mat_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 to the discrete Gaussian distribution.
10
11use crate::{
12    error::MathError,
13    integer::MatZ,
14    macros::seeded::seedable_function,
15    rational::{MatQ, Q},
16    traits::{MatrixDimensions, MatrixSetEntry},
17    utils::sample::discrete_gauss::{
18        DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT, sample_d,
19        sample_d_precomputed_gso,
20    },
21    utils::sample::uniform::SamplerRng,
22};
23use std::fmt::Display;
24
25impl MatZ {
26    seedable_function!(
27        /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry
28        /// sampled independently according to the discrete Gaussian distribution,
29        /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss).
30        ///
31        /// Parameters:
32        /// - `num_rows`: specifies the number of rows the new matrix should have
33        /// - `num_cols`: specifies the number of columns the new matrix should have
34        /// - `center`: specifies the positions of the center with peak probability
35        /// - `s`: specifies the Gaussian parameter, which is proportional
36        ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
37        #[seeded]
38        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
39        #[optionally_seeded]
40        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
41        #[optionally_seeded]
42        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
43        ///
44        /// Returns a matrix with each entry sampled independently from the
45        /// specified discrete Gaussian distribution or an error if `s < 0`.
46        ///
47        /// # Examples
48        /// ```
49        /// use qfall_math::integer::MatZ;
50        ///
51        #[unseeded]
52        /// let sample = MatZ::sample_discrete_gauss(3, 1, 0, 1.25f32).unwrap();
53        #[seeded]
54        /// let sample = MatZ::sample_discrete_gauss_seeded(3, 1, 0, 1.25f32, [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 the provided number of rows and columns are not suited to create a matrix.
63        ///   For further information see [`MatZ::new`].
64        pub(crate) fn sample_discrete_gauss(
65            num_rows: impl TryInto<i64> + Display,
66            num_cols: impl TryInto<i64> + Display,
67            center: impl Into<Q>,
68            s: impl Into<Q>,
69            seed: Option<[u8; 32]>,
70        ) -> Result<MatZ, MathError> {
71            let mut out = Self::new(num_rows, num_cols);
72
73            let mut dgis = DiscreteGaussianIntegerSampler::init(
74                center,
75                s,
76                unsafe { TAILCUT },
77                LookupTableSetting::FillOnTheFly,
78                seed,
79            )?;
80
81            for row in 0..out.get_num_rows() {
82                for col in 0..out.get_num_columns() {
83                    let sample = dgis.sample_z();
84                    unsafe { out.set_entry_unchecked(row, col, sample) };
85                }
86            }
87
88            Ok(out)
89        }
90    );
91
92    seedable_function!(
93        /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
94        ///
95        /// We do not check whether `basis` is actually a basis. Hence, the callee is
96        /// responsible for making sure that `basis` provides a suitable basis.
97        ///
98        /// Parameters:
99        /// - `basis`: specifies a basis for the lattice from which is sampled
100        /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples
101        /// - `center`: specifies the positions of the center with peak probability
102        /// - `s`: specifies the Gaussian parameter, which is proportional
103        ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
104        #[seeded]
105        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
106        #[optionally_seeded]
107        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
108        #[optionally_seeded]
109        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
110        ///
111        /// Returns a lattice vector sampled according to the discrete Gaussian distribution
112        /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ,
113        /// or if `center` is not a column vector.
114        ///
115        /// # Examples
116        /// ```
117        /// use qfall_math::{integer::MatZ, rational::MatQ};
118        /// let basis = MatZ::identity(5, 5);
119        /// let center = MatQ::new(5, 1);
120        ///
121        #[unseeded]
122        /// let sample = MatZ::sample_d(&basis, &center, 1.25f32).unwrap();
123        #[seeded]
124        /// let sample = MatZ::sample_d_seeded(&basis, &center, 1.25f32, [42; 32]).unwrap();
125        /// ```
126        ///
127        /// # Errors and Failures
128        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
129        ///   if `s < 0`.
130        /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
131        ///   if the number of rows of the `basis` and `center` differ.
132        /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
133        ///   if `center` is not a column vector.
134        ///
135        /// This function implements SampleD according to:
136        /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
137        ///   Trapdoors for hard lattices and new cryptographic constructions.
138        ///   In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
139        ///   <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
140        pub(crate) fn sample_d(
141            basis: &MatZ,
142            center: &MatQ,
143            s: impl Into<Q>,
144            seed: Option<[u8; 32]>,
145        ) -> Result<Self, MathError> {
146            let s: Q = s.into();
147
148            sample_d(basis, center, &s, seed)
149        }
150    );
151
152    seedable_function!(
153        /// Samples a non-spherical discrete Gaussian depending on your choice of
154        /// `sigma_sqrt` using the standard basis and center `0`.
155        ///
156        /// Parameters:
157        /// - `sigma_sqrt`: specifies the positive definite Gaussian covariance matrix
158        ///   with which the *intermediate* continuous Gaussian is sampled before
159        ///   the randomized rounding is applied. Here `sigma_sqrt = sqrt(sigma^2 - r^2*I)`
160        ///   where sigma is the target covariance matrix. The root can be computed using
161        ///   the [`MatQ::cholesky_decomposition`].
162        /// - `r`: specifies the rounding parameter for [`MatQ::randomized_rounding`].
163        #[seeded]
164        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
165        #[optionally_seeded]
166        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
167        #[optionally_seeded]
168        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
169        ///
170        /// Returns a lattice vector sampled according to the discrete Gaussian distribution.
171        ///
172        /// # Examples
173        /// ```
174        /// use qfall_math::integer::MatZ;
175        /// use qfall_math::rational::{Q, MatQ};
176        /// use std::str::FromStr;
177        /// use qfall_math::traits::Pow;
178        ///
179        /// let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap();
180        /// let r = Q::from(4);
181        ///
182        /// let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2);
183        ///
184        #[unseeded]
185        /// let sample = MatZ::sample_d_common_non_spherical(&sigma_sqrt.cholesky_decomposition(), r).unwrap();
186        #[seeded]
187        /// let sample = MatZ::sample_d_common_non_spherical_seeded(&sigma_sqrt.cholesky_decomposition(), r, [42; 32]).unwrap();
188        /// ```
189        ///
190        /// # Errors and Failures
191        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
192        ///   if the `r < 0`.
193        /// - Returns a [`MathError`] of type [`NoSquareMatrix`](MathError::NoSquareMatrix)
194        ///   if the matrix is not symmetric.
195        ///
196        /// This function implements SampleD according to Algorithm 1. in \[2\].
197        /// - \[2\] Peikert, Chris.
198        ///   "An efficient and parallel Gaussian sampler for lattices.
199        ///   In Annual Cryptology Conference, pp. 80-97. Berlin, Heidelberg: Springer
200        ///   Berlin Heidelberg, 2010.
201        ///   <https://link.springer.com/chapter/10.1007/978-3-642-14623-7_5>
202        pub(crate) fn sample_d_common_non_spherical(
203            sigma_sqrt: &MatQ,
204            r: impl Into<Q>,
205            seed: Option<[u8; 32]>,
206        ) -> Result<Self, MathError> {
207            // derives an independent seed for every call of a seedable function below,
208            // see the documentation of `seedable_function` in `src/macros/seeded.rs`
209            let mut rng = SamplerRng::new(seed);
210
211            if !sigma_sqrt.is_square() {
212                return Err(MathError::NoSquareMatrix("The covariance matrix has to be square, otherwise no discrete Gaussian can be defined.".to_string()));
213            }
214            let r = r.into();
215
216            // sample a continuous Gaussian centered around `0` in every dimension with
217            // gaussian parameter `1`.
218            let d_1 = MatQ::sample_gauss_same_center_optionally_seeded(
219                sigma_sqrt.get_num_columns(),
220                1,
221                0,
222                1,
223                rng.derive_seed(),
224            )?;
225
226            // compute a continuous Gaussian centered around `0` in every dimension with
227            // covariance matrix `b_2` (the cholesky decomposition we computed)
228            let x_2 = sigma_sqrt * d_1;
229
230            // perform randomized rounding
231            x_2.randomized_rounding_optionally_seeded(r, rng.derive_seed())
232        }
233    );
234
235    seedable_function!(
236        /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
237        ///
238        /// We do not check whether `basis` is actually a basis or whether `basis_gso` is
239        /// actually the gso of `basis`. Hence, the callee is responsible for making sure
240        /// that `basis` provides a suitable basis and `basis_gso` is a corresponding GSO.
241        ///
242        /// Parameters:
243        /// - `basis`: specifies a basis for the lattice from which is sampled
244        /// - `basis_gso`: specifies the precomputed gso for `basis`
245        /// - `center`: specifies the positions of the center with peak probability
246        /// - `s`: specifies the Gaussian parameter, which is proportional
247        ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
248        #[seeded]
249        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
250        #[optionally_seeded]
251        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
252        #[optionally_seeded]
253        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
254        ///
255        /// Returns a lattice vector sampled according to the discrete Gaussian distribution
256        /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ,
257        /// or if `center` is not a column vector.
258        ///
259        /// # Examples
260        /// ```
261        /// use qfall_math::{integer::MatZ, rational::MatQ};
262        /// let basis = MatZ::identity(5, 5);
263        /// let center = MatQ::new(5, 1);
264        /// let basis_gso = MatQ::from(&basis).gso();
265        ///
266        #[unseeded]
267        /// let sample = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1.25f32).unwrap();
268        #[seeded]
269        /// let sample = MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, &center, 1.25f32, [42; 32]).unwrap();
270        /// ```
271        ///
272        /// # Errors and Failures
273        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
274        ///   if `s < 0`.
275        /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
276        ///   if the number of rows of the `basis` and `center` differ.
277        /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
278        ///   if `center` is not a column vector.
279        ///
280        /// # Panics ...
281        /// - if the number of rows/columns of `basis_gso` and `basis` mismatch.
282        ///
283        /// This function implements SampleD according to:
284        /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
285        ///   Trapdoors for hard lattices and new cryptographic constructions.
286        ///   In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
287        ///   <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
288        pub(crate) fn sample_d_precomputed_gso(
289            basis: &MatZ,
290            basis_gso: &MatQ,
291            center: &MatQ,
292            s: impl Into<Q>,
293            seed: Option<[u8; 32]>,
294        ) -> Result<Self, MathError> {
295            let s: Q = s.into();
296
297            sample_d_precomputed_gso(basis, basis_gso, center, &s, seed)
298        }
299    );
300}
301
302#[cfg(test)]
303mod test_sample_discrete_gauss {
304    use crate::utils::sample::test_seed;
305    use crate::{
306        integer::{MatZ, Z},
307        rational::Q,
308    };
309
310    // This function only allows for a broader availability, which is tested here.
311
312    /// Checks whether `sample_discrete_gauss` is available for all types
313    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
314    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
315    #[test]
316    fn availability() {
317        for seeded in [false, true] {
318            let n = Z::from(1024);
319            let center = Q::ZERO;
320            let s = Q::ONE;
321
322            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
323                2u64,
324                3i8,
325                &center,
326                1u16,
327                seeded.then(test_seed),
328            );
329            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
330                3u8,
331                2i16,
332                &center,
333                1u8,
334                seeded.then(test_seed),
335            );
336            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
337                1,
338                1,
339                &center,
340                1u32,
341                seeded.then(test_seed),
342            );
343            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
344                1,
345                1,
346                &center,
347                1u64,
348                seeded.then(test_seed),
349            );
350            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
351                1,
352                1,
353                &center,
354                1i64,
355                seeded.then(test_seed),
356            );
357            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
358                1,
359                1,
360                &center,
361                1i32,
362                seeded.then(test_seed),
363            );
364            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
365                1,
366                1,
367                &center,
368                1i16,
369                seeded.then(test_seed),
370            );
371            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
372                1,
373                1,
374                &center,
375                1i8,
376                seeded.then(test_seed),
377            );
378            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
379                1,
380                1,
381                &center,
382                1i64,
383                seeded.then(test_seed),
384            );
385            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
386                1,
387                1,
388                &center,
389                &n,
390                seeded.then(test_seed),
391            );
392            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
393                1,
394                1,
395                &center,
396                &s,
397                seeded.then(test_seed),
398            );
399            let _ =
400                MatZ::sample_discrete_gauss_optionally_seeded(1, 1, 1, &s, seeded.then(test_seed));
401            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
402                1,
403                1,
404                2.25,
405                &s,
406                seeded.then(test_seed),
407            );
408            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
409                1,
410                1,
411                &center,
412                1.25f64,
413                seeded.then(test_seed),
414            );
415            let _ = MatZ::sample_discrete_gauss_optionally_seeded(
416                1,
417                1,
418                &center,
419                15.75f32,
420                seeded.then(test_seed),
421            );
422
423            // explicitly checks the existence of the unseeded and the seeded function
424            if seeded {
425                let _ = MatZ::sample_discrete_gauss_seeded(2u64, 3i8, &center, 1u16, test_seed());
426            } else {
427                let _ = MatZ::sample_discrete_gauss(2u64, 3i8, &center, 1u16);
428            }
429        }
430    }
431
432    /// Checks whether the same seed results in the same sample.
433    #[test]
434    fn same_seed_same_sample() {
435        use crate::integer::MatZ;
436        let sample_0 = MatZ::sample_discrete_gauss_seeded(3, 1, 0, 1.25f32, [42; 32]).unwrap();
437        let sample_1 = MatZ::sample_discrete_gauss_seeded(3, 1, 0, 1.25f32, [42; 32]).unwrap();
438
439        assert_eq!(sample_0, sample_1);
440    }
441
442    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
443    #[test]
444    fn known_answer() {
445        use crate::integer::MatZ;
446        let sample = MatZ::sample_discrete_gauss_seeded(3, 1, 0, 1.25f32, [42; 32]).unwrap();
447
448        assert_eq!("[[1],[-1],[0]]", sample.to_string());
449    }
450}
451
452#[cfg(test)]
453mod test_sample_d {
454    use crate::utils::sample::test_seed;
455    use crate::{
456        integer::{MatZ, Z},
457        rational::{MatQ, Q},
458    };
459
460    // Appropriate inputs were tested in utils and thus omitted here.
461    // This function only allows for a broader availability, which is tested here.
462
463    /// Checks whether `sample_d` is available for all types
464    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
465    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
466    #[test]
467    fn availability() {
468        for seeded in [false, true] {
469            let basis = MatZ::identity(5, 5);
470            let n = Z::from(1024);
471            let center = MatQ::new(5, 1);
472            let s = Q::ONE;
473
474            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1u16, seeded.then(test_seed));
475            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1u8, seeded.then(test_seed));
476            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1u32, seeded.then(test_seed));
477            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1u64, seeded.then(test_seed));
478            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1i64, seeded.then(test_seed));
479            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1i32, seeded.then(test_seed));
480            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1i16, seeded.then(test_seed));
481            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1i8, seeded.then(test_seed));
482            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, 1i64, seeded.then(test_seed));
483            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, &n, seeded.then(test_seed));
484            let _ = MatZ::sample_d_optionally_seeded(&basis, &center, &s, seeded.then(test_seed));
485            let _ =
486                MatZ::sample_d_optionally_seeded(&basis, &center, 1.25f64, seeded.then(test_seed));
487            let _ =
488                MatZ::sample_d_optionally_seeded(&basis, &center, 15.75f32, seeded.then(test_seed));
489
490            // explicitly checks the existence of the unseeded and the seeded function
491            if seeded {
492                let _ = MatZ::sample_d_seeded(&basis, &center, 1u16, test_seed());
493            } else {
494                let _ = MatZ::sample_d(&basis, &center, 1u16);
495            }
496        }
497    }
498
499    /// Checks whether `sample_d_precomputed_gso` is available for all types
500    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
501    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
502    #[test]
503    fn availability_prec_gso() {
504        for seeded in [false, true] {
505            let basis = MatZ::identity(5, 5);
506            let n = Z::from(1024);
507            let center = MatQ::new(5, 1);
508            let s = Q::ONE;
509            let basis_gso = MatQ::from(&basis);
510
511            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
512                &basis,
513                &basis_gso,
514                &center,
515                1u16,
516                seeded.then(test_seed),
517            );
518            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
519                &basis,
520                &basis_gso,
521                &center,
522                1u8,
523                seeded.then(test_seed),
524            );
525            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
526                &basis,
527                &basis_gso,
528                &center,
529                1u32,
530                seeded.then(test_seed),
531            );
532            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
533                &basis,
534                &basis_gso,
535                &center,
536                1u64,
537                seeded.then(test_seed),
538            );
539            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
540                &basis,
541                &basis_gso,
542                &center,
543                1i64,
544                seeded.then(test_seed),
545            );
546            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
547                &basis,
548                &basis_gso,
549                &center,
550                1i32,
551                seeded.then(test_seed),
552            );
553            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
554                &basis,
555                &basis_gso,
556                &center,
557                1i16,
558                seeded.then(test_seed),
559            );
560            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
561                &basis,
562                &basis_gso,
563                &center,
564                1i8,
565                seeded.then(test_seed),
566            );
567            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
568                &basis,
569                &basis_gso,
570                &center,
571                1i64,
572                seeded.then(test_seed),
573            );
574            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
575                &basis,
576                &basis_gso,
577                &center,
578                &n,
579                seeded.then(test_seed),
580            );
581            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
582                &basis,
583                &basis_gso,
584                &center,
585                &s,
586                seeded.then(test_seed),
587            );
588            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
589                &basis,
590                &basis_gso,
591                &center,
592                1.25f64,
593                seeded.then(test_seed),
594            );
595            let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
596                &basis,
597                &basis_gso,
598                &center,
599                15.75f32,
600                seeded.then(test_seed),
601            );
602
603            // explicitly checks the existence of the unseeded and the seeded function
604            if seeded {
605                let _ = MatZ::sample_d_precomputed_gso_seeded(
606                    &basis,
607                    &basis_gso,
608                    &center,
609                    1u16,
610                    test_seed(),
611                );
612            } else {
613                let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1u16);
614            }
615        }
616    }
617
618    /// Checks whether the same seed results in the same sample.
619    #[test]
620    fn same_seed_same_sample_d() {
621        use crate::{integer::MatZ, rational::MatQ};
622        let basis = MatZ::identity(5, 5);
623        let center = MatQ::new(5, 1);
624        let sample_0 = MatZ::sample_d_seeded(&basis, &center, 1.25f32, [42; 32]).unwrap();
625        let sample_1 = MatZ::sample_d_seeded(&basis, &center, 1.25f32, [42; 32]).unwrap();
626
627        assert_eq!(sample_0, sample_1);
628    }
629
630    /// Checks whether the same seed results in the same sample.
631    #[test]
632    fn same_seed_same_sample_d_precomputed_gso() {
633        use crate::{integer::MatZ, rational::MatQ};
634        let basis = MatZ::identity(5, 5);
635        let center = MatQ::new(5, 1);
636        let basis_gso = MatQ::from(&basis).gso();
637        let sample_0 =
638            MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, &center, 1.25f32, [42; 32])
639                .unwrap();
640        let sample_1 =
641            MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, &center, 1.25f32, [42; 32])
642                .unwrap();
643
644        assert_eq!(sample_0, sample_1);
645    }
646
647    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
648    #[test]
649    fn known_answer_d() {
650        use crate::{integer::MatZ, rational::MatQ};
651        let basis = MatZ::identity(5, 5);
652        let center = MatQ::new(5, 1);
653        let sample = MatZ::sample_d_seeded(&basis, &center, 1.25f32, [42; 32]).unwrap();
654
655        assert_eq!("[[0],[-1],[0],[0],[0]]", sample.to_string());
656    }
657
658    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
659    #[test]
660    fn known_answer_d_precomputed_gso() {
661        use crate::{integer::MatZ, rational::MatQ};
662        let basis = MatZ::identity(5, 5);
663        let center = MatQ::new(5, 1);
664        let basis_gso = MatQ::from(&basis).gso();
665        let sample =
666            MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, &center, 1.25f32, [42; 32])
667                .unwrap();
668
669        assert_eq!("[[0],[-1],[0],[0],[0]]", sample.to_string());
670    }
671}
672
673#[cfg(test)]
674mod test_sample_d_common_non_spherical {
675    use crate::utils::sample::test_seed;
676    use crate::{
677        integer::{MatZ, Z},
678        rational::{MatQ, Q},
679        traits::{MatrixDimensions, Pow},
680    };
681    use std::str::FromStr;
682
683    /// Checks whether `sample_d_common_non_spherical` is available for all types
684    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
685    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
686    /// or [`Into<MatQ>`], i.e. MatQ, MatZ
687    #[test]
688    fn availability() {
689        for seeded in [false, true] {
690            let r = Q::from(8);
691            let covariance_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap();
692            let covariance_matrix = (covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2))
693                .cholesky_decomposition();
694
695            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
696                &covariance_matrix,
697                8,
698                seeded.then(test_seed),
699            )
700            .unwrap();
701
702            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
703                &covariance_matrix,
704                8_u16,
705                seeded.then(test_seed),
706            )
707            .unwrap();
708            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
709                &covariance_matrix,
710                8_u32,
711                seeded.then(test_seed),
712            )
713            .unwrap();
714            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
715                &covariance_matrix,
716                8_u64,
717                seeded.then(test_seed),
718            )
719            .unwrap();
720            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
721                &covariance_matrix,
722                8_i8,
723                seeded.then(test_seed),
724            )
725            .unwrap();
726            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
727                &covariance_matrix,
728                8_i16,
729                seeded.then(test_seed),
730            )
731            .unwrap();
732            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
733                &covariance_matrix,
734                8_i32,
735                seeded.then(test_seed),
736            )
737            .unwrap();
738            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
739                &covariance_matrix,
740                8_i64,
741                seeded.then(test_seed),
742            )
743            .unwrap();
744            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
745                &covariance_matrix,
746                Q::from(8),
747                seeded.then(test_seed),
748            )
749            .unwrap();
750            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
751                &covariance_matrix,
752                Z::from(8),
753                seeded.then(test_seed),
754            )
755            .unwrap();
756            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
757                &covariance_matrix,
758                8f32,
759                seeded.then(test_seed),
760            )
761            .unwrap();
762            let _ = MatZ::sample_d_common_non_spherical_optionally_seeded(
763                &covariance_matrix,
764                8f64,
765                seeded.then(test_seed),
766            )
767            .unwrap();
768
769            // explicitly checks the existence of the unseeded and the seeded function
770            if seeded {
771                let _ =
772                    MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8, test_seed());
773            } else {
774                let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8);
775            }
776        }
777    }
778
779    /// Checks whether the function panics if a non-square matrix is provided.
780    /// anymore
781    #[test]
782    fn not_square() {
783        for seeded in [false, true] {
784            let covariance_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap();
785
786            assert!(
787                MatZ::sample_d_common_non_spherical_optionally_seeded(
788                    &covariance_matrix,
789                    8,
790                    seeded.then(test_seed)
791                )
792                .is_err()
793            );
794        }
795    }
796
797    /// Checks whether the function returns an error if `r` is too small.
798    #[test]
799    fn too_small_parameters() {
800        for seeded in [false, true] {
801            let covariance_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap();
802
803            assert!(
804                MatZ::sample_d_common_non_spherical_optionally_seeded(
805                    &covariance_matrix,
806                    -1,
807                    seeded.then(test_seed)
808                )
809                .is_err()
810            );
811        }
812    }
813
814    /// Checks whether the dimension of the output matches the provided covariance matrix
815    #[test]
816    fn correct_dimensions() {
817        for seeded in [false, true] {
818            let covariance_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap();
819            let covariance_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap();
820
821            let sample_1 = MatZ::sample_d_common_non_spherical_optionally_seeded(
822                &covariance_matrix_1,
823                8,
824                seeded.then(test_seed),
825            )
826            .unwrap();
827            let sample_2 = MatZ::sample_d_common_non_spherical_optionally_seeded(
828                &covariance_matrix_2,
829                8,
830                seeded.then(test_seed),
831            )
832            .unwrap();
833
834            assert_eq!(2, sample_1.get_num_rows());
835            assert!(sample_1.is_column_vector());
836            assert_eq!(3, sample_2.get_num_rows());
837            assert!(sample_2.is_column_vector());
838        }
839    }
840
841    /// Checks whether the same seed results in the same sample.
842    #[test]
843    fn same_seed_same_sample() {
844        use crate::integer::MatZ;
845        use crate::rational::{MatQ, Q};
846        use crate::traits::Pow;
847        use std::str::FromStr;
848        let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap();
849        let r = Q::from(4);
850        let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2);
851        let sample_0 = MatZ::sample_d_common_non_spherical_seeded(
852            &sigma_sqrt.cholesky_decomposition(),
853            &r,
854            [42; 32],
855        )
856        .unwrap();
857        let sample_1 = MatZ::sample_d_common_non_spherical_seeded(
858            &sigma_sqrt.cholesky_decomposition(),
859            &r,
860            [42; 32],
861        )
862        .unwrap();
863
864        assert_eq!(sample_0, sample_1);
865    }
866
867    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
868    #[test]
869    fn known_answer() {
870        use crate::integer::MatZ;
871        use crate::rational::{MatQ, Q};
872        use crate::traits::Pow;
873        use std::str::FromStr;
874        let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap();
875        let r = Q::from(4);
876        let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2);
877        let sample = MatZ::sample_d_common_non_spherical_seeded(
878            &sigma_sqrt.cholesky_decomposition(),
879            &r,
880            [42; 32],
881        )
882        .unwrap();
883
884        assert_eq!("[[-19],[1]]", sample.to_string());
885    }
886}