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, ¢er, 1.25f32).unwrap();
123 #[seeded]
124 /// let sample = MatZ::sample_d_seeded(&basis, ¢er, 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, ¢er, 1.25f32).unwrap();
268 #[seeded]
269 /// let sample = MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 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 ¢er,
326 1u16,
327 seeded.then(test_seed),
328 );
329 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
330 3u8,
331 2i16,
332 ¢er,
333 1u8,
334 seeded.then(test_seed),
335 );
336 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
337 1,
338 1,
339 ¢er,
340 1u32,
341 seeded.then(test_seed),
342 );
343 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
344 1,
345 1,
346 ¢er,
347 1u64,
348 seeded.then(test_seed),
349 );
350 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
351 1,
352 1,
353 ¢er,
354 1i64,
355 seeded.then(test_seed),
356 );
357 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
358 1,
359 1,
360 ¢er,
361 1i32,
362 seeded.then(test_seed),
363 );
364 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
365 1,
366 1,
367 ¢er,
368 1i16,
369 seeded.then(test_seed),
370 );
371 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
372 1,
373 1,
374 ¢er,
375 1i8,
376 seeded.then(test_seed),
377 );
378 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
379 1,
380 1,
381 ¢er,
382 1i64,
383 seeded.then(test_seed),
384 );
385 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
386 1,
387 1,
388 ¢er,
389 &n,
390 seeded.then(test_seed),
391 );
392 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
393 1,
394 1,
395 ¢er,
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 ¢er,
412 1.25f64,
413 seeded.then(test_seed),
414 );
415 let _ = MatZ::sample_discrete_gauss_optionally_seeded(
416 1,
417 1,
418 ¢er,
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, ¢er, 1u16, test_seed());
426 } else {
427 let _ = MatZ::sample_discrete_gauss(2u64, 3i8, ¢er, 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, ¢er, 1u16, seeded.then(test_seed));
475 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1u8, seeded.then(test_seed));
476 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1u32, seeded.then(test_seed));
477 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1u64, seeded.then(test_seed));
478 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i64, seeded.then(test_seed));
479 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i32, seeded.then(test_seed));
480 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i16, seeded.then(test_seed));
481 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i8, seeded.then(test_seed));
482 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i64, seeded.then(test_seed));
483 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, &n, seeded.then(test_seed));
484 let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, &s, seeded.then(test_seed));
485 let _ =
486 MatZ::sample_d_optionally_seeded(&basis, ¢er, 1.25f64, seeded.then(test_seed));
487 let _ =
488 MatZ::sample_d_optionally_seeded(&basis, ¢er, 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, ¢er, 1u16, test_seed());
493 } else {
494 let _ = MatZ::sample_d(&basis, ¢er, 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 ¢er,
515 1u16,
516 seeded.then(test_seed),
517 );
518 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
519 &basis,
520 &basis_gso,
521 ¢er,
522 1u8,
523 seeded.then(test_seed),
524 );
525 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
526 &basis,
527 &basis_gso,
528 ¢er,
529 1u32,
530 seeded.then(test_seed),
531 );
532 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
533 &basis,
534 &basis_gso,
535 ¢er,
536 1u64,
537 seeded.then(test_seed),
538 );
539 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
540 &basis,
541 &basis_gso,
542 ¢er,
543 1i64,
544 seeded.then(test_seed),
545 );
546 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
547 &basis,
548 &basis_gso,
549 ¢er,
550 1i32,
551 seeded.then(test_seed),
552 );
553 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
554 &basis,
555 &basis_gso,
556 ¢er,
557 1i16,
558 seeded.then(test_seed),
559 );
560 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
561 &basis,
562 &basis_gso,
563 ¢er,
564 1i8,
565 seeded.then(test_seed),
566 );
567 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
568 &basis,
569 &basis_gso,
570 ¢er,
571 1i64,
572 seeded.then(test_seed),
573 );
574 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
575 &basis,
576 &basis_gso,
577 ¢er,
578 &n,
579 seeded.then(test_seed),
580 );
581 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
582 &basis,
583 &basis_gso,
584 ¢er,
585 &s,
586 seeded.then(test_seed),
587 );
588 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
589 &basis,
590 &basis_gso,
591 ¢er,
592 1.25f64,
593 seeded.then(test_seed),
594 );
595 let _ = MatZ::sample_d_precomputed_gso_optionally_seeded(
596 &basis,
597 &basis_gso,
598 ¢er,
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 ¢er,
609 1u16,
610 test_seed(),
611 );
612 } else {
613 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 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, ¢er, 1.25f32, [42; 32]).unwrap();
625 let sample_1 = MatZ::sample_d_seeded(&basis, ¢er, 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, ¢er, 1.25f32, [42; 32])
639 .unwrap();
640 let sample_1 =
641 MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 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, ¢er, 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, ¢er, 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}