diff --git a/benches/sample_z.rs b/benches/sample_z.rs index e5fd1510..6ecccd9c 100644 --- a/benches/sample_z.rs +++ b/benches/sample_z.rs @@ -16,45 +16,41 @@ use qfall_math::{ /// benchmark creating a matrix of size 100x100 sampled by a comparatively wide discrete Gaussian distribution. pub fn bench_sample_z_wide(c: &mut Criterion) { - let n = Z::from(1000); let center = Q::from(0); let s = Q::from(100); c.bench_function("SampleZ wide 10,000", |b| { - b.iter(|| MatZ::sample_discrete_gauss(100, 100, &n, ¢er, &s).unwrap()) + b.iter(|| MatZ::sample_discrete_gauss(100, 100, ¢er, &s).unwrap()) }); } /// benchmark creating a matrix of size 100x100 sampled by a comparatively narrow discrete Gaussian distribution. pub fn bench_sample_z_narrow(c: &mut Criterion) { - let n = Z::from(100); let center = Q::from(0); let s = Q::from(2); c.bench_function("SampleZ narrow 10,000", |b| { - b.iter(|| MatZ::sample_discrete_gauss(100, 100, &n, ¢er, &s).unwrap()) + b.iter(|| MatZ::sample_discrete_gauss(100, 100, ¢er, &s).unwrap()) }); } /// benchmark creating a single integer sampled by a comparatively wide discrete Gaussian distribution. pub fn bench_sample_z_wide_single(c: &mut Criterion) { - let n = Z::from(1000); let center = Q::from(0); let s = Q::from(100); c.bench_function("SampleZ wide single", |b| { - b.iter(|| Z::sample_discrete_gauss(&n, ¢er, &s).unwrap()) + b.iter(|| Z::sample_discrete_gauss(¢er, &s).unwrap()) }); } /// benchmark creating a single integer sampled by a comparatively wide discrete Gaussian distribution. pub fn bench_sample_z_narrow_single(c: &mut Criterion) { - let n = Z::from(100); let center = Q::from(0); let s = Q::from(2); c.bench_function("SampleZ narrow single", |b| { - b.iter(|| Z::sample_discrete_gauss(&n, ¢er, &s).unwrap()) + b.iter(|| Z::sample_discrete_gauss(¢er, &s).unwrap()) }); } diff --git a/benches/sampling.rs b/benches/sampling.rs index 4fe6a36f..d957340e 100644 --- a/benches/sampling.rs +++ b/benches/sampling.rs @@ -15,11 +15,10 @@ use qfall_math::{integer::*, rational::*}; /// Sample a [`MatZ`] with `sample_d`. pub fn sample_d() { let basis = MatZ::identity(5, 5); - let n = Z::from(1024); let center = MatQ::new(5, 1); let gaussian_parameter = Q::ONE; - let _ = MatZ::sample_d(&basis, &n, ¢er, &gaussian_parameter).unwrap(); + let _ = MatZ::sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); } /// benchmark [sample_d] diff --git a/src/integer/mat_poly_over_z/sample/discrete_gauss.rs b/src/integer/mat_poly_over_z/sample/discrete_gauss.rs index 4df36435..8f29de7c 100644 --- a/src/integer/mat_poly_over_z/sample/discrete_gauss.rs +++ b/src/integer/mat_poly_over_z/sample/discrete_gauss.rs @@ -10,13 +10,16 @@ use crate::{ error::MathError, - integer::{MatPolyOverZ, MatZ, PolyOverZ, Z}, + integer::{MatPolyOverZ, MatZ, PolyOverZ}, rational::{PolyOverQ, Q}, traits::{ Concatenate, FromCoefficientEmbedding, IntoCoefficientEmbedding, MatrixDimensions, MatrixSetEntry, SetCoefficient, }, - utils::{index::evaluate_index, sample::discrete_gauss::DiscreteGaussianIntegerSampler}, + utils::{ + index::evaluate_index, + sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT}, + }, }; use std::fmt::Display; @@ -29,24 +32,23 @@ impl MatPolyOverZ { /// - `num_rows`: specifies the number of rows the new matrix should have /// - `num_cols`: specifies the number of columns the new matrix should have /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a [`MatPolyOverZ`] with each entry sampled independently from the - /// specified discrete Gaussian distribution or an error if `n <= 1` or `s <= 0`. + /// specified discrete Gaussian distribution or an error if `s < 0`. /// /// # Examples /// ``` /// use qfall_math::integer::MatPolyOverZ; /// - /// let matrix = MatPolyOverZ::sample_discrete_gauss(3, 1, 5, 1024, 0, 1.25f32).unwrap(); + /// let matrix = MatPolyOverZ::sample_discrete_gauss(3, 1, 5, 0, 1.25f32).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// # Panics ... /// - if the provided number of rows and columns are not suited to create a matrix. @@ -56,17 +58,18 @@ impl MatPolyOverZ { num_rows: impl TryInto + Display, num_cols: impl TryInto + Display, max_degree: impl TryInto + Display, - n: impl Into, center: impl Into, s: impl Into, ) -> Result { - let n = n.into(); - let center = center.into(); - let s = s.into(); let max_degree = evaluate_index(max_degree).unwrap(); let mut matrix = MatPolyOverZ::new(num_rows, num_cols); - let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &s)?; + let mut dgis = DiscreteGaussianIntegerSampler::init( + center, + s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + )?; for row in 0..matrix.get_num_rows() { for col in 0..matrix.get_num_columns() { @@ -91,14 +94,13 @@ impl MatPolyOverZ { /// Parameters: /// - `basis`: specifies a basis for the lattice from which is sampled /// - `k`: the maximal length the polynomial can have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a vector of polynomials sampled according to the /// discrete Gaussian distribution or an error if the basis is not a row vector, - /// `n <= 1` or `s <= 0`, or the number of rows of the `basis` and `center` differ. + /// `s < 0`, or the number of rows of the `basis` and `center` differ. /// /// # Example /// ``` @@ -111,14 +113,14 @@ impl MatPolyOverZ { /// let basis = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1, 2 2 2]]").unwrap(); /// let center = vec![PolyOverQ::default()]; /// - /// let sample = MatPolyOverZ::sample_d(&basis, 3, 100, ¢er, 10.5_f64).unwrap(); + /// let sample = MatPolyOverZ::sample_d(&basis, 3, ¢er, 10.5_f64).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`VectorFunctionCalledOnNonVector`](MathError::VectorFunctionCalledOnNonVector), /// if the basis is not a row vector. /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) /// if the number of rows of the `basis` and `center` differ. /// @@ -133,7 +135,6 @@ impl MatPolyOverZ { pub fn sample_d( basis: &Self, k: impl Into, - n: impl Into, center: &[PolyOverQ], s: impl Into, ) -> Result { @@ -148,7 +149,7 @@ impl MatPolyOverZ { center_embedded = center_embedded.concat_vertical(&c_row)?; } - let sample = MatZ::sample_d(&basis_embedded, n, ¢er_embedded, s)?; + let sample = MatZ::sample_d(&basis_embedded, ¢er_embedded, s)?; Ok(MatPolyOverZ::from_coefficient_embedding((&sample, k - 1))) } @@ -163,19 +164,19 @@ mod test_sample_discrete_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let _ = MatPolyOverZ::sample_discrete_gauss(2, 3, 16u16, 128, 1u16, 2); - let _ = MatPolyOverZ::sample_discrete_gauss(1, 3, 2u32, 128, 1u8, 2.0); - let _ = MatPolyOverZ::sample_discrete_gauss(2_i64, 3, 2u64, 128, 1u32, 2.0_f64); - let _ = MatPolyOverZ::sample_discrete_gauss(2_i32, 3, 2i8, 128, 1u64, 1); - let _ = MatPolyOverZ::sample_discrete_gauss(2_i16, 3, 2i16, 128, 1i64, 3_u64); - let _ = MatPolyOverZ::sample_discrete_gauss(2_i8, 3, 2i32, 128, 1i32, 1); - let _ = MatPolyOverZ::sample_discrete_gauss(2_u64, 3, 2i64, 128, 1i16, 1); - let _ = MatPolyOverZ::sample_discrete_gauss(2_u32, 3, 4, 128, 1i8, 1); - let _ = MatPolyOverZ::sample_discrete_gauss(2_u16, 3, 2u8, 128, 1i64, 2); - let _ = MatPolyOverZ::sample_discrete_gauss(2_u8, 3, 2, 128, -2, 3); - let _ = MatPolyOverZ::sample_discrete_gauss(1, 3, 2, 128, 4, 3); - let _ = MatPolyOverZ::sample_discrete_gauss(3, 3, 2, 128, 1.25f64, 3); - let _ = MatPolyOverZ::sample_discrete_gauss(4, 3, 2, 128, 15.75f32, 3); + let _ = MatPolyOverZ::sample_discrete_gauss(2, 3, 128, 1u16, 2); + let _ = MatPolyOverZ::sample_discrete_gauss(1, 3, 128, 1u8, 2.0); + let _ = MatPolyOverZ::sample_discrete_gauss(2_i64, 3, 128, 1u32, 2.0_f64); + let _ = MatPolyOverZ::sample_discrete_gauss(2_i32, 3, 128, 1u64, 1); + let _ = MatPolyOverZ::sample_discrete_gauss(2_i16, 3, 128, 1i64, 3_u64); + let _ = MatPolyOverZ::sample_discrete_gauss(2_i8, 3, 128, 1i32, 1); + let _ = MatPolyOverZ::sample_discrete_gauss(2_u64, 3, 128, 1i16, 1); + let _ = MatPolyOverZ::sample_discrete_gauss(2_u32, 3, 128, 1i8, 1); + let _ = MatPolyOverZ::sample_discrete_gauss(2_u16, 3, 128, 1i64, 2); + let _ = MatPolyOverZ::sample_discrete_gauss(2_u8, 3, 128, -2, 3); + let _ = MatPolyOverZ::sample_discrete_gauss(1, 3, 128, 4, 3); + let _ = MatPolyOverZ::sample_discrete_gauss(3, 3, 128, 1.25f64, 3); + let _ = MatPolyOverZ::sample_discrete_gauss(4, 3, 128, 15.75f32, 3); } /// Ensures that the resulting entries have correct degree. @@ -183,7 +184,7 @@ mod test_sample_discrete_gauss { fn correct_degree_entries() { let degrees = [1, 3, 7, 15, 32, 120]; for degree in degrees { - let res = MatPolyOverZ::sample_discrete_gauss(1, 1, degree, 1024, i64::MAX, 1).unwrap(); + let res = MatPolyOverZ::sample_discrete_gauss(1, 1, degree, i64::MAX, 1).unwrap(); assert_eq!( res.get_entry(0, 0).unwrap().get_degree(), @@ -197,7 +198,7 @@ mod test_sample_discrete_gauss { #[test] #[should_panic] fn invalid_max_degree() { - let _ = MatPolyOverZ::sample_discrete_gauss(2, 2, -1, 1024, 0, 1).unwrap(); + let _ = MatPolyOverZ::sample_discrete_gauss(2, 2, -1, 0, 1).unwrap(); } } @@ -217,7 +218,7 @@ mod test_sample_d { let center = vec![PolyOverQ::default()]; for _ in 0..10 { - let sample = MatPolyOverZ::sample_d(&base, 3, 100, ¢er, 10.5_f64).unwrap(); + let sample = MatPolyOverZ::sample_d(&base, 3, ¢er, 10.5_f64).unwrap(); let sample_vec = sample.into_coefficient_embedding(3); let orthogonal = MatZ::from_str("[[0],[1],[1]]").unwrap(); @@ -237,7 +238,7 @@ mod test_sample_d { let orthogonal = MatZ::from_str("[[0, 1, 1, 0, 1, 1, 0 , 0, 0]]").unwrap(); for _ in 0..10 { - let sample = MatPolyOverZ::sample_d(&base, 3, 100, ¢er, 10.5_f64).unwrap(); + let sample = MatPolyOverZ::sample_d(&base, 3, ¢er, 10.5_f64).unwrap(); let sample_embedded = sample.into_coefficient_embedding(3); assert_eq!(MatZ::new(1, 1), &orthogonal * &sample_embedded); @@ -254,18 +255,18 @@ mod test_sample_d { let n = Z::from(1024); let s = Q::ONE; - let _ = MatPolyOverZ::sample_d(&basis, 3, 16u16, ¢er, 1u16); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2u32, ¢er, 1u8); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2u64, ¢er, 1u32); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2i8, ¢er, 1u64); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2i16, ¢er, 1i64); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2i32, ¢er, 1i32); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2i64, ¢er, 1i16); - let _ = MatPolyOverZ::sample_d(&basis, 3, &n, ¢er, 1i8); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2u8, ¢er, 1i64); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2, ¢er, &n); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2, ¢er, &s); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2, ¢er, 1.25f64); - let _ = MatPolyOverZ::sample_d(&basis, 3, 2, ¢er, 15.75f32); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u16); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u8); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u32); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u64); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i64); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i32); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i16); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i8); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i64); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, &n); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, &s); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1.25f64); + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 15.75f32); } } diff --git a/src/integer/mat_z/sample/discrete_gauss.rs b/src/integer/mat_z/sample/discrete_gauss.rs index e02f9dc4..f02e4928 100644 --- a/src/integer/mat_z/sample/discrete_gauss.rs +++ b/src/integer/mat_z/sample/discrete_gauss.rs @@ -10,11 +10,12 @@ use crate::{ error::MathError, - integer::{MatZ, Z}, + integer::MatZ, rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixSetEntry}, utils::sample::discrete_gauss::{ - sample_d, sample_d_precomputed_gso, DiscreteGaussianIntegerSampler, + sample_d, sample_d_precomputed_gso, DiscreteGaussianIntegerSampler, LookupTableSetting, + TAILCUT, }, }; use std::fmt::Display; @@ -22,29 +23,28 @@ use std::fmt::Display; impl MatZ { /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry /// sampled independently according to the discrete Gaussian distribution, - /// using [`Z::sample_discrete_gauss`]. + /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). /// /// Parameters: /// - `num_rows`: specifies the number of rows the new matrix should have /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a matrix with each entry sampled independently from the - /// specified discrete Gaussian distribution or an error if `n <= 1` or `s <= 0`. + /// specified discrete Gaussian distribution or an error if `s < 0`. /// /// # Examples /// ``` /// use qfall_math::integer::MatZ; /// - /// let sample = MatZ::sample_discrete_gauss(3, 1, 1024, 0, 1.25f32).unwrap(); + /// let sample = MatZ::sample_discrete_gauss(3, 1, 0, 1.25f32).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// # Panics ... /// - if the provided number of rows and columns are not suited to create a matrix. @@ -52,16 +52,17 @@ impl MatZ { pub fn sample_discrete_gauss( num_rows: impl TryInto + Display, num_cols: impl TryInto + Display, - n: impl Into, center: impl Into, s: impl Into, ) -> Result { let mut out = Self::new(num_rows, num_cols); - let n: Z = n.into(); - let center: Q = center.into(); - let s: Q = s.into(); - let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &s)?; + let mut dgis = DiscreteGaussianIntegerSampler::init( + center, + s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + )?; for row in 0..out.get_num_rows() { for col in 0..out.get_num_columns() { @@ -80,13 +81,13 @@ impl MatZ { /// /// Parameters: /// - `basis`: specifies a basis for the lattice from which is sampled - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples + /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a lattice vector sampled according to the discrete Gaussian distribution - /// or an error if `n <= 1` or `s <= 0`, the number of rows of the `basis` and `center` differ, + /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, /// or if `center` is not a column vector. /// /// # Examples @@ -95,12 +96,12 @@ impl MatZ { /// let basis = MatZ::identity(5, 5); /// let center = MatQ::new(5, 1); /// - /// let sample = MatZ::sample_d(&basis, 1024, ¢er, 1.25f32).unwrap(); + /// let sample = MatZ::sample_d(&basis, ¢er, 1.25f32).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) /// if the number of rows of the `basis` and `center` differ. /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) @@ -111,54 +112,20 @@ impl MatZ { /// Trapdoors for hard lattices and new cryptographic constructions. /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. /// - pub fn sample_d( - basis: &MatZ, - n: impl Into, - center: &MatQ, - s: impl Into, - ) -> Result { - let n: Z = n.into(); + pub fn sample_d(basis: &MatZ, center: &MatQ, s: impl Into) -> Result { let s: Q = s.into(); - sample_d(basis, &n, center, &s) - } - - /// Runs [`MatZ::sample_d`] with identity basis and center vector `0`. - /// The full documentation can be found at [`MatZ::sample_d`]. - /// - /// Parameters: - /// - `dimension`: specifies the number of rows and columns - /// that the identity basis should have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a lattice vector sampled according to the discrete Gaussian distribution. - /// The lattice specified as `Z^m` for `m = dimension` and its center fixed to `0^m`. - /// - /// # Panics ... - /// - if the provided `dimension` is not suited to create a matrix. - /// For further information see [`MatZ::new`]. - pub fn sample_d_common( - dimension: impl TryInto + Display + Clone, - n: impl Into, - s: impl Into, - ) -> Result { - let basis = MatZ::identity(dimension.clone(), dimension); - let center = MatQ::new(basis.get_num_rows(), 1); - - MatZ::sample_d(&basis, n, ¢er, s) + sample_d(basis, center, &s) } /// Samples a non-spherical discrete Gaussian depending on your choice of /// `sigma_sqrt` using the standard basis and center `0`. /// /// Parameters: - /// - `n`: specifies the range from which [`MatQ::randomized_rounding`] samples - /// - `sigma_sqrt`: specifies the positive definite Gaussian convolution matrix + /// - `sigma_sqrt`: specifies the positive definite Gaussian covariance matrix /// with which the *intermediate* continuous Gaussian is sampled before /// the randomized rounding is applied. Here `sigma_sqrt = sqrt(sigma^2 - r^2*I)` - /// where sigma is the target convolution matrix. The root can be computed using + /// where sigma is the target covariance matrix. The root can be computed using /// the [`MatQ::cholesky_decomposition`]. /// - `r`: specifies the rounding parameter for [`MatQ::randomized_rounding`]. /// @@ -171,17 +138,17 @@ impl MatZ { /// use std::str::FromStr; /// use crate::qfall_math::traits::Pow; /// - /// let convolution_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap(); + /// let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap(); /// let r = Q::from(4); /// - /// let sigma_sqrt = convolution_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2); + /// let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2); /// - /// let sample = MatZ::sample_d_common_non_spherical(16, &sigma_sqrt.cholesky_decomposition(), r).unwrap(); + /// let sample = MatZ::sample_d_common_non_spherical(&sigma_sqrt.cholesky_decomposition(), r).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if the `n <= 1` or `r <= 0`. + /// if the `r < 0`. /// - Returns a [`MathError`] of type [`NoSquareMatrix`](MathError::NoSquareMatrix) /// if the matrix is not symmetric. /// @@ -192,7 +159,6 @@ impl MatZ { /// Berlin Heidelberg, 2010. /// pub fn sample_d_common_non_spherical( - n: impl Into, sigma_sqrt: &MatQ, r: impl Into, ) -> Result { @@ -206,11 +172,11 @@ impl MatZ { let d_1 = MatQ::sample_gauss_same_center(sigma_sqrt.get_num_columns(), 1, 0, 1)?; // compute a continuous Gaussian centered around `0` in every dimension with - // convolution matrix `b_2` (the cholesky decomposition we computed) + // covariance matrix `b_2` (the cholesky decomposition we computed) let x_2 = sigma_sqrt * d_1; // perform randomized rounding - x_2.randomized_rounding(r, n) + x_2.randomized_rounding(r) } /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. @@ -222,13 +188,12 @@ impl MatZ { /// Parameters: /// - `basis`: specifies a basis for the lattice from which is sampled /// - `basis_gso`: specifies the precomputed gso for `basis` - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a lattice vector sampled according to the discrete Gaussian distribution - /// or an error if `n <= 1` or `s <= 0`, the number of rows of the `basis` and `center` differ, + /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, /// or if `center` is not a column vector. /// /// # Examples @@ -238,12 +203,12 @@ impl MatZ { /// let center = MatQ::new(5, 1); /// let basis_gso = MatQ::from(&basis).gso(); /// - /// let sample = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 1024, ¢er, 1.25f32).unwrap(); + /// let sample = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f32).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) /// if the number of rows of the `basis` and `center` differ. /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) @@ -260,14 +225,12 @@ impl MatZ { pub fn sample_d_precomputed_gso( basis: &MatZ, basis_gso: &MatQ, - n: impl Into, center: &MatQ, s: impl Into, ) -> Result { - let n: Z = n.into(); let s: Q = s.into(); - sample_d_precomputed_gso(basis, basis_gso, &n, center, &s) + sample_d_precomputed_gso(basis, basis_gso, center, &s) } } @@ -289,21 +252,21 @@ mod test_sample_discrete_gauss { let center = Q::ZERO; let s = Q::ONE; - let _ = MatZ::sample_discrete_gauss(2u64, 3i8, 16u16, ¢er, 1u16); - let _ = MatZ::sample_discrete_gauss(3u8, 2i16, 200u32, ¢er, 1u8); - let _ = MatZ::sample_discrete_gauss(1, 1, 200u64, ¢er, 1u32); - let _ = MatZ::sample_discrete_gauss(1, 1, 40i8, ¢er, 1u64); - let _ = MatZ::sample_discrete_gauss(1, 1, 200i16, ¢er, 1i64); - let _ = MatZ::sample_discrete_gauss(1, 1, 200i32, ¢er, 1i32); - let _ = MatZ::sample_discrete_gauss(1, 1, 200i64, ¢er, 1i16); - let _ = MatZ::sample_discrete_gauss(1, 1, &n, ¢er, 1i8); - let _ = MatZ::sample_discrete_gauss(1, 1, 2u8, ¢er, 1i64); - let _ = MatZ::sample_discrete_gauss(1, 1, 200, ¢er, &n); - let _ = MatZ::sample_discrete_gauss(1, 1, 200, ¢er, &s); - let _ = MatZ::sample_discrete_gauss(1, 1, 200, 1, &s); - let _ = MatZ::sample_discrete_gauss(1, 1, 200, 2.25, &s); - let _ = MatZ::sample_discrete_gauss(1, 1, 200, ¢er, 1.25f64); - let _ = MatZ::sample_discrete_gauss(1, 1, 200, ¢er, 15.75f32); + let _ = MatZ::sample_discrete_gauss(2u64, 3i8, ¢er, 1u16); + let _ = MatZ::sample_discrete_gauss(3u8, 2i16, ¢er, 1u8); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1u32); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1u64); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i64); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i32); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i16); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i8); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i64); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, &n); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, &s); + let _ = MatZ::sample_discrete_gauss(1, 1, 1, &s); + let _ = MatZ::sample_discrete_gauss(1, 1, 2.25, &s); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1.25f64); + let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 15.75f32); } } @@ -327,19 +290,19 @@ mod test_sample_d { let center = MatQ::new(5, 1); let s = Q::ONE; - let _ = MatZ::sample_d(&basis, 16u16, ¢er, 1u16); - let _ = MatZ::sample_d(&basis, 2u32, ¢er, 1u8); - let _ = MatZ::sample_d(&basis, 2u64, ¢er, 1u32); - let _ = MatZ::sample_d(&basis, 2i8, ¢er, 1u64); - let _ = MatZ::sample_d(&basis, 2i16, ¢er, 1i64); - let _ = MatZ::sample_d(&basis, 2i32, ¢er, 1i32); - let _ = MatZ::sample_d(&basis, 2i64, ¢er, 1i16); - let _ = MatZ::sample_d(&basis, &n, ¢er, 1i8); - let _ = MatZ::sample_d(&basis, 2u8, ¢er, 1i64); - let _ = MatZ::sample_d(&basis, 2, ¢er, &n); - let _ = MatZ::sample_d(&basis, 2, ¢er, &s); - let _ = MatZ::sample_d(&basis, 2, ¢er, 1.25f64); - let _ = MatZ::sample_d(&basis, 2, ¢er, 15.75f32); + let _ = MatZ::sample_d(&basis, ¢er, 1u16); + let _ = MatZ::sample_d(&basis, ¢er, 1u8); + let _ = MatZ::sample_d(&basis, ¢er, 1u32); + let _ = MatZ::sample_d(&basis, ¢er, 1u64); + let _ = MatZ::sample_d(&basis, ¢er, 1i64); + let _ = MatZ::sample_d(&basis, ¢er, 1i32); + let _ = MatZ::sample_d(&basis, ¢er, 1i16); + let _ = MatZ::sample_d(&basis, ¢er, 1i8); + let _ = MatZ::sample_d(&basis, ¢er, 1i64); + let _ = MatZ::sample_d(&basis, ¢er, &n); + let _ = MatZ::sample_d(&basis, ¢er, &s); + let _ = MatZ::sample_d(&basis, ¢er, 1.25f64); + let _ = MatZ::sample_d(&basis, ¢er, 15.75f32); } /// Checks whether `sample_d_precomputed_gso` is available for all types @@ -353,28 +316,19 @@ mod test_sample_d { let s = Q::ONE; let basis_gso = MatQ::from(&basis); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 16u16, ¢er, 1u16); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2u32, ¢er, 1u8); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2u64, ¢er, 1u32); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2i8, ¢er, 1u64); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2i16, ¢er, 1i64); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2i32, ¢er, 1i32); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2i64, ¢er, 1i16); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, 1i8); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2u8, ¢er, 1i64); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2, ¢er, &n); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2, ¢er, &s); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2, ¢er, 1.25f64); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, 2, ¢er, 15.75f32); - } - - // As `sample_d_common` just calls `MatZ::sample_d` with identity basis - // and center 0, further tests are omitted. - - /// Ensures that `sample_d_common` works properly. - #[test] - fn common() { - let _ = MatZ::sample_d_common(10, 1024, 1.25f32).unwrap(); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u16); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u8); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u32); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u64); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i32); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i16); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i8); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &n); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &s); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f64); + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 15.75f32); } } @@ -394,54 +348,50 @@ mod test_sample_d_common_non_spherical { #[test] fn availability() { let r = Q::from(8); - let convolution_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap(); - let convolution_matrix = (convolution_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2)) - .cholesky_decomposition(); - - let _ = MatZ::sample_d_common_non_spherical(16, &convolution_matrix, 8).unwrap(); - - let _ = MatZ::sample_d_common_non_spherical(16u16, &convolution_matrix, 8_u16).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16u32, &convolution_matrix, 8_u32).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16u64, &convolution_matrix, 8_u64).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16i8, &convolution_matrix, 8_i8).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16i16, &convolution_matrix, 8_i16).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16i32, &convolution_matrix, 8_i32).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16i64, &convolution_matrix, 8_i64).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(Z::from(16), &convolution_matrix, Q::from(8)) - .unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16, &convolution_matrix, Z::from(8)).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16, &convolution_matrix, 8f32).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(16, &convolution_matrix, 8f64).unwrap(); + let covariance_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap(); + let covariance_matrix = + (covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2)).cholesky_decomposition(); + + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8).unwrap(); + + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u16).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u32).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u64).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i8).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i16).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i32).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i64).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, Q::from(8)).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, Z::from(8)).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8f32).unwrap(); + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8f64).unwrap(); } /// Checks whether the function panics if a non-square matrix is provided. /// anymore #[test] fn not_square() { - let convolution_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap(); + let covariance_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap(); - assert!(MatZ::sample_d_common_non_spherical(16, &convolution_matrix, 8).is_err()); + assert!(MatZ::sample_d_common_non_spherical(&covariance_matrix, 8).is_err()); } - /// Checks whether the function returns an error if `n` or `r` is too small. + /// Checks whether the function returns an error if `r` is too small. #[test] fn too_small_parameters() { - let convolution_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap(); + let covariance_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap(); - assert!(MatZ::sample_d_common_non_spherical(16, &convolution_matrix, 0).is_err()); - assert!(MatZ::sample_d_common_non_spherical(16, &convolution_matrix, -1).is_err()); - assert!(MatZ::sample_d_common_non_spherical(1, &convolution_matrix, 8).is_err()); - assert!(MatZ::sample_d_common_non_spherical(-1, &convolution_matrix, 8).is_err()); + assert!(MatZ::sample_d_common_non_spherical(&covariance_matrix, -1).is_err()); } - /// Checks whether the dimension of the output matches the provided convolution matrix + /// Checks whether the dimension of the output matches the provided covariance matrix #[test] fn correct_dimensions() { - let convolution_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap(); - let convolution_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap(); + let covariance_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap(); + let covariance_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap(); - let sample_1 = MatZ::sample_d_common_non_spherical(16, &convolution_matrix_1, 8).unwrap(); - let sample_2 = MatZ::sample_d_common_non_spherical(16, &convolution_matrix_2, 8).unwrap(); + let sample_1 = MatZ::sample_d_common_non_spherical(&covariance_matrix_1, 8).unwrap(); + let sample_2 = MatZ::sample_d_common_non_spherical(&covariance_matrix_2, 8).unwrap(); assert_eq!(2, sample_1.get_num_rows()); assert!(sample_1.is_column_vector()); diff --git a/src/integer/poly_over_z/sample/discrete_gauss.rs b/src/integer/poly_over_z/sample/discrete_gauss.rs index ef356848..2938b37f 100644 --- a/src/integer/poly_over_z/sample/discrete_gauss.rs +++ b/src/integer/poly_over_z/sample/discrete_gauss.rs @@ -11,56 +11,61 @@ use crate::{ error::MathError, - integer::{PolyOverZ, Z}, + integer::PolyOverZ, rational::Q, traits::SetCoefficient, - utils::{index::evaluate_index, sample::discrete_gauss::DiscreteGaussianIntegerSampler}, + utils::{ + index::evaluate_index, + sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT}, + }, }; use std::fmt::Display; impl PolyOverZ { /// Initializes a new [`PolyOverZ`] with maximum degree `max_degree` /// and with each entry sampled independently according to the - /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`]. + /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). /// /// Parameters: /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a fresh [`PolyOverZ`] instance of maximum degree `max_degree` /// with coefficients chosen independently according the discrete Gaussian distribution or - /// a [`MathError`] if `n <= 1` or `s <= 0`. + /// a [`MathError`] if `s < 0`. /// /// # Examples /// ``` /// use qfall_math::integer::PolyOverZ; /// - /// let sample = PolyOverZ::sample_discrete_gauss(2, 1024, 0, 1).unwrap(); + /// let sample = PolyOverZ::sample_discrete_gauss(2, 0, 1).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// # Panics ... /// - if `max_degree` is negative, or does not fit into an [`i64`]. pub fn sample_discrete_gauss( max_degree: impl TryInto + Display, - n: impl Into, center: impl Into, s: impl Into, ) -> Result { let max_degree = evaluate_index(max_degree).unwrap(); - let n = n.into(); let center = center.into(); let s = s.into(); let mut poly = PolyOverZ::default(); - let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &s)?; + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + )?; for index in 0..=max_degree { let sample = dgis.sample_z(); @@ -72,30 +77,26 @@ impl PolyOverZ { #[cfg(test)] mod test_sample_discrete_gauss { - use crate::{ - integer::{PolyOverZ, Z}, - rational::Q, - }; + use crate::{integer::PolyOverZ, rational::Q}; /// Checks whether `sample_discrete_gauss` is available for all types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let n = Z::from(1024); let center = Q::ZERO; let s = Q::ONE; - let _ = PolyOverZ::sample_discrete_gauss(1u8, 16u8, 0f32, 1u8); - let _ = PolyOverZ::sample_discrete_gauss(1u16, 16u16, 0f64, 1u16); - let _ = PolyOverZ::sample_discrete_gauss(1u32, 16u32, 0f32, 1u32); - let _ = PolyOverZ::sample_discrete_gauss(1u64, 16u64, 0f64, 1u64); - let _ = PolyOverZ::sample_discrete_gauss(1i8, 16u8, 0f32, 1i8); - let _ = PolyOverZ::sample_discrete_gauss(1i8, 16i16, 0f32, 1i16); - let _ = PolyOverZ::sample_discrete_gauss(1i16, 16i32, 0f32, 1i32); - let _ = PolyOverZ::sample_discrete_gauss(1i32, 16i64, 0f64, 1i64); - let _ = PolyOverZ::sample_discrete_gauss(1i64, n, center, s); - let _ = PolyOverZ::sample_discrete_gauss(1u8, 16i64, 0f32, 1f64); + let _ = PolyOverZ::sample_discrete_gauss(1u8, 0f32, 1u8); + let _ = PolyOverZ::sample_discrete_gauss(1u16, 0f64, 1u16); + let _ = PolyOverZ::sample_discrete_gauss(1u32, 0f32, 1u32); + let _ = PolyOverZ::sample_discrete_gauss(1u64, 0f64, 1u64); + let _ = PolyOverZ::sample_discrete_gauss(1i8, 0f32, 1i8); + let _ = PolyOverZ::sample_discrete_gauss(1i8, 0f32, 1i16); + let _ = PolyOverZ::sample_discrete_gauss(1i16, 0f32, 1i32); + let _ = PolyOverZ::sample_discrete_gauss(1i32, 0f64, 1i64); + let _ = PolyOverZ::sample_discrete_gauss(1i64, center, s); + let _ = PolyOverZ::sample_discrete_gauss(1u8, 0f32, 1f64); } /// Checks whether the number of coefficients is correct. @@ -103,7 +104,7 @@ mod test_sample_discrete_gauss { fn nr_coeffs() { let degrees = [1, 3, 7, 15, 32, 120]; for degree in degrees { - let res = PolyOverZ::sample_discrete_gauss(degree, 1024, i64::MAX, 1).unwrap(); + let res = PolyOverZ::sample_discrete_gauss(degree, i64::MAX, 1).unwrap(); assert_eq!( res.get_degree(), @@ -117,6 +118,6 @@ mod test_sample_discrete_gauss { #[test] #[should_panic] fn invalid_max_degree() { - let _ = PolyOverZ::sample_discrete_gauss(-1, 1024, 0, 1).unwrap(); + let _ = PolyOverZ::sample_discrete_gauss(-1, 0, 1).unwrap(); } } diff --git a/src/integer/z/sample/discrete_gauss.rs b/src/integer/z/sample/discrete_gauss.rs index e206f2d8..bbff6a84 100644 --- a/src/integer/z/sample/discrete_gauss.rs +++ b/src/integer/z/sample/discrete_gauss.rs @@ -9,13 +9,15 @@ //! This module contains algorithms for sampling according to the discrete Gaussian distribution. use crate::{ - error::MathError, integer::Z, rational::Q, - utils::sample::discrete_gauss::DiscreteGaussianIntegerSampler, + error::MathError, + integer::Z, + rational::Q, + utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT}, }; impl Z { /// Chooses a [`Z`] instance according to the discrete Gaussian distribution - /// in `[center - ⌈s * log_2(n)⌉ , center + ⌊s * log_2(n)⌋ ]`. + /// in `[center - ⌈6 * s⌉ , center + ⌊6 * s⌋ ]`. /// /// This function samples discrete Gaussians according to the definition of /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35). @@ -28,34 +30,34 @@ impl Z { /// /// Returns new [`Z`] sample chosen according to the specified discrete Gaussian /// distribution or a [`MathError`] if the specified parameters were not chosen - /// appropriately, i.e. `n > 1` and `s > 0`. + /// appropriately, i.e. `s < 0`. /// /// # Examples /// ``` /// use qfall_math::integer::Z; /// - /// let sample = Z::sample_discrete_gauss(128, 0, 1).unwrap(); + /// let sample = Z::sample_discrete_gauss(0, 1).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// This function implements SampleZ according to: /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). /// Trapdoors for hard lattices and new cryptographic constructions. /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. /// - pub fn sample_discrete_gauss( - n: impl Into, - center: impl Into, - s: impl Into, - ) -> Result { - let n: Z = n.into(); + pub fn sample_discrete_gauss(center: impl Into, s: impl Into) -> Result { let center: Q = center.into(); let s: Q = s.into(); - let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &s)?; + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::NoLookup, + )?; Ok(dgis.sample_z()) } @@ -76,19 +78,19 @@ mod test_sample_discrete_gauss { let z = Z::from(2); let q = Q::from(2); - let _ = Z::sample_discrete_gauss(16u16, 7u8, 1u16); - let _ = Z::sample_discrete_gauss(2u32, 7u16, 1u8); - let _ = Z::sample_discrete_gauss(2u64, 7u32, 1u32); - let _ = Z::sample_discrete_gauss(2i8, 7u64, 1u64); - let _ = Z::sample_discrete_gauss(2i16, 7i8, 1i64); - let _ = Z::sample_discrete_gauss(2i32, 7i16, 1i32); - let _ = Z::sample_discrete_gauss(2i64, 7i32, 1i16); - let _ = Z::sample_discrete_gauss(&z, 7i64, 1i8); - let _ = Z::sample_discrete_gauss(2u8, &q, 1i64); - let _ = Z::sample_discrete_gauss(2, 0i8, &z); - let _ = Z::sample_discrete_gauss(2, &z, &q); - let _ = Z::sample_discrete_gauss(2, 1f32, 1f64); - let _ = Z::sample_discrete_gauss(2, 1f64, 1f32); + let _ = Z::sample_discrete_gauss(7u8, 1u16); + let _ = Z::sample_discrete_gauss(7u16, 1u8); + let _ = Z::sample_discrete_gauss(7u32, 1u32); + let _ = Z::sample_discrete_gauss(7u64, 1u64); + let _ = Z::sample_discrete_gauss(7i8, 1i64); + let _ = Z::sample_discrete_gauss(7i16, 1i32); + let _ = Z::sample_discrete_gauss(7i32, 1i16); + let _ = Z::sample_discrete_gauss(7i64, 1i8); + let _ = Z::sample_discrete_gauss(&q, 1i64); + let _ = Z::sample_discrete_gauss(0i8, &z); + let _ = Z::sample_discrete_gauss(&z, &q); + let _ = Z::sample_discrete_gauss(1f32, 1f64); + let _ = Z::sample_discrete_gauss(1f64, 1f32); } /// Roughly checks the collected samples are distributed @@ -101,7 +103,7 @@ mod test_sample_discrete_gauss { let mut counts = [0; 20]; // count sampled instances for _ in 0..200 { - let sample = Z::sample_discrete_gauss(1024, 10, 2).unwrap(); + let sample = Z::sample_discrete_gauss(10, 2).unwrap(); let sample_int = i64::try_from(&sample).unwrap() as usize; counts[sample_int] += 1; } diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs b/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs index 37f50410..df83cebb 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs @@ -10,7 +10,7 @@ use crate::{ error::MathError, - integer::{MatPolyOverZ, Z}, + integer::MatPolyOverZ, integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, rational::{PolyOverQ, Q}, }; @@ -26,13 +26,13 @@ impl MatPolynomialRingZq { /// - `num_cols`: specifies the number of columns the new matrix should have /// - `modulus`: specifies the Modulus for the matrix and the maximum degree /// any discrete Gaussian polynomial can have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples + /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a [`MatPolynomialRingZq`] with each entry sampled independently from the - /// specified discrete Gaussian distribution or an error if `n <= 1` or `s <= 0`. + /// specified discrete Gaussian distribution or an error if `s < 0`. /// /// # Examples /// ``` @@ -40,12 +40,12 @@ impl MatPolynomialRingZq { /// use std::str::FromStr; /// let modulus = ModulusPolynomialRingZq::from_str("3 1 0 1 mod 17").unwrap(); /// - /// let matrix = MatPolynomialRingZq::sample_discrete_gauss(3, 1, &modulus, 128, 0, 1.25f32).unwrap(); + /// let matrix = MatPolynomialRingZq::sample_discrete_gauss(3, 1, &modulus, 0, 1.25f32).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// # Panics ... /// - if the provided number of rows and columns are not suited to create a matrix. @@ -56,7 +56,6 @@ impl MatPolynomialRingZq { num_rows: impl TryInto + Display, num_cols: impl TryInto + Display, modulus: impl Into, - n: impl Into, center: impl Into, s: impl Into, ) -> Result { @@ -65,7 +64,6 @@ impl MatPolynomialRingZq { num_rows, num_cols, modulus.get_degree() - 1, - n, center, s, )?; @@ -80,14 +78,14 @@ impl MatPolynomialRingZq { /// Parameters: /// - `basis`: specifies a basis for the lattice from which is sampled /// - `k`: the maximal length the polynomial can have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples + /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a vector of polynomials sampled according to the /// discrete Gaussian distribution or an error if the basis is not a row vector, - /// `n <= 1` or `s <= 0`, or the number of rows of the `basis` and `center` differ. + /// `s < 0`, or the number of rows of the `basis` and `center` differ. /// /// # Example /// ``` @@ -105,16 +103,15 @@ impl MatPolynomialRingZq { /// let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); /// let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); /// let center = vec![PolyOverQ::default()]; - /// let n = Z::from(1024); /// let s = Q::from(8); /// - /// let sample = MatPolynomialRingZq::sample_d(&basis, 3, 16, ¢er, s); + /// let sample = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, s); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`VectorFunctionCalledOnNonVector`](MathError::VectorFunctionCalledOnNonVector), if the basis is not a row vector /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) /// if the number of rows of the `basis` and `center` differ. /// @@ -129,11 +126,10 @@ impl MatPolynomialRingZq { pub fn sample_d( basis: &Self, k: impl Into, - n: impl Into, center: &[PolyOverQ], s: impl Into, ) -> Result { - let sample = MatPolyOverZ::sample_d(&basis.matrix, k, n, center, s)?; + let sample = MatPolyOverZ::sample_d(&basis.matrix, k, center, s)?; Ok(MatPolynomialRingZq::from((&sample, &basis.get_mod()))) } } @@ -155,8 +151,8 @@ mod test_sample_discrete_gauss { coeff_emb_mod.set_entry(0, 0, 1).unwrap(); coeff_emb_mod.set_entry(degree, 0, 1).unwrap(); let modulus = ModulusPolynomialRingZq::from_coefficient_embedding(&coeff_emb_mod); - let res = MatPolynomialRingZq::sample_discrete_gauss(1, 1, modulus, 1024, i32::MAX, 1) - .unwrap(); + let res = + MatPolynomialRingZq::sample_discrete_gauss(1, 1, modulus, i32::MAX, 1).unwrap(); let entry: PolyOverZ = res.get_entry(0, 0).unwrap(); assert_eq!( @@ -187,7 +183,7 @@ mod test_sample_d { let center = vec![PolyOverQ::default()]; for _ in 0..10 { - let sample = MatPolynomialRingZq::sample_d(&base, 3, 100, ¢er, 20.5_f64).unwrap(); + let sample = MatPolynomialRingZq::sample_d(&base, 3, ¢er, 20.5_f64).unwrap(); let sample: PolyOverZ = sample.get_entry(0, 0).unwrap(); let sample_vec = MatZq::from((&sample.into_coefficient_embedding(3), 17)); let orthogonal = MatZq::from_str("[[0],[1],[1]] mod 17").unwrap(); @@ -211,18 +207,18 @@ mod test_sample_d { let n = Z::from(1024); let s = Q::from(8); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 16u16, ¢er, 1u16); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2u32, ¢er, 1u8); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2u64, ¢er, 1u32); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2i8, ¢er, 1u64); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2i16, ¢er, 1i64); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2i32, ¢er, 1i32); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2i64, ¢er, 1i16); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, &n, ¢er, 1i8); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2u8, ¢er, 1i64); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2, ¢er, &n); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2, ¢er, &s); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2, ¢er, 1.25f64); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, 2, ¢er, 15.75f32); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u16); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u8); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u32); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u64); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i64); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i32); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i16); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i8); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i64); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, &n); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, &s); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1.25f64); + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 15.75f32); } } diff --git a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs index 46897fb6..cd68d392 100644 --- a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs @@ -10,12 +10,12 @@ use crate::{ error::MathError, - integer::Z, integer_mod_q::{MatZq, Modulus}, rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixSetEntry}, utils::sample::discrete_gauss::{ - sample_d, sample_d_precomputed_gso, DiscreteGaussianIntegerSampler, + sample_d, sample_d_precomputed_gso, DiscreteGaussianIntegerSampler, LookupTableSetting, + TAILCUT, }, }; use std::fmt::Display; @@ -23,29 +23,28 @@ use std::fmt::Display; impl MatZq { /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry /// sampled independently according to the discrete Gaussian distribution, - /// using [`Z::sample_discrete_gauss`]. + /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). /// /// Parameters: /// - `num_rows`: specifies the number of rows the new matrix should have /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a matrix with each entry sampled independently from the - /// specified discrete Gaussian distribution or an error if `n <= 1` or `s <= 0`. + /// specified discrete Gaussian distribution or an error if `s < 0`. /// /// # Examples /// ``` /// use qfall_math::integer_mod_q::MatZq; /// - /// let sample = MatZq::sample_discrete_gauss(3, 1, 83, 1024, 0, 1.25f32).unwrap(); + /// let sample = MatZq::sample_discrete_gauss(3, 1, 83, 0, 1.25f32).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// # Panics ... /// - if the provided number of rows and columns or the modulus are not suited to create a matrix. @@ -55,16 +54,19 @@ impl MatZq { num_rows: impl TryInto + Display, num_cols: impl TryInto + Display, modulus: impl Into, - n: impl Into, center: impl Into, s: impl Into, ) -> Result { - let n: Z = n.into(); let center: Q = center.into(); let s: Q = s.into(); let mut out = Self::new(num_rows, num_cols, modulus); - let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &s)?; + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + )?; for row in 0..out.get_num_rows() { for col in 0..out.get_num_columns() { @@ -83,13 +85,12 @@ impl MatZq { /// /// Parameters: /// - `basis`: specifies a basis for the lattice from which is sampled - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a lattice vector sampled according to the discrete Gaussian distribution - /// or an error if `n <= 1` or `s <= 0`, the number of rows of the `basis` and `center` differ, + /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, /// or if `center` is not a column vector. /// /// # Examples @@ -98,12 +99,12 @@ impl MatZq { /// let basis = MatZq::identity(5, 5, 17); /// let center = MatQ::new(5, 1); /// - /// let sample = MatZq::sample_d(&basis, 1024, ¢er, 1.25f32).unwrap(); + /// let sample = MatZq::sample_d(&basis, ¢er, 1.25f32).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) /// if the number of rows of the `basis` and `center` differ. /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) @@ -114,18 +115,11 @@ impl MatZq { /// Trapdoors for hard lattices and new cryptographic constructions. /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. /// - pub fn sample_d( - basis: &MatZq, - n: impl Into, - center: &MatQ, - s: impl Into, - ) -> Result { - let n: Z = n.into(); + pub fn sample_d(basis: &MatZq, center: &MatQ, s: impl Into) -> Result { let s: Q = s.into(); let sample = sample_d( &basis.get_representative_least_nonnegative_residue(), - &n, center, &s, )?; @@ -133,36 +127,6 @@ impl MatZq { Ok(MatZq::from((&sample, basis.get_mod()))) } - /// Runs [`MatZq::sample_d`] with identity basis and center vector `0`. - /// The full documentation can be found at [`MatZq::sample_d`]. - /// - /// Parameters: - /// - `dimension`: specifies the number of rows and columns - /// that the identity basis should have - /// - `modulus`: specifies the modulus of the new matrix - /// - `n`: specifies the range from which - /// [`Zq::sample_discrete_gauss`](crate::integer_mod_q::Zq::sample_discrete_gauss) samples - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a lattice vector sampled according to the discrete Gaussian distribution. - /// The lattice specified as `Z^m` for `m = dimension` and its center fixed to `0^m`. - /// - /// # Panics - /// - if the provided `dimension` is smaller than 1 or does not fit into an [`i64`]. - /// - if `modulus < 2`. - pub fn sample_d_common( - dimension: impl TryInto + Display + Clone, - modulus: impl Into, - n: impl Into, - s: impl Into, - ) -> Result { - let basis = MatZq::identity(dimension.clone(), dimension, modulus); - let center = MatQ::new(basis.get_num_rows(), 1); - - Self::sample_d(&basis, n, ¢er, s) - } - /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. /// /// We do not check whether `basis` is actually a basis or whether `basis_gso` is @@ -172,13 +136,12 @@ impl MatZq { /// Parameters: /// - `basis`: specifies a basis for the lattice from which is sampled /// - `basis_gso`: specifies the precomputed gso for `basis` - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a lattice vector sampled according to the discrete Gaussian distribution - /// or an error if `n <= 1` or `s <= 0`, the number of rows of the `basis` and `center` differ, + /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, /// or if `center` is not a column vector. /// /// # Examples @@ -188,12 +151,12 @@ impl MatZq { /// let center = MatQ::new(5, 1); /// let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()).gso(); /// - /// let sample = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 1024, ¢er, 1.25f32).unwrap(); + /// let sample = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f32).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) /// if the number of rows of the `basis` and `center` differ. /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) @@ -210,17 +173,14 @@ impl MatZq { pub fn sample_d_precomputed_gso( basis: &MatZq, basis_gso: &MatQ, - n: impl Into, center: &MatQ, s: impl Into, ) -> Result { - let n: Z = n.into(); let s: Q = s.into(); let sample = sample_d_precomputed_gso( &basis.get_representative_least_nonnegative_residue(), basis_gso, - &n, center, &s, )?; @@ -249,20 +209,20 @@ mod test_sample_discrete_gauss { let s = Q::ONE; let modulus = Modulus::from(83); - let _ = MatZq::sample_discrete_gauss(2u64, 3i8, &modulus, 16u16, 0f32, 1u16); - let _ = MatZq::sample_discrete_gauss(3u8, 2i16, 83u8, 2u32, ¢er, 1u8); - let _ = MatZq::sample_discrete_gauss(1, 1, &n, 2u64, ¢er, 1u32); - let _ = MatZq::sample_discrete_gauss(1, 1, 83i8, 2i8, ¢er, 1u64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2i16, ¢er, 1i64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2i32, ¢er, 1i32); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2i64, ¢er, 1i16); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, &n, ¢er, 1i8); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2u8, ¢er, 1i64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2, ¢er, &n); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2, ¢er, &s); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2, ¢er, 1.25f64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2, 0, 1.25f64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 2, center, 15.75f32); + let _ = MatZq::sample_discrete_gauss(2u64, 3i8, &modulus, 0f32, 1u16); + let _ = MatZq::sample_discrete_gauss(3u8, 2i16, 83u8, ¢er, 1u8); + let _ = MatZq::sample_discrete_gauss(1, 1, &n, ¢er, 1u32); + let _ = MatZq::sample_discrete_gauss(1, 1, 83i8, ¢er, 1u64); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i64); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i32); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i16); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i8); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i64); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, &n); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, &s); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1.25f64); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, 0, 1.25f64); + let _ = MatZq::sample_discrete_gauss(1, 1, 83, center, 15.75f32); } } @@ -270,7 +230,7 @@ mod test_sample_discrete_gauss { mod test_sample_d { use crate::{ integer::Z, - integer_mod_q::{MatZq, Modulus}, + integer_mod_q::MatZq, rational::{MatQ, Q}, }; @@ -287,19 +247,19 @@ mod test_sample_d { let center = MatQ::new(5, 1); let s = Q::ONE; - let _ = MatZq::sample_d(&basis, 16u16, ¢er, 1u16); - let _ = MatZq::sample_d(&basis, 2u32, ¢er, 1u8); - let _ = MatZq::sample_d(&basis, 2u64, ¢er, 1u32); - let _ = MatZq::sample_d(&basis, 2i8, ¢er, 1u64); - let _ = MatZq::sample_d(&basis, 2i16, ¢er, 1i64); - let _ = MatZq::sample_d(&basis, 2i32, ¢er, 1i32); - let _ = MatZq::sample_d(&basis, 2i64, ¢er, 1i16); - let _ = MatZq::sample_d(&basis, &n, ¢er, 1i8); - let _ = MatZq::sample_d(&basis, 2u8, ¢er, 1i64); - let _ = MatZq::sample_d(&basis, 2, ¢er, &n); - let _ = MatZq::sample_d(&basis, 2, ¢er, &s); - let _ = MatZq::sample_d(&basis, 2, ¢er, 1.25f64); - let _ = MatZq::sample_d(&basis, 2, ¢er, 15.75f32); + let _ = MatZq::sample_d(&basis, ¢er, 1u16); + let _ = MatZq::sample_d(&basis, ¢er, 1u8); + let _ = MatZq::sample_d(&basis, ¢er, 1u32); + let _ = MatZq::sample_d(&basis, ¢er, 1u64); + let _ = MatZq::sample_d(&basis, ¢er, 1i64); + let _ = MatZq::sample_d(&basis, ¢er, 1i32); + let _ = MatZq::sample_d(&basis, ¢er, 1i16); + let _ = MatZq::sample_d(&basis, ¢er, 1i8); + let _ = MatZq::sample_d(&basis, ¢er, 1i64); + let _ = MatZq::sample_d(&basis, ¢er, &n); + let _ = MatZq::sample_d(&basis, ¢er, &s); + let _ = MatZq::sample_d(&basis, ¢er, 1.25f64); + let _ = MatZq::sample_d(&basis, ¢er, 15.75f32); } /// Checks whether `sample_d_precomputed_gso` is available for all types @@ -313,29 +273,18 @@ mod test_sample_d { let s = Q::ONE; let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 16u16, ¢er, 1u16); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2u32, ¢er, 1u8); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2u64, ¢er, 1u32); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2i8, ¢er, 1u64); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2i16, ¢er, 1i64); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2i32, ¢er, 1i32); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2i64, ¢er, 1i16); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, 1i8); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2u8, ¢er, 1i64); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2, ¢er, &n); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2, ¢er, &s); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2, ¢er, 1.25f64); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, 2, ¢er, 15.75f32); - } - - // As `sample_d_common` just calls `MatZq::sample_d` with identity basis - // and center 0, further tests are omitted. - - /// Ensures that `sample_d_common` works properly. - #[test] - fn common() { - let modulus = Modulus::from(17); - - let _ = MatZq::sample_d_common(10, &modulus, 1024, 1.25f32).unwrap(); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u16); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u8); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u32); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u64); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i32); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i16); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i8); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &n); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &s); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f64); + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 15.75f32); } } diff --git a/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs b/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs index 81d07085..1b74fca4 100644 --- a/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs @@ -11,41 +11,42 @@ use crate::{ error::MathError, - integer::Z, integer_mod_q::{Modulus, PolyOverZq}, rational::Q, traits::SetCoefficient, - utils::{index::evaluate_index, sample::discrete_gauss::DiscreteGaussianIntegerSampler}, + utils::{ + index::evaluate_index, + sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT}, + }, }; use std::fmt::Display; impl PolyOverZq { /// Initializes a new [`PolyOverZq`] with maximum degree `max_degree` /// and with each entry sampled independently according to the - /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`]. + /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). /// /// Parameters: /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZq`] should have /// - `modulus`: specififes the [`Modulus`] over which the ring of integer coefficients is defined - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a fresh [`PolyOverZq`] instance of maximum degree `max_degree` /// with coefficients chosen independently according the discrete Gaussian distribution or - /// a [`MathError`] if `n <= 1` or `s <= 0`. + /// a [`MathError`] if `s < 0`. /// /// # Examples /// ``` /// use qfall_math::integer_mod_q::PolyOverZq; /// - /// let sample = PolyOverZq::sample_discrete_gauss(2, 17, 1024, 0, 1).unwrap(); + /// let sample = PolyOverZq::sample_discrete_gauss(2, 17, 0, 1).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// # Panics ... /// - if `max_degree` is negative, or does not fit into an [`i64`]. @@ -53,19 +54,22 @@ impl PolyOverZq { pub fn sample_discrete_gauss( max_degree: impl TryInto + Display, modulus: impl Into, - n: impl Into, center: impl Into, s: impl Into, ) -> Result { let max_degree = evaluate_index(max_degree).unwrap(); let modulus = modulus.into(); - let n = n.into(); let center = center.into(); let s = s.into(); let mut poly = PolyOverZq::from(&modulus); - let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &s)?; + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + )?; for index in 0..=max_degree { let sample = dgis.sample_z(); @@ -84,20 +88,19 @@ mod test_sample_discrete_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let n = Z::from(1024); let center = Q::ZERO; let s = Q::ONE; - let _ = PolyOverZq::sample_discrete_gauss(1u8, 17u8, 16u8, 0f32, 1u8); - let _ = PolyOverZq::sample_discrete_gauss(1u16, 17u16, 16u16, 0f64, 1u16); - let _ = PolyOverZq::sample_discrete_gauss(1u32, 17u32, 16u32, 0f32, 1u32); - let _ = PolyOverZq::sample_discrete_gauss(1u64, 17u64, 16u64, 0f64, 1u64); - let _ = PolyOverZq::sample_discrete_gauss(1i8, 17u8, 16u8, 0f32, 1i8); - let _ = PolyOverZq::sample_discrete_gauss(1i8, 17i8, 16i16, 0f32, 1i16); - let _ = PolyOverZq::sample_discrete_gauss(1i16, 17i16, 16i32, 0f32, 1i32); - let _ = PolyOverZq::sample_discrete_gauss(1i32, 17i32, 16i64, 0f64, 1i64); - let _ = PolyOverZq::sample_discrete_gauss(1i64, 17i64, n, center, s); - let _ = PolyOverZq::sample_discrete_gauss(1u8, 17u8, 16i64, 0f32, 1f64); + let _ = PolyOverZq::sample_discrete_gauss(1u8, 17u8, 0f32, 1u8); + let _ = PolyOverZq::sample_discrete_gauss(1u16, 17u16, 0f64, 1u16); + let _ = PolyOverZq::sample_discrete_gauss(1u32, 17u32, 0f32, 1u32); + let _ = PolyOverZq::sample_discrete_gauss(1u64, 17u64, 0f64, 1u64); + let _ = PolyOverZq::sample_discrete_gauss(1i8, 17u8, 0f32, 1i8); + let _ = PolyOverZq::sample_discrete_gauss(1i8, 17i8, 0f32, 1i16); + let _ = PolyOverZq::sample_discrete_gauss(1i16, 17i16, 0f32, 1i32); + let _ = PolyOverZq::sample_discrete_gauss(1i32, 17i32, 0f64, 1i64); + let _ = PolyOverZq::sample_discrete_gauss(1i64, 17i64, center, s); + let _ = PolyOverZq::sample_discrete_gauss(1u8, 17u8, 0f32, 1f64); } /// Checks whether the boundaries of the interval are kept for small moduli. @@ -106,7 +109,7 @@ mod test_sample_discrete_gauss { let modulus = 128; for _ in 0..32 { - let poly = PolyOverZq::sample_discrete_gauss(3, modulus, 1024, 15, 1).unwrap(); + let poly = PolyOverZq::sample_discrete_gauss(3, modulus, 15, 1).unwrap(); for i in 0..3 { let sample: Z = poly.get_coeff(i).unwrap(); @@ -122,7 +125,7 @@ mod test_sample_discrete_gauss { let modulus = u64::MAX; for _ in 0..256 { - let poly = PolyOverZq::sample_discrete_gauss(3, modulus, 1024, 1, 1).unwrap(); + let poly = PolyOverZq::sample_discrete_gauss(3, modulus, 1, 1).unwrap(); for i in 0..3 { let sample: Z = poly.get_coeff(i).unwrap(); @@ -137,8 +140,7 @@ mod test_sample_discrete_gauss { fn nr_coeffs() { let degrees = [1, 3, 7, 15, 32, 120]; for degree in degrees { - let res = - PolyOverZq::sample_discrete_gauss(degree, u64::MAX, 1024, i64::MAX, 1).unwrap(); + let res = PolyOverZq::sample_discrete_gauss(degree, u64::MAX, i64::MAX, 1).unwrap(); assert_eq!( res.get_degree(), @@ -152,13 +154,13 @@ mod test_sample_discrete_gauss { #[test] #[should_panic] fn invalid_max_degree() { - let _ = PolyOverZq::sample_discrete_gauss(-1, 17, 1024, 0, 1).unwrap(); + let _ = PolyOverZq::sample_discrete_gauss(-1, 17, 0, 1).unwrap(); } /// Checks whether too small modulus is insufficient. #[test] #[should_panic] fn invalid_modulus() { - let _ = PolyOverZq::sample_discrete_gauss(3, 1, 1024, 0, 1).unwrap(); + let _ = PolyOverZq::sample_discrete_gauss(3, 1, 0, 1).unwrap(); } } diff --git a/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs b/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs index b1e29374..432f9201 100644 --- a/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs @@ -11,7 +11,7 @@ use crate::{ error::MathError, - integer::{PolyOverZ, Z}, + integer::PolyOverZ, integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}, rational::Q, }; @@ -24,14 +24,13 @@ impl PolynomialRingZq { /// Parameters: /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the /// ring of polynomials modulo `modulus.get_q()` is defined - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`] samples /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` /// with coefficients chosen independently according the discrete Gaussian distribution or - /// a [`MathError`] if `n <= 1` or `s <= 0`. + /// a [`MathError`] if `s < 0`. /// /// # Examples /// ``` @@ -39,18 +38,17 @@ impl PolynomialRingZq { /// use std::str::FromStr; /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 3 mod 17").unwrap(); /// - /// let sample = PolynomialRingZq::sample_discrete_gauss(&modulus, 1024, 0, 1).unwrap(); + /// let sample = PolynomialRingZq::sample_discrete_gauss(&modulus, 0, 1).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// # Panics ... /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. pub fn sample_discrete_gauss( modulus: impl Into, - n: impl Into, center: impl Into, s: impl Into, ) -> Result { @@ -60,7 +58,7 @@ impl PolynomialRingZq { "ModulusPolynomial of degree 0 is insufficient to sample over." ); - let poly_z = PolyOverZ::sample_discrete_gauss(modulus.get_degree() - 1, n, center, s)?; + let poly_z = PolyOverZ::sample_discrete_gauss(modulus.get_degree() - 1, center, s)?; let mut poly_ringzq = PolynomialRingZq { poly: poly_z, modulus, @@ -86,21 +84,20 @@ mod test_sample_discrete_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let n = Z::from(1024); let center = Q::ZERO; let s = Q::ONE; let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16u8, 0f32, 1u8); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16u16, 0f64, 1u16); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16u32, 0f32, 1u32); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16u64, 0f64, 1u64); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16u8, 0f32, 1i8); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16i16, 0f32, 1i16); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16i32, 0f32, 1i32); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16i64, 0f64, 1i64); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, n, center, s); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 16i64, 0f32, 1f64); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1u8); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f64, 1u16); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1u32); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f64, 1u64); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1i8); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1i16); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1i32); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f64, 1i64); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, center, s); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1f64); } /// Checks whether the boundaries of the interval are kept for small moduli. @@ -109,7 +106,7 @@ mod test_sample_discrete_gauss { let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); for _ in 0..32 { - let poly = PolynomialRingZq::sample_discrete_gauss(&modulus, 1024, 15, 1).unwrap(); + let poly = PolynomialRingZq::sample_discrete_gauss(&modulus, 15, 1).unwrap(); for i in 0..3 { let sample: Z = poly.get_coeff(i).unwrap(); @@ -126,7 +123,7 @@ mod test_sample_discrete_gauss { ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); for _ in 0..256 { - let poly = PolynomialRingZq::sample_discrete_gauss(&modulus, 1024, 1, 1).unwrap(); + let poly = PolynomialRingZq::sample_discrete_gauss(&modulus, 1, 1).unwrap(); for i in 0..3 { let sample: Z = poly.get_coeff(i).unwrap(); @@ -145,7 +142,7 @@ mod test_sample_discrete_gauss { modulus.set_coeff(degree, 1).unwrap(); let modulus = ModulusPolynomialRingZq::from(&modulus); - let res = PolynomialRingZq::sample_discrete_gauss(&modulus, 1024, i64::MAX, 1).unwrap(); + let res = PolynomialRingZq::sample_discrete_gauss(&modulus, i64::MAX, 1).unwrap(); assert_eq!( res.get_degree() + 1, @@ -161,6 +158,6 @@ mod test_sample_discrete_gauss { fn invalid_modulus() { let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 1024, 0, 1).unwrap(); + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0, 1).unwrap(); } } diff --git a/src/integer_mod_q/z_q/sample/discrete_gauss.rs b/src/integer_mod_q/z_q/sample/discrete_gauss.rs index 19d7e3cc..9d801ce5 100644 --- a/src/integer_mod_q/z_q/sample/discrete_gauss.rs +++ b/src/integer_mod_q/z_q/sample/discrete_gauss.rs @@ -10,40 +10,38 @@ use crate::{ error::MathError, - integer::Z, integer_mod_q::{Modulus, Zq}, rational::Q, - utils::sample::discrete_gauss::DiscreteGaussianIntegerSampler, + utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT}, }; impl Zq { /// Chooses a [`Zq`] instance chosen according to the discrete Gaussian distribution - /// in `[center - ⌈s * log_2(n)⌉ , center + ⌊s * log_2(n)⌋ ]`. + /// in `[center - ⌈6 * s⌉ , center + ⌊ 6 * s⌋ ]`. /// /// This function samples discrete Gaussians according to the definition of /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35). /// /// Parameters: /// - `modulus`: specifies the modulus of the new [`Zq`] element - /// - `n`: specifies the range from which is sampled /// - `center`: specifies the position of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` /// /// Returns new [`Zq`] sample chosen according to the specified discrete Gaussian /// distribution or a [`MathError`] if the specified parameters were not chosen - /// appropriately, i.e. `n > 1` and `s > 0`. + /// appropriately, i.e. `s < 0`. /// /// # Examples /// ``` /// use qfall_math::integer_mod_q::Zq; /// - /// let sample = Zq::sample_discrete_gauss(17, 128, 0, 1).unwrap(); + /// let sample = Zq::sample_discrete_gauss(17, 0, 1).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. + /// if `s < 0`. /// /// # Panics ... /// - if `modulus` is smaller than `2`. @@ -55,16 +53,19 @@ impl Zq { /// pub fn sample_discrete_gauss( modulus: impl Into, - n: impl Into, center: impl Into, s: impl Into, ) -> Result { let modulus: Modulus = modulus.into(); - let n: Z = n.into(); let center: Q = center.into(); let s: Q = s.into(); - let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &s)?; + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::NoLookup, + )?; let sample = dgis.sample_z(); Ok(Zq::from((&sample, &modulus))) @@ -86,26 +87,26 @@ mod test_sample_discrete_gauss { let z = Z::from(2); let q = Q::from(2); - let _ = Zq::sample_discrete_gauss(17u64, 16u16, 7u8, 1u16); - let _ = Zq::sample_discrete_gauss(17u16, 2u32, 7u16, 1u8); - let _ = Zq::sample_discrete_gauss(17u8, 2u64, 7u32, 1u32); - let _ = Zq::sample_discrete_gauss(17u32, 2i8, 7u64, 1u64); - let _ = Zq::sample_discrete_gauss(17i16, 2i16, 7i8, 1i64); - let _ = Zq::sample_discrete_gauss(17i8, 2i32, 7i16, 1i32); - let _ = Zq::sample_discrete_gauss(17i64, 2i64, 7i32, 1i16); - let _ = Zq::sample_discrete_gauss(17i32, z.clone(), 7i64, 1i8); - let _ = Zq::sample_discrete_gauss(17, 2u8, q.clone(), 1i64); - let _ = Zq::sample_discrete_gauss(z.clone(), 2, 0i8, z.clone()); - let _ = Zq::sample_discrete_gauss(17, 2, z.clone(), q.clone()); - let _ = Zq::sample_discrete_gauss(17, 2, 1f32, 1f64); - let _ = Zq::sample_discrete_gauss(17, 2, 1f64, 1f32); + let _ = Zq::sample_discrete_gauss(17u64, 7u8, 1u16); + let _ = Zq::sample_discrete_gauss(17u16, 7u16, 1u8); + let _ = Zq::sample_discrete_gauss(17u8, 7u32, 1u32); + let _ = Zq::sample_discrete_gauss(17u32, 7u64, 1u64); + let _ = Zq::sample_discrete_gauss(17i16, 7i8, 1i64); + let _ = Zq::sample_discrete_gauss(17i8, 7i16, 1i32); + let _ = Zq::sample_discrete_gauss(17i64, 7i32, 1i16); + let _ = Zq::sample_discrete_gauss(17i32, 7i64, 1i8); + let _ = Zq::sample_discrete_gauss(17, q.clone(), 1i64); + let _ = Zq::sample_discrete_gauss(z.clone(), 0i8, z.clone()); + let _ = Zq::sample_discrete_gauss(17, z.clone(), q.clone()); + let _ = Zq::sample_discrete_gauss(17, 1f32, 1f64); + let _ = Zq::sample_discrete_gauss(17, 1f64, 1f32); // With references - let _ = Zq::sample_discrete_gauss(17i64, 2i64, 7i32, 1i16); - let _ = Zq::sample_discrete_gauss(17i32, &z, 7i64, 1i8); - let _ = Zq::sample_discrete_gauss(17, 2u8, &q, 1i64); - let _ = Zq::sample_discrete_gauss(&z, 2, 0i8, &z); - let _ = Zq::sample_discrete_gauss(17, 2, &z, &q); + let _ = Zq::sample_discrete_gauss(17i64, 7i32, 1i16); + let _ = Zq::sample_discrete_gauss(17i32, 7i64, 1i8); + let _ = Zq::sample_discrete_gauss(17, &q, 1i64); + let _ = Zq::sample_discrete_gauss(&z, 0i8, &z); + let _ = Zq::sample_discrete_gauss(17, &z, &q); } /// Roughly checks the collected samples are distributed @@ -118,7 +119,7 @@ mod test_sample_discrete_gauss { let mut counts = [0; 20]; // count sampled instances for _ in 0..200 { - let sample = Zq::sample_discrete_gauss(20, 1024, 0, 2).unwrap(); + let sample = Zq::sample_discrete_gauss(20, 0, 2).unwrap(); let sample_int = i64::try_from(&sample.get_representative_least_nonnegative_residue()) .unwrap() as usize; counts[sample_int] += 1; diff --git a/src/rational/mat_q/rounding.rs b/src/rational/mat_q/rounding.rs index 13de5188..a86f3cf5 100644 --- a/src/rational/mat_q/rounding.rs +++ b/src/rational/mat_q/rounding.rs @@ -11,7 +11,7 @@ use super::MatQ; use crate::{ error::MathError, - integer::{MatZ, Z}, + integer::MatZ, rational::Q, traits::{MatrixDimensions, MatrixGetEntry, MatrixSetEntry}, }; @@ -173,11 +173,10 @@ impl MatQ { /// by `self` with gaussian parameter `r`. /// /// Parameters: - /// - `n`: the security parameter; also specifies the range from which is sampled /// - `r`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = r` /// - /// Returns the rounded matrix as a [`MatZ`] or an error if `n <= 1` or `r <= 0`. + /// Returns the rounded matrix as a [`MatZ`] or an error if `r < 0`. /// /// # Examples /// ``` @@ -185,26 +184,24 @@ impl MatQ { /// use std::str::FromStr; /// /// let value = MatQ::from_str("[[5/2, 1]]").unwrap(); - /// let rounded = value.randomized_rounding(3,5).unwrap(); + /// let rounded = value.randomized_rounding(3).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `r <= 0`. + /// if `r < 0`. /// /// This function implements randomized rounding according to: /// - \[1\] Peikert, C. (2010, August). /// An efficient and parallel Gaussian sampler for lattices. /// In: Annual Cryptology Conference (pp. 80-97). /// - pub fn randomized_rounding(&self, r: impl Into, n: impl Into) -> Result { + pub fn randomized_rounding(&self, r: impl Into) -> Result { let mut out = MatZ::new(self.get_num_rows(), self.get_num_columns()); let r = r.into(); - let n = n.into(); for i in 0..out.get_num_rows() { for j in 0..out.get_num_columns() { - let entry = - unsafe { self.get_entry_unchecked(i, j) }.randomized_rounding(&r, &n)?; + let entry = unsafe { self.get_entry_unchecked(i, j) }.randomized_rounding(&r)?; unsafe { out.set_entry_unchecked(i, j, entry) }; } } @@ -289,19 +286,10 @@ mod test_randomized_rounding { use crate::rational::MatQ; use std::str::FromStr; - /// Ensure that a `n <= 1` throws an error - #[test] - fn small_n() { - let value = MatQ::from_str("[[5/2, 1]]").unwrap(); - assert!(value.randomized_rounding(3, 1).is_err()); - assert!(value.randomized_rounding(3, -3).is_err()); - } - - /// Ensure that a `r <= 0` throws an error + /// Ensure that a `r < 0` throws an error #[test] fn negative_r() { let value = MatQ::from_str("[[5/2, 1]]").unwrap(); - assert!(value.randomized_rounding(0, 5).is_err()); - assert!(value.randomized_rounding(-1, 5).is_err()); + assert!(value.randomized_rounding(-1).is_err()); } } diff --git a/src/rational/mat_q/sample/gauss.rs b/src/rational/mat_q/sample/gauss.rs index a02c562f..15fc8f83 100644 --- a/src/rational/mat_q/sample/gauss.rs +++ b/src/rational/mat_q/sample/gauss.rs @@ -8,13 +8,17 @@ //! This module contains sampling algorithms for Gaussian distributions over [`MatQ`]. -use std::fmt::Display; - use crate::{ error::MathError, rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixGetEntry, MatrixSetEntry}, }; +use probability::{ + prelude::{Gaussian, Sample}, + source, +}; +use rand::RngCore; +use std::fmt::Display; impl MatQ { /// Chooses a [`MatQ`] instance according to the continuous Gaussian distribution. @@ -90,10 +94,22 @@ impl MatQ { ) -> Result { let mut out = MatQ::new(num_rows, num_cols); let (center, sigma) = (center.into(), sigma.into()); + if sigma <= 0.0 { + return Err(MathError::NonPositive(format!( + "The sigma has to be positive and not zero, but the provided value is {sigma}." + ))); + } + let mut rng = rand::rng(); + let mut source = source::default(rng.next_u64()); + + // Instead of sampling with a center of c, we sample with center 0 and add the + // center later. These are equivalent and this way we can sample in larger ranges + let sampler = Gaussian::new(0.0, sigma); for i in 0..out.get_num_rows() { for j in 0..out.get_num_columns() { - let sample = Q::sample_gauss(¢er, sigma)?; + let mut sample = Q::from(sampler.sample(&mut source)); + sample += ¢er; unsafe { out.set_entry_unchecked(i, j, sample) }; } } diff --git a/src/rational/poly_over_q/rounding.rs b/src/rational/poly_over_q/rounding.rs index 7ff7d7cc..f3067b81 100644 --- a/src/rational/poly_over_q/rounding.rs +++ b/src/rational/poly_over_q/rounding.rs @@ -11,7 +11,7 @@ use super::PolyOverQ; use crate::{ error::MathError, - integer::{PolyOverZ, Z}, + integer::PolyOverZ, rational::Q, traits::{GetCoefficient, SetCoefficient}, }; @@ -98,11 +98,10 @@ impl PolyOverQ { /// by `self` with gaussian parameter `r`. /// /// Parameters: - /// - `n`: the security parameter; also specifies the range from which is sampled /// - `r`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = r` /// - /// Returns the rounded polynomial as a [`PolyOverZ`] or an error if `n <= 1` or `r <= 0`. + /// Returns the rounded polynomial as a [`PolyOverZ`] or an error if `r < 0`. /// /// # Examples /// ``` @@ -110,29 +109,24 @@ impl PolyOverQ { /// use std::str::FromStr; /// /// let value = PolyOverQ::from_str("2 5/2 1").unwrap(); - /// let rounded = value.randomized_rounding(3,5).unwrap(); + /// let rounded = value.randomized_rounding(3).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `r <= 0`. + /// if `r < 0`. /// /// This function implements randomized rounding according to: /// - \[1\] Peikert, C. (2010, August). /// An efficient and parallel Gaussian sampler for lattices. /// In: Annual Cryptology Conference (pp. 80-97). /// - pub fn randomized_rounding( - &self, - r: impl Into, - n: impl Into, - ) -> Result { + pub fn randomized_rounding(&self, r: impl Into) -> Result { let r = r.into(); - let n = n.into(); let mut out = - PolyOverZ::from(unsafe { self.get_coeff_unchecked(0).randomized_rounding(&r, &n)? }); + PolyOverZ::from(unsafe { self.get_coeff_unchecked(0).randomized_rounding(&r)? }); for i in 1..self.get_degree() + 1 { - let coeff = unsafe { self.get_coeff_unchecked(i).randomized_rounding(&r, &n)? }; + let coeff = unsafe { self.get_coeff_unchecked(i).randomized_rounding(&r)? }; unsafe { out.set_coeff_unchecked(i, coeff) }; } @@ -217,19 +211,10 @@ mod test_randomized_rounding { use crate::rational::PolyOverQ; use std::str::FromStr; - /// Ensure that a `n <= 1` throws an error - #[test] - fn small_n() { - let value = PolyOverQ::from_str("2 5/2 1").unwrap(); - assert!(value.randomized_rounding(3, 1).is_err()); - assert!(value.randomized_rounding(3, -3).is_err()); - } - - /// Ensure that a `r <= 0` throws an error + /// Ensure that a `r < 0` throws an error #[test] fn negative_r() { let value = PolyOverQ::from_str("2 5/2 1").unwrap(); - assert!(value.randomized_rounding(0, 5).is_err()); - assert!(value.randomized_rounding(-1, 5).is_err()); + assert!(value.randomized_rounding(-1).is_err()); } } diff --git a/src/rational/poly_over_q/sample/gauss.rs b/src/rational/poly_over_q/sample/gauss.rs index 1c671faa..61d743c4 100644 --- a/src/rational/poly_over_q/sample/gauss.rs +++ b/src/rational/poly_over_q/sample/gauss.rs @@ -15,6 +15,11 @@ use crate::{ traits::SetCoefficient, utils::index::evaluate_index, }; +use probability::{ + prelude::{Gaussian, Sample}, + source, +}; +use rand::RngCore; use std::fmt::Display; impl PolyOverQ { @@ -56,8 +61,21 @@ impl PolyOverQ { let sigma: f64 = sigma.into(); let mut poly = PolyOverQ::default(); + if sigma <= 0.0 { + return Err(MathError::NonPositive(format!( + "The sigma has to be positive and not zero, but the provided value is {sigma}." + ))); + } + let mut rng = rand::rng(); + let mut source = source::default(rng.next_u64()); + + // Instead of sampling with a center of c, we sample with center 0 and add the + // center later. These are equivalent and this way we can sample in larger ranges + let sampler = Gaussian::new(0.0, sigma); + for index in 0..=max_degree { - let sample: Q = Q::sample_gauss(¢er, sigma)?; + let mut sample = Q::from(sampler.sample(&mut source)); + sample += ¢er; unsafe { poly.set_coeff_unchecked(index, &sample) }; } Ok(poly) diff --git a/src/rational/q/rounding.rs b/src/rational/q/rounding.rs index 2ed12237..a42a3630 100644 --- a/src/rational/q/rounding.rs +++ b/src/rational/q/rounding.rs @@ -153,31 +153,30 @@ impl Q { /// by `self` with gaussian parameter `r`. /// /// Parameters: - /// - `n`: the security parameter; also specifies the range from which is sampled /// - `r`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = r` /// - /// Returns the rounded value as an [`Z`] or an error if `n <= 1` or `r <= 0`. + /// Returns the rounded value as an [`Z`] or an error if `r < 0`. /// /// # Examples /// ``` /// use qfall_math::rational::Q; /// /// let value = Q::from((5, 2)); - /// let rounded = value.randomized_rounding(3,5).unwrap(); + /// let rounded = value.randomized_rounding(3).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `r <= 0`. + /// if `r < 0`. /// /// This function implements randomized rounding according to: /// - Peikert, C. (2010, August). /// An efficient and parallel Gaussian sampler for lattices. /// In: Annual Cryptology Conference (pp. 80-97). /// - pub fn randomized_rounding(&self, r: impl Into, n: impl Into) -> Result { - Z::sample_discrete_gauss(n, self, r) + pub fn randomized_rounding(&self, r: impl Into) -> Result { + Z::sample_discrete_gauss(self, r) } } @@ -332,19 +331,10 @@ mod test_simplify { mod test_randomized_rounding { use crate::rational::Q; - /// Ensure that a `n <= 1` throws an error - #[test] - fn small_n() { - let value = Q::from((2, 3)); - assert!(value.randomized_rounding(3, 1).is_err()); - assert!(value.randomized_rounding(3, -3).is_err()); - } - - /// Ensure that a `r <= 0` throws an error + /// Ensure that a `r < 0` throws an error #[test] fn negative_r() { let value = Q::from((2, 3)); - assert!(value.randomized_rounding(0, 5).is_err()); - assert!(value.randomized_rounding(-1, 5).is_err()); + assert!(value.randomized_rounding(-1).is_err()); } } diff --git a/src/utils/sample/discrete_gauss.rs b/src/utils/sample/discrete_gauss.rs index c76d69c9..ecb088d4 100644 --- a/src/utils/sample/discrete_gauss.rs +++ b/src/utils/sample/discrete_gauss.rs @@ -23,14 +23,34 @@ use crate::{ rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixGetSubmatrix, Pow}, }; -use rand::RngCore; -use serde::Serialize; +use rand::Rng; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +/// Defines whether a lookup-table should be precomputed, filled on-the-fly, +/// or not used at all for a discrete Gaussian sampler. +#[derive(PartialEq, Clone, Copy, Serialize, Deserialize, Debug)] +pub enum LookupTableSetting { + Precompute, + FillOnTheFly, + NoLookup, +} + +/// This is the global variable used in all `sample_discrete_gauss` and `sample_d` +/// functions. Its value should be in `ω(log(sqrt(n)))`. We set it (as most other libraries) +/// statically to `6.0`. +/// +/// You can use and change in an `unsafe` environment. +/// ```compile_fail +/// unsafe { TAILCUT = 4.0 }; +/// ``` +/// Make sure that the tailcut stays positive and large enough for your purposes. +/// If you use multi-threading, read up on the behaviour of a `static mut` variable. +/// Our tests only cover cases where `TAILCUT = 6.0`. +pub static mut TAILCUT: f64 = 6.0; + /// Enables for discrete Gaussian sampling out of -/// `[⌈center - s * log_2(n)⌉ , ⌊center + s * log_2(n)⌋ ]`. -/// This struct evaluates the Gauss function lazily (i.e. only if required) -/// and saves it in a [`HashMap`]. +/// `[⌈center - s * tailcut⌉ , ⌊center + s * tailcut⌋ ]`. /// /// **WARNING:** If the attributes are not set using [`DiscreteGaussianIntegerSampler::init`], /// we can't guarantee sampling from the correct discrete Gaussian distribution. @@ -38,29 +58,35 @@ use std::collections::HashMap; /// other attributes, too. /// /// Attributes: -/// - `n`: specifies the range from which is sampled /// - `center`: specifies the position of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` +/// - `lower_bound`: specifies the lower bound to sample uniformly from +/// - `interval_size`: specifies the interval size to sample uniformly from +/// - `lookup_table_setting`: Specifies whether a lookup-table should be used and +/// how it should be filled, i.e. lazily on-the-fly (impacting sampling time slightly) or precomputed +/// - `table`: the lookup-table if one is used /// /// # Examples /// ``` /// use qfall_math::{integer::Z, rational::Q}; -/// use qfall_math::utils::sample::discrete_gauss::DiscreteGaussianIntegerSampler; +/// use qfall_math::utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting}; /// let n = Z::from(1024); -/// let center = Q::ZERO; -/// let gaussian_parameter = Q::ONE; +/// let center = 0.0; +/// let gaussian_parameter = 1.0; +/// let tailcut = 6.0; /// -/// let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &gaussian_parameter).unwrap(); +/// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::NoLookup).unwrap(); /// /// let sample = dgis.sample_z(); /// ``` -#[derive(Debug, Serialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct DiscreteGaussianIntegerSampler { pub center: Q, pub s: Q, pub lower_bound: Z, pub interval_size: Z, + pub lookup_table_setting: LookupTableSetting, pub table: HashMap, } @@ -69,8 +95,8 @@ impl DiscreteGaussianIntegerSampler { /// - `center` as the center of the discrete Gaussian to sample from, /// - `s` defining the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s`, - /// - `lower_bound` as `⌈center - s * log_2(n)⌉`, - /// - `interval_size` as `⌊center + s * log_2(n)⌋ - ⌈center - s * log_2(n)⌉ + 1`, and + /// - `lower_bound` as `⌈center - 6 * s⌉`, + /// - `interval_size` as `⌊center + 6 * s⌋ - ⌈center - 6 * s⌉ + 1`, and /// - `table` as an empty [`HashMap`] to store evaluations of the Gaussian function. /// /// Parameters: @@ -86,42 +112,72 @@ impl DiscreteGaussianIntegerSampler { /// # Examples /// ``` /// use qfall_math::{integer::Z, rational::Q}; - /// use qfall_math::utils::sample::discrete_gauss::DiscreteGaussianIntegerSampler; - /// let n = Z::from(1024); - /// let center = Q::ZERO; - /// let gaussian_parameter = Q::ONE; + /// use qfall_math::utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting}; + /// let center = 0.0; + /// let gaussian_parameter = 1.0; + /// let tailcut = 6.0; /// - /// let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &gaussian_parameter).unwrap(); + /// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute).unwrap(); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n <= 1` or `s <= 0`. - pub fn init(n: &Z, center: &Q, s: &Q) -> Result { - if n <= &Z::ONE { + /// if `tailcut < 0` or `s < 0`. + pub fn init( + center: impl Into, + s: impl Into, + tailcut: impl Into, + lookup_table_setting: LookupTableSetting, + ) -> Result { + let center = center.into(); + let mut s = s.into(); + let tailcut = tailcut.into(); + if tailcut < Q::ZERO { return Err(MathError::InvalidIntegerInput(format!( - "The value {n} was provided for parameter n of the function sample_z. - This function expects this input to be larger than 1." + "The value {tailcut} was provided for parameter tailcut of the function sample_z. + This function expects this input no smaller than 0." ))); } - if s <= &Q::ZERO { + if s < Q::ZERO { return Err(MathError::InvalidIntegerInput(format!( "The value {s} was provided for parameter s of the function sample_z. - This function expects this input to be larger than 0." + This function expects this input to be no smaller than 0." ))); } + if s == Q::ZERO { + // Ensure that s != 0 s.t. we can divide by s^2 in the Gaussian function + s = Q::from(0.00001); + } - let lower_bound = (center - s * n.log(2).unwrap()).ceil(); - let upper_bound = (center + s * n.log(2).unwrap()).floor(); + let lower_bound = (¢er - &s * &tailcut).ceil(); + let upper_bound = (¢er + &s * tailcut).floor(); // interval [lower_bound, upper_bound] has upper_bound - lower_bound + 1 elements in it let interval_size = &upper_bound - &lower_bound + Z::ONE; + let mut table = HashMap::new(); + + if lookup_table_setting == LookupTableSetting::FillOnTheFly && interval_size > u16::MAX { + println!("WARNING: A completely filled lookup table will exceed 2^16 entries. You should reconsider your sampling method for discrete Gaussians.") + } + + if lookup_table_setting == LookupTableSetting::Precompute { + assert!(interval_size <= u16::MAX, "The interval size {interval_size} for discrete Gaussian sampling exceeds 2^16 entries. You should reconsider your sampling method."); + + let mut i = lower_bound.clone(); + while i <= upper_bound { + let evaluated_gauss_function = gaussian_function(&i, ¢er, &s); + table.insert(i.clone(), evaluated_gauss_function); + i += Z::ONE; + } + } + Ok(Self { - center: center.clone(), - s: s.clone(), + center, + s, lower_bound, interval_size, - table: HashMap::new(), + lookup_table_setting, + table, }) } @@ -134,12 +190,12 @@ impl DiscreteGaussianIntegerSampler { /// # Examples /// ``` /// use qfall_math::{integer::Z, rational::Q}; - /// use qfall_math::utils::sample::discrete_gauss::DiscreteGaussianIntegerSampler; - /// let n = Z::from(1024); - /// let center = Q::ZERO; - /// let gaussian_parameter = Q::ONE; + /// use qfall_math::utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting}; + /// let center = 0.0; + /// let gaussian_parameter = 1.0; + /// let tailcut = 6.0; /// - /// let mut dgis = DiscreteGaussianIntegerSampler::init(&n, ¢er, &gaussian_parameter).unwrap(); + /// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute).unwrap(); /// /// let sample = dgis.sample_z(); /// ``` @@ -147,21 +203,29 @@ impl DiscreteGaussianIntegerSampler { let mut rng = rand::rng(); let mut uis = UniformIntegerSampler::init(&self.interval_size).unwrap(); loop { - // sample x in [c - s * log_2(n), c + s * log_2(n)] + // sample x in [c - s * tailcut, c + s * tailcut] let sample = &self.lower_bound + uis.sample(); - // grab value of Gauss function for sample if it exists - let evaluated_gauss_function = self.table.get(&sample); - let evaluated_gauss_function = match evaluated_gauss_function { - Some(x) => x, - None => &{ - let evaluated_gauss_function = - gaussian_function(&sample, &self.center, &self.s); - self.table.insert(sample.clone(), evaluated_gauss_function); - evaluated_gauss_function - }, + let evaluated_gauss_function: &f64 = match self.lookup_table_setting { + LookupTableSetting::NoLookup => &gaussian_function(&sample, &self.center, &self.s), + LookupTableSetting::FillOnTheFly => { + let pot_evaluated_gauss_function = self.table.get(&sample); + match pot_evaluated_gauss_function { + Some(x) => x, + None => &{ + // if the entry doesn't exist yet, compute and insert it + let evaluated_function = + gaussian_function(&sample, &self.center, &self.s); + self.table.insert(sample.clone(), evaluated_function); + evaluated_function + }, + } + } + LookupTableSetting::Precompute => self.table.get(&sample).unwrap(), }; - if evaluated_gauss_function >= &(rng.next_u64() as f64 / u64::MAX as f64) { + + let random_f64: f64 = rng.random(); + if evaluated_gauss_function >= &random_f64 { return sample; } } @@ -237,9 +301,9 @@ pub fn gaussian_function(x: &Z, c: &Q, s: &Q) -> f64 { /// if the number of rows of the `basis` and `center` differ. /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) /// if `center` is not a column vector. -pub(crate) fn sample_d(basis: &MatZ, n: &Z, center: &MatQ, s: &Q) -> Result { +pub(crate) fn sample_d(basis: &MatZ, center: &MatQ, s: &Q) -> Result { let basis_gso = MatQ::from(basis).gso(); - sample_d_precomputed_gso(basis, &basis_gso, n, center, s) + sample_d_precomputed_gso(basis, &basis_gso, center, s) } /// SampleD samples a discrete Gaussian from the lattice with `basis` using [`sample_z`] as a subroutine. @@ -288,7 +352,6 @@ pub(crate) fn sample_d(basis: &MatZ, n: &Z, center: &MatQ, s: &Q) -> Result Result { @@ -340,7 +403,12 @@ pub(crate) fn sample_d_precomputed_gso( let s_2 = s / (basisvector_orth_i.norm_eucl_sqrd().unwrap().sqrt()); // sample z ~ D_{Z, s2, c2} - let mut dgis = DiscreteGaussianIntegerSampler::init(n, &c_2, &s_2)?; + let mut dgis = DiscreteGaussianIntegerSampler::init( + &c_2, + &s_2, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + )?; let z = dgis.sample_z(); // update the center c = c - z * b[i] @@ -357,33 +425,24 @@ pub(crate) fn sample_d_precomputed_gso( #[cfg(test)] mod test_discrete_gaussian_integer_sampler { use super::DiscreteGaussianIntegerSampler; - use crate::{integer::Z, rational::Q}; - - /// Ensures that the doc tests works correctly. - #[test] - fn doc_test() { - let n = Z::from(1024); - let center = Q::ZERO; - let gaussian_parameter = Q::ONE; - - let mut dgis = - DiscreteGaussianIntegerSampler::init(&n, ¢er, &gaussian_parameter).unwrap(); - - let sample = dgis.sample_z(); - - assert!(-10 <= sample); - assert!(sample <= 10); - } + use crate::{ + rational::Q, + utils::sample::discrete_gauss::{LookupTableSetting, TAILCUT}, + }; /// Checks whether samples are kept in correct interval for a small interval. #[test] fn small_interval() { - let n = Z::from(1024); let center = Q::from(15); let gaussian_parameter = Q::from((1, 2)); - let mut dgis = - DiscreteGaussianIntegerSampler::init(&n, ¢er, &gaussian_parameter).unwrap(); + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &gaussian_parameter, + 8.0, + LookupTableSetting::FillOnTheFly, + ) + .unwrap(); for _ in 0..64 { let sample = dgis.sample_z(); @@ -396,12 +455,16 @@ mod test_discrete_gaussian_integer_sampler { /// Checks whether samples are kept in correct interval for a large interval. #[test] fn large_interval() { - let n = Z::from(i64::MAX as u64 + 1); let center = Q::MINUS_ONE; let gaussian_parameter = Q::ONE; - let mut dgis = - DiscreteGaussianIntegerSampler::init(&n, ¢er, &gaussian_parameter).unwrap(); + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &gaussian_parameter, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + ) + .unwrap(); for _ in 0..256 { let sample = dgis.sample_z(); @@ -411,37 +474,45 @@ mod test_discrete_gaussian_integer_sampler { } } - /// Checks whether `sample_z` returns an error if the gaussian parameter `s <= 0`. + /// Checks whether `sample_z` returns an error if the gaussian parameter `s < 0`. #[test] fn invalid_gaussian_parameter() { - let n = Z::from(4); let center = Q::ZERO; - assert!(DiscreteGaussianIntegerSampler::init(&n, ¢er, &Q::ZERO).is_err()); - assert!(DiscreteGaussianIntegerSampler::init(&n, ¢er, &Q::MINUS_ONE).is_err()); - assert!(DiscreteGaussianIntegerSampler::init(&n, ¢er, &Q::from(i64::MIN)).is_err()); + assert!(DiscreteGaussianIntegerSampler::init( + ¢er, + &Q::MINUS_ONE, + 6.0, + LookupTableSetting::FillOnTheFly + ) + .is_err()); + assert!(DiscreteGaussianIntegerSampler::init( + ¢er, + &Q::from(i64::MIN), + 6.0, + LookupTableSetting::FillOnTheFly + ) + .is_err()); } - /// Checks whether `sample_z` returns an error if `n <= 1`. + /// Checks whether `sample_z` returns an error if `n < 0`. #[test] - fn invalid_n() { + fn invalid_tailcut() { let center = Q::MINUS_ONE; let gaussian_parameter = Q::ONE; - assert!( - DiscreteGaussianIntegerSampler::init(&Z::ONE, ¢er, &gaussian_parameter).is_err() - ); - assert!( - DiscreteGaussianIntegerSampler::init(&Z::ZERO, ¢er, &gaussian_parameter).is_err() - ); - assert!( - DiscreteGaussianIntegerSampler::init(&Z::MINUS_ONE, ¢er, &gaussian_parameter) - .is_err() - ); assert!(DiscreteGaussianIntegerSampler::init( - &Z::from(i64::MIN), ¢er, - &gaussian_parameter + &gaussian_parameter, + -0.1, + LookupTableSetting::FillOnTheFly + ) + .is_err()); + assert!(DiscreteGaussianIntegerSampler::init( + ¢er, + &gaussian_parameter, + i64::MIN, + LookupTableSetting::FillOnTheFly ) .is_err()); } @@ -534,42 +605,36 @@ mod test_sample_d { #[test] fn doc_test() { let basis = MatZ::identity(5, 5); - let n = Z::from(1024); let center = MatQ::new(5, 1); let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let _ = sample_d(&basis, &n, ¢er, &gaussian_parameter).unwrap(); - let _ = - sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter).unwrap(); } /// Ensures that `sample_d` works properly for a non-zero center. #[test] fn non_zero_center() { let basis = MatZ::identity(5, 5); - let n = Z::from(1024); let center = MatQ::identity(5, 1); let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let _ = sample_d(&basis, &n, ¢er, &gaussian_parameter).unwrap(); - let _ = - sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter).unwrap(); } /// Ensures that `sample_d` works properly for a different basis. #[test] fn non_identity_basis() { let basis = MatZ::from_str("[[2, 1],[1, 2]]").unwrap(); - let n = Z::from(1024); let center = MatQ::new(2, 1); let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let _ = sample_d(&basis, &n, ¢er, &gaussian_parameter).unwrap(); - let _ = - sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter).unwrap(); } /// Ensures that `sample_d` outputs a vector that's part of the specified lattice. @@ -580,14 +645,13 @@ mod test_sample_d { #[test] fn point_of_lattice() { let basis = MatZ::from_str("[[7, 0],[7, 3]]").unwrap(); - let n = Z::from(1024); let center = MatQ::new(2, 1); let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let sample = sample_d(&basis, &n, ¢er, &gaussian_parameter).unwrap(); + let sample = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); let sample_prec = - sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &gaussian_parameter).unwrap(); + sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter).unwrap(); // check whether hermite normal form of HNF(b) = HNF([b|sample_vector]) let basis_concat_sample = basis.concat_horizontal(&sample).unwrap(); @@ -632,83 +696,30 @@ mod test_sample_d { .is_zero()); } - /// Checks whether `sample_d` returns an error if the gaussian parameter `s <= 0`. + /// Checks whether `sample_d` returns an error if the gaussian parameter `s < 0`. #[test] fn invalid_gaussian_parameter() { let basis = MatZ::identity(5, 5); - let n = Z::from(1024); let center = MatQ::new(5, 1); let basis_gso = MatQ::from(&basis).gso(); - assert!(sample_d(&basis, &n, ¢er, &Q::ZERO).is_err()); - assert!(sample_d(&basis, &n, ¢er, &Q::MINUS_ONE).is_err()); - assert!(sample_d(&basis, &n, ¢er, &Q::from(i64::MIN)).is_err()); - - assert!(sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &Q::ZERO).is_err()); - assert!(sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &Q::MINUS_ONE).is_err()); - assert!( - sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &Q::from(i64::MIN)).is_err() - ); - } - - /// Checks whether `sample_d` returns an error if `n <= 1`. - #[test] - fn invalid_n() { - let basis = MatZ::identity(5, 5); - let center = MatQ::new(5, 1); - let gaussian_parameter = Q::ONE; - let basis_gso = MatQ::from(&basis).gso(); + assert!(sample_d(&basis, ¢er, &Q::MINUS_ONE).is_err()); + assert!(sample_d(&basis, ¢er, &Q::from(i64::MIN)).is_err()); - assert!(sample_d(&basis, &Z::ONE, ¢er, &gaussian_parameter).is_err()); - assert!(sample_d(&basis, &Z::ZERO, ¢er, &gaussian_parameter).is_err()); - assert!(sample_d(&basis, &Z::MINUS_ONE, ¢er, &gaussian_parameter).is_err()); - assert!(sample_d(&basis, &Z::from(i64::MIN), ¢er, &gaussian_parameter).is_err()); - assert!(sample_d_precomputed_gso( - &basis, - &basis_gso, - &Z::ONE, - ¢er, - &gaussian_parameter - ) - .is_err()); - assert!(sample_d_precomputed_gso( - &basis, - &basis_gso, - &Z::ZERO, - ¢er, - &gaussian_parameter - ) - .is_err()); - assert!(sample_d_precomputed_gso( - &basis, - &basis_gso, - &Z::MINUS_ONE, - ¢er, - &gaussian_parameter - ) - .is_err()); - assert!(sample_d_precomputed_gso( - &basis, - &basis_gso, - &Z::from(i64::MIN), - ¢er, - &gaussian_parameter - ) - .is_err()); + assert!(sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &Q::MINUS_ONE).is_err()); + assert!(sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &Q::from(i64::MIN)).is_err()); } /// Checks whether `sample_d` returns an error if the basis and center number of rows differs. #[test] fn mismatching_matrix_dimensions() { let basis = MatZ::identity(3, 5); - let n = Z::from(1024); let center = MatQ::new(4, 1); let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let res = sample_d(&basis, &n, ¢er, &gaussian_parameter); - let res_prec = - sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &gaussian_parameter); + let res = sample_d(&basis, ¢er, &gaussian_parameter); + let res_prec = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter); assert!(res.is_err()); assert!(res_prec.is_err()); @@ -718,14 +729,12 @@ mod test_sample_d { #[test] fn center_not_column_vector() { let basis = MatZ::identity(2, 2); - let n = Z::from(1024); let center = MatQ::new(2, 2); let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let res = sample_d(&basis, &n, ¢er, &gaussian_parameter); - let res_prec = - sample_d_precomputed_gso(&basis, &basis_gso, &n, ¢er, &gaussian_parameter); + let res = sample_d(&basis, ¢er, &gaussian_parameter); + let res_prec = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter); assert!(res.is_err()); assert!(res_prec.is_err()); @@ -755,9 +764,9 @@ mod test_sample_d { len * n.log(2).unwrap().sqrt() * (n.log(2).unwrap().log(2).unwrap()); for _ in 0..20 { - let res = sample_d(&basis, &n, ¢er, &gaussian_parameter).unwrap(); + let res = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); let res_prec = - sample_d_precomputed_gso(&basis, &orth, &n, ¢er, &gaussian_parameter).unwrap(); + sample_d_precomputed_gso(&basis, &orth, ¢er, &gaussian_parameter).unwrap(); assert!( res.norm_eucl_sqrd().unwrap() <= gaussian_parameter.pow(2).unwrap().round() * &n, @@ -780,7 +789,7 @@ mod test_sample_d { let center = MatQ::new(&n, 1); let false_gso = MatQ::new(basis.get_num_rows() + 1, basis.get_num_columns()); - let _ = sample_d_precomputed_gso(&basis, &false_gso, &n, ¢er, &Q::from(5)).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &false_gso, ¢er, &Q::from(5)).unwrap(); } /// Ensure that an orthogonalized base with not matching columns panics. #[test] @@ -791,6 +800,6 @@ mod test_sample_d { let center = MatQ::new(&n, 1); let false_gso = MatQ::new(basis.get_num_rows(), basis.get_num_columns() + 1); - let _ = sample_d_precomputed_gso(&basis, &false_gso, &n, ¢er, &Q::from(5)).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &false_gso, ¢er, &Q::from(5)).unwrap(); } }