Skip to main content

qfall_math/integer_mod_q/mat_zq/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_mod_q::{MatZq, Modulus},
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};
22use std::fmt::Display;
23
24impl MatZq {
25    seedable_function!(
26        /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry
27        /// sampled independently according to the discrete Gaussian distribution,
28        /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss).
29        ///
30        /// Parameters:
31        /// - `num_rows`: specifies the number of rows the new matrix should have
32        /// - `num_cols`: specifies the number of columns the new matrix 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 matrix with each entry sampled independently from the
44        /// specified discrete Gaussian distribution or an error if `s < 0`.
45        ///
46        /// # Examples
47        /// ```
48        /// use qfall_math::integer_mod_q::MatZq;
49        ///
50        #[unseeded]
51        /// let sample = MatZq::sample_discrete_gauss(3, 1, 83, 0, 1.25f32).unwrap();
52        #[seeded]
53        /// let sample = MatZq::sample_discrete_gauss_seeded(3, 1, 83, 0, 1.25f32, [42; 32]).unwrap();
54        /// ```
55        ///
56        /// # Errors and Failures
57        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
58        ///   if `s < 0`.
59        ///
60        /// # Panics ...
61        /// - if the provided number of rows and columns or the modulus are not suited to create a matrix.
62        ///   For further information see [`MatZq::new`].
63        /// - if the provided `modulus < 2`.
64        pub(crate) fn sample_discrete_gauss(
65            num_rows: impl TryInto<i64> + Display,
66            num_cols: impl TryInto<i64> + Display,
67            modulus: impl Into<Modulus>,
68            center: impl Into<Q>,
69            s: impl Into<Q>,
70            seed: Option<[u8; 32]>,
71        ) -> Result<MatZq, MathError> {
72            let modulus: Modulus = modulus.into();
73            let center: Q = center.into();
74            let s: Q = s.into();
75            let mut out = Self::new(num_rows, num_cols, &modulus);
76
77            let mut dgis = DiscreteGaussianIntegerSampler::init(
78                &center,
79                &s,
80                unsafe { TAILCUT },
81                LookupTableSetting::FillOnTheFly,
82                seed,
83            )?;
84
85            for row in 0..out.get_num_rows() {
86                for col in 0..out.get_num_columns() {
87                    let mut sample = dgis.sample_z();
88                    sample = sample % &modulus;
89                    unsafe { out.set_entry_unchecked(row, col, sample) };
90                }
91            }
92
93            Ok(out)
94        }
95    );
96
97    seedable_function!(
98        /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
99        ///
100        /// We do not check whether `basis` is actually a basis. Hence, the callee is
101        /// responsible for making sure that `basis` provides a suitable basis.
102        ///
103        /// Parameters:
104        /// - `basis`: specifies a basis for the lattice from which is sampled
105        /// - `center`: specifies the positions of the center with peak probability
106        /// - `s`: specifies the Gaussian parameter, which is proportional
107        ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
108        #[seeded]
109        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
110        #[optionally_seeded]
111        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
112        #[optionally_seeded]
113        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
114        ///
115        /// Returns a lattice vector sampled according to the discrete Gaussian distribution
116        /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ,
117        /// or if `center` is not a column vector.
118        ///
119        /// # Examples
120        /// ```
121        /// use qfall_math::{integer_mod_q::MatZq, rational::MatQ};
122        /// let basis = MatZq::identity(5, 5, 17);
123        /// let center = MatQ::new(5, 1);
124        ///
125        #[unseeded]
126        /// let sample = MatZq::sample_d(&basis, &center, 1.25f32).unwrap();
127        #[seeded]
128        /// let sample = MatZq::sample_d_seeded(&basis, &center, 1.25f32, [42; 32]).unwrap();
129        /// ```
130        ///
131        /// # Errors and Failures
132        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
133        ///   if `s < 0`.
134        /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
135        ///   if the number of rows of the `basis` and `center` differ.
136        /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
137        ///   if `center` is not a column vector.
138        ///
139        /// This function implements SampleD according to:
140        /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
141        ///   Trapdoors for hard lattices and new cryptographic constructions.
142        ///   In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
143        ///   <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
144        pub(crate) fn sample_d(
145            basis: &MatZq,
146            center: &MatQ,
147            s: impl Into<Q>,
148            seed: Option<[u8; 32]>,
149        ) -> Result<Self, MathError> {
150            let s: Q = s.into();
151
152            let sample = sample_d(
153                &basis.get_representative_least_nonnegative_residue(),
154                center,
155                &s,
156                seed,
157            )?;
158
159            Ok(MatZq::from((&sample, basis.get_mod())))
160        }
161    );
162
163    seedable_function!(
164        /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
165        ///
166        /// We do not check whether `basis` is actually a basis or whether `basis_gso` is
167        /// actually the gso of `basis`. Hence, the callee is responsible for making sure
168        /// that `basis` provides a suitable basis and `basis_gso` is a corresponding GSO.
169        ///
170        /// Parameters:
171        /// - `basis`: specifies a basis for the lattice from which is sampled
172        /// - `basis_gso`: specifies the precomputed gso for `basis`
173        /// - `center`: specifies the positions of the center with peak probability
174        /// - `s`: specifies the Gaussian parameter, which is proportional
175        ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
176        #[seeded]
177        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
178        #[optionally_seeded]
179        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
180        #[optionally_seeded]
181        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
182        ///
183        /// Returns a lattice vector sampled according to the discrete Gaussian distribution
184        /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ,
185        /// or if `center` is not a column vector.
186        ///
187        /// # Examples
188        /// ```
189        /// use qfall_math::{integer_mod_q::MatZq, rational::MatQ};
190        /// let basis = MatZq::identity(5, 5, 17);
191        /// let center = MatQ::new(5, 1);
192        /// let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()).gso();
193        ///
194        #[unseeded]
195        /// let sample = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1.25f32).unwrap();
196        #[seeded]
197        /// let sample = MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, &center, 1.25f32, [42; 32]).unwrap();
198        /// ```
199        ///
200        /// # Errors and Failures
201        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
202        ///   if `s < 0`.
203        /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
204        ///   if the number of rows of the `basis` and `center` differ.
205        /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
206        ///   if `center` is not a column vector.
207        ///
208        /// # Panics ...
209        /// - if the number of rows/columns of `basis_gso` and `basis` mismatch.
210        ///
211        /// This function implements SampleD according to:
212        /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
213        ///   Trapdoors for hard lattices and new cryptographic constructions.
214        ///   In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
215        ///   <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
216        pub(crate) fn sample_d_precomputed_gso(
217            basis: &MatZq,
218            basis_gso: &MatQ,
219            center: &MatQ,
220            s: impl Into<Q>,
221            seed: Option<[u8; 32]>,
222        ) -> Result<Self, MathError> {
223            let s: Q = s.into();
224
225            let sample = sample_d_precomputed_gso(
226                &basis.get_representative_least_nonnegative_residue(),
227                basis_gso,
228                center,
229                &s,
230                seed,
231            )?;
232
233            Ok(MatZq::from((&sample, basis.get_mod())))
234        }
235    );
236}
237
238#[cfg(test)]
239mod test_sample_discrete_gauss {
240    use crate::utils::sample::test_seed;
241    use crate::{
242        integer::Z,
243        integer_mod_q::{MatZq, Modulus},
244        rational::Q,
245        traits::MatrixGetEntry,
246    };
247
248    // This function only allows for a broader availability, which is tested here.
249
250    /// Checks if entries are properly reduced.
251    #[test]
252    fn reduced() {
253        for seeded in [false, true] {
254            let matrix = MatZq::sample_discrete_gauss_optionally_seeded(
255                1,
256                1,
257                3,
258                0,
259                15.0,
260                seeded.then(test_seed),
261            )
262            .unwrap();
263            let value: Z = matrix.get_entry(0, 0).unwrap();
264            assert!(Z::ZERO <= value && value <= 3)
265        }
266    }
267
268    /// Checks whether `sample_discrete_gauss` is available for all types
269    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
270    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
271    #[test]
272    fn availability() {
273        for seeded in [false, true] {
274            let n = Z::from(1024);
275            let center = Q::ZERO;
276            let s = Q::ONE;
277            let modulus = Modulus::from(83);
278
279            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
280                2u64,
281                3i8,
282                &modulus,
283                0f32,
284                1u16,
285                seeded.then(test_seed),
286            );
287            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
288                3u8,
289                2i16,
290                83u8,
291                &center,
292                1u8,
293                seeded.then(test_seed),
294            );
295            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
296                1,
297                1,
298                &n,
299                &center,
300                1u32,
301                seeded.then(test_seed),
302            );
303            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
304                1,
305                1,
306                83i8,
307                &center,
308                1u64,
309                seeded.then(test_seed),
310            );
311            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
312                1,
313                1,
314                83,
315                &center,
316                1i64,
317                seeded.then(test_seed),
318            );
319            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
320                1,
321                1,
322                83,
323                &center,
324                1i32,
325                seeded.then(test_seed),
326            );
327            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
328                1,
329                1,
330                83,
331                &center,
332                1i16,
333                seeded.then(test_seed),
334            );
335            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
336                1,
337                1,
338                83,
339                &center,
340                1i8,
341                seeded.then(test_seed),
342            );
343            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
344                1,
345                1,
346                83,
347                &center,
348                1i64,
349                seeded.then(test_seed),
350            );
351            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
352                1,
353                1,
354                83,
355                &center,
356                &n,
357                seeded.then(test_seed),
358            );
359            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
360                1,
361                1,
362                83,
363                &center,
364                &s,
365                seeded.then(test_seed),
366            );
367            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
368                1,
369                1,
370                83,
371                &center,
372                1.25f64,
373                seeded.then(test_seed),
374            );
375            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
376                1,
377                1,
378                83,
379                0,
380                1.25f64,
381                seeded.then(test_seed),
382            );
383            let _ = MatZq::sample_discrete_gauss_optionally_seeded(
384                1,
385                1,
386                83,
387                center,
388                15.75f32,
389                seeded.then(test_seed),
390            );
391
392            // explicitly checks the existence of the unseeded and the seeded function
393            if seeded {
394                let _ = MatZq::sample_discrete_gauss_seeded(
395                    2u64,
396                    3i8,
397                    &modulus,
398                    0f32,
399                    1u16,
400                    test_seed(),
401                );
402            } else {
403                let _ = MatZq::sample_discrete_gauss(2u64, 3i8, &modulus, 0f32, 1u16);
404            }
405        }
406    }
407
408    /// Checks whether the same seed results in the same sample.
409    #[test]
410    fn same_seed_same_sample() {
411        use crate::integer_mod_q::MatZq;
412        let sample_0 = MatZq::sample_discrete_gauss_seeded(3, 1, 83, 0, 1.25f32, [42; 32]).unwrap();
413        let sample_1 = MatZq::sample_discrete_gauss_seeded(3, 1, 83, 0, 1.25f32, [42; 32]).unwrap();
414
415        assert_eq!(sample_0, sample_1);
416    }
417
418    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
419    #[test]
420    fn known_answer() {
421        use crate::integer_mod_q::MatZq;
422        let sample = MatZq::sample_discrete_gauss_seeded(3, 1, 83, 0, 1.25f32, [42; 32]).unwrap();
423
424        assert_eq!("[[1],[82],[0]] mod 83", sample.to_string());
425    }
426}
427
428#[cfg(test)]
429mod test_sample_d {
430    use crate::utils::sample::test_seed;
431    use crate::{
432        integer::Z,
433        integer_mod_q::MatZq,
434        rational::{MatQ, Q},
435    };
436
437    // Appropriate inputs were tested in utils and thus omitted here.
438    // This function only allows for a broader availability, which is tested here.
439
440    /// Checks whether `sample_d` is available for all types
441    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
442    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
443    #[test]
444    fn availability() {
445        for seeded in [false, true] {
446            let basis = MatZq::identity(5, 5, 17);
447            let n = Z::from(1024);
448            let center = MatQ::new(5, 1);
449            let s = Q::ONE;
450
451            let _ =
452                MatZq::sample_d_optionally_seeded(&basis, &center, 1u16, seeded.then(test_seed));
453            let _ = MatZq::sample_d_optionally_seeded(&basis, &center, 1u8, seeded.then(test_seed));
454            let _ =
455                MatZq::sample_d_optionally_seeded(&basis, &center, 1u32, seeded.then(test_seed));
456            let _ =
457                MatZq::sample_d_optionally_seeded(&basis, &center, 1u64, seeded.then(test_seed));
458            let _ =
459                MatZq::sample_d_optionally_seeded(&basis, &center, 1i64, seeded.then(test_seed));
460            let _ =
461                MatZq::sample_d_optionally_seeded(&basis, &center, 1i32, seeded.then(test_seed));
462            let _ =
463                MatZq::sample_d_optionally_seeded(&basis, &center, 1i16, seeded.then(test_seed));
464            let _ = MatZq::sample_d_optionally_seeded(&basis, &center, 1i8, seeded.then(test_seed));
465            let _ =
466                MatZq::sample_d_optionally_seeded(&basis, &center, 1i64, seeded.then(test_seed));
467            let _ = MatZq::sample_d_optionally_seeded(&basis, &center, &n, seeded.then(test_seed));
468            let _ = MatZq::sample_d_optionally_seeded(&basis, &center, &s, seeded.then(test_seed));
469            let _ =
470                MatZq::sample_d_optionally_seeded(&basis, &center, 1.25f64, seeded.then(test_seed));
471            let _ = MatZq::sample_d_optionally_seeded(
472                &basis,
473                &center,
474                15.75f32,
475                seeded.then(test_seed),
476            );
477
478            // explicitly checks the existence of the unseeded and the seeded function
479            if seeded {
480                let _ = MatZq::sample_d_seeded(&basis, &center, 1u16, test_seed());
481            } else {
482                let _ = MatZq::sample_d(&basis, &center, 1u16);
483            }
484        }
485    }
486
487    /// Checks whether `sample_d_precomputed_gso` is available for all types
488    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
489    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
490    #[test]
491    fn availability_prec_gso() {
492        for seeded in [false, true] {
493            let basis = MatZq::identity(5, 5, 17);
494            let n = Z::from(1024);
495            let center = MatQ::new(5, 1);
496            let s = Q::ONE;
497            let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue());
498
499            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
500                &basis,
501                &basis_gso,
502                &center,
503                1u16,
504                seeded.then(test_seed),
505            );
506            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
507                &basis,
508                &basis_gso,
509                &center,
510                1u8,
511                seeded.then(test_seed),
512            );
513            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
514                &basis,
515                &basis_gso,
516                &center,
517                1u32,
518                seeded.then(test_seed),
519            );
520            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
521                &basis,
522                &basis_gso,
523                &center,
524                1u64,
525                seeded.then(test_seed),
526            );
527            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
528                &basis,
529                &basis_gso,
530                &center,
531                1i64,
532                seeded.then(test_seed),
533            );
534            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
535                &basis,
536                &basis_gso,
537                &center,
538                1i32,
539                seeded.then(test_seed),
540            );
541            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
542                &basis,
543                &basis_gso,
544                &center,
545                1i16,
546                seeded.then(test_seed),
547            );
548            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
549                &basis,
550                &basis_gso,
551                &center,
552                1i8,
553                seeded.then(test_seed),
554            );
555            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
556                &basis,
557                &basis_gso,
558                &center,
559                1i64,
560                seeded.then(test_seed),
561            );
562            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
563                &basis,
564                &basis_gso,
565                &center,
566                &n,
567                seeded.then(test_seed),
568            );
569            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
570                &basis,
571                &basis_gso,
572                &center,
573                &s,
574                seeded.then(test_seed),
575            );
576            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
577                &basis,
578                &basis_gso,
579                &center,
580                1.25f64,
581                seeded.then(test_seed),
582            );
583            let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
584                &basis,
585                &basis_gso,
586                &center,
587                15.75f32,
588                seeded.then(test_seed),
589            );
590
591            // explicitly checks the existence of the unseeded and the seeded function
592            if seeded {
593                let _ = MatZq::sample_d_precomputed_gso_seeded(
594                    &basis,
595                    &basis_gso,
596                    &center,
597                    1u16,
598                    test_seed(),
599                );
600            } else {
601                let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, &center, 1u16);
602            }
603        }
604    }
605
606    /// Checks whether the same seed results in the same sample.
607    #[test]
608    fn same_seed_same_sample_d() {
609        use crate::{integer_mod_q::MatZq, rational::MatQ};
610        let basis = MatZq::identity(5, 5, 17);
611        let center = MatQ::new(5, 1);
612        let sample_0 = MatZq::sample_d_seeded(&basis, &center, 1.25f32, [42; 32]).unwrap();
613        let sample_1 = MatZq::sample_d_seeded(&basis, &center, 1.25f32, [42; 32]).unwrap();
614
615        assert_eq!(sample_0, sample_1);
616    }
617
618    /// Checks whether the same seed results in the same sample.
619    #[test]
620    fn same_seed_same_sample_d_precomputed_gso() {
621        use crate::{integer_mod_q::MatZq, rational::MatQ};
622        let basis = MatZq::identity(5, 5, 17);
623        let center = MatQ::new(5, 1);
624        let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()).gso();
625        let sample_0 =
626            MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, &center, 1.25f32, [42; 32])
627                .unwrap();
628        let sample_1 =
629            MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, &center, 1.25f32, [42; 32])
630                .unwrap();
631
632        assert_eq!(sample_0, sample_1);
633    }
634
635    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
636    #[test]
637    fn known_answer_d() {
638        use crate::{integer_mod_q::MatZq, rational::MatQ};
639        let basis = MatZq::identity(5, 5, 17);
640        let center = MatQ::new(5, 1);
641        let sample = MatZq::sample_d_seeded(&basis, &center, 1.25f32, [42; 32]).unwrap();
642
643        assert_eq!("[[0],[16],[0],[0],[0]] mod 17", sample.to_string());
644    }
645
646    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
647    #[test]
648    fn known_answer_d_precomputed_gso() {
649        use crate::{integer_mod_q::MatZq, rational::MatQ};
650        let basis = MatZq::identity(5, 5, 17);
651        let center = MatQ::new(5, 1);
652        let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()).gso();
653        let sample =
654            MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, &center, 1.25f32, [42; 32])
655                .unwrap();
656
657        assert_eq!("[[0],[16],[0],[0],[0]] mod 17", sample.to_string());
658    }
659}