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 ¢er,
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, ¢er, 1.25f32).unwrap();
127 #[seeded]
128 /// let sample = MatZq::sample_d_seeded(&basis, ¢er, 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, ¢er, 1.25f32).unwrap();
196 #[seeded]
197 /// let sample = MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 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 ¢er,
292 1u8,
293 seeded.then(test_seed),
294 );
295 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
296 1,
297 1,
298 &n,
299 ¢er,
300 1u32,
301 seeded.then(test_seed),
302 );
303 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
304 1,
305 1,
306 83i8,
307 ¢er,
308 1u64,
309 seeded.then(test_seed),
310 );
311 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
312 1,
313 1,
314 83,
315 ¢er,
316 1i64,
317 seeded.then(test_seed),
318 );
319 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
320 1,
321 1,
322 83,
323 ¢er,
324 1i32,
325 seeded.then(test_seed),
326 );
327 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
328 1,
329 1,
330 83,
331 ¢er,
332 1i16,
333 seeded.then(test_seed),
334 );
335 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
336 1,
337 1,
338 83,
339 ¢er,
340 1i8,
341 seeded.then(test_seed),
342 );
343 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
344 1,
345 1,
346 83,
347 ¢er,
348 1i64,
349 seeded.then(test_seed),
350 );
351 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
352 1,
353 1,
354 83,
355 ¢er,
356 &n,
357 seeded.then(test_seed),
358 );
359 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
360 1,
361 1,
362 83,
363 ¢er,
364 &s,
365 seeded.then(test_seed),
366 );
367 let _ = MatZq::sample_discrete_gauss_optionally_seeded(
368 1,
369 1,
370 83,
371 ¢er,
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, ¢er, 1u16, seeded.then(test_seed));
453 let _ = MatZq::sample_d_optionally_seeded(&basis, ¢er, 1u8, seeded.then(test_seed));
454 let _ =
455 MatZq::sample_d_optionally_seeded(&basis, ¢er, 1u32, seeded.then(test_seed));
456 let _ =
457 MatZq::sample_d_optionally_seeded(&basis, ¢er, 1u64, seeded.then(test_seed));
458 let _ =
459 MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i64, seeded.then(test_seed));
460 let _ =
461 MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i32, seeded.then(test_seed));
462 let _ =
463 MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i16, seeded.then(test_seed));
464 let _ = MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i8, seeded.then(test_seed));
465 let _ =
466 MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i64, seeded.then(test_seed));
467 let _ = MatZq::sample_d_optionally_seeded(&basis, ¢er, &n, seeded.then(test_seed));
468 let _ = MatZq::sample_d_optionally_seeded(&basis, ¢er, &s, seeded.then(test_seed));
469 let _ =
470 MatZq::sample_d_optionally_seeded(&basis, ¢er, 1.25f64, seeded.then(test_seed));
471 let _ = MatZq::sample_d_optionally_seeded(
472 &basis,
473 ¢er,
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, ¢er, 1u16, test_seed());
481 } else {
482 let _ = MatZq::sample_d(&basis, ¢er, 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 ¢er,
503 1u16,
504 seeded.then(test_seed),
505 );
506 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
507 &basis,
508 &basis_gso,
509 ¢er,
510 1u8,
511 seeded.then(test_seed),
512 );
513 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
514 &basis,
515 &basis_gso,
516 ¢er,
517 1u32,
518 seeded.then(test_seed),
519 );
520 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
521 &basis,
522 &basis_gso,
523 ¢er,
524 1u64,
525 seeded.then(test_seed),
526 );
527 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
528 &basis,
529 &basis_gso,
530 ¢er,
531 1i64,
532 seeded.then(test_seed),
533 );
534 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
535 &basis,
536 &basis_gso,
537 ¢er,
538 1i32,
539 seeded.then(test_seed),
540 );
541 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
542 &basis,
543 &basis_gso,
544 ¢er,
545 1i16,
546 seeded.then(test_seed),
547 );
548 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
549 &basis,
550 &basis_gso,
551 ¢er,
552 1i8,
553 seeded.then(test_seed),
554 );
555 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
556 &basis,
557 &basis_gso,
558 ¢er,
559 1i64,
560 seeded.then(test_seed),
561 );
562 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
563 &basis,
564 &basis_gso,
565 ¢er,
566 &n,
567 seeded.then(test_seed),
568 );
569 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
570 &basis,
571 &basis_gso,
572 ¢er,
573 &s,
574 seeded.then(test_seed),
575 );
576 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
577 &basis,
578 &basis_gso,
579 ¢er,
580 1.25f64,
581 seeded.then(test_seed),
582 );
583 let _ = MatZq::sample_d_precomputed_gso_optionally_seeded(
584 &basis,
585 &basis_gso,
586 ¢er,
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 ¢er,
597 1u16,
598 test_seed(),
599 );
600 } else {
601 let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 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, ¢er, 1.25f32, [42; 32]).unwrap();
613 let sample_1 = MatZq::sample_d_seeded(&basis, ¢er, 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, ¢er, 1.25f32, [42; 32])
627 .unwrap();
628 let sample_1 =
629 MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 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, ¢er, 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, ¢er, 1.25f32, [42; 32])
655 .unwrap();
656
657 assert_eq!("[[0],[16],[0],[0],[0]] mod 17", sample.to_string());
658 }
659}