qfall_math/integer/mat_z/sample/
uniform.rs

1// Copyright © 2023 Niklas Siemer
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! This module contains sampling algorithms for uniform distributions.
10
11use crate::{
12    error::MathError,
13    integer::{MatZ, Z},
14    traits::{MatrixDimensions, MatrixSetEntry},
15    utils::sample::uniform::UniformIntegerSampler,
16};
17use std::fmt::Display;
18
19impl MatZ {
20    /// Outputs a [`MatZ`] instance with entries chosen uniform at random
21    /// in `[lower_bound, upper_bound)`.
22    ///
23    /// The internally used uniform at random chosen bytes are generated
24    /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and
25    /// is considered cryptographically secure.
26    ///
27    /// Parameters:
28    /// - `num_rows`: specifies the number of rows the new matrix should have
29    /// - `num_cols`: specifies the number of columns the new matrix should have
30    /// - `lower_bound`: specifies the included lower bound of the
31    ///   interval over which is sampled
32    /// - `upper_bound`: specifies the excluded upper bound of the
33    ///   interval over which is sampled
34    ///
35    /// Returns a new [`MatZ`] instance with entries chosen
36    /// uniformly at random in `[lower_bound, upper_bound)` or a [`MathError`]
37    /// if the dimensions of the matrix or the interval were chosen too small.
38    ///
39    /// # Examples
40    /// ```
41    /// use qfall_math::integer::MatZ;
42    ///
43    /// let matrix = MatZ::sample_uniform(3, 3, 17, 26).unwrap();
44    /// ```
45    ///
46    /// # Errors and Failures
47    /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval)
48    ///   if the given `upper_bound` isn't at least larger than `lower_bound`.
49    ///
50    /// # Panics ...
51    /// - if the provided number of rows and columns are not suited to create a matrix.
52    ///   For further information see [`MatZ::new`].
53    pub fn sample_uniform(
54        num_rows: impl TryInto<i64> + Display,
55        num_cols: impl TryInto<i64> + Display,
56        lower_bound: impl Into<Z>,
57        upper_bound: impl Into<Z>,
58    ) -> Result<Self, MathError> {
59        let lower_bound: Z = lower_bound.into();
60        let upper_bound: Z = upper_bound.into();
61        let mut matrix = MatZ::new(num_rows, num_cols);
62
63        let interval_size = &upper_bound - &lower_bound;
64        let mut uis = UniformIntegerSampler::init(&interval_size)?;
65        for row in 0..matrix.get_num_rows() {
66            for col in 0..matrix.get_num_columns() {
67                let sample = uis.sample();
68                unsafe { matrix.set_entry_unchecked(row, col, &lower_bound + sample) };
69            }
70        }
71
72        Ok(matrix)
73    }
74}
75
76#[cfg(test)]
77mod test_sample_uniform {
78    use crate::traits::{MatrixDimensions, MatrixGetEntry};
79    use crate::{
80        integer::{MatZ, Z},
81        integer_mod_q::Modulus,
82    };
83
84    /// Checks whether the boundaries of the interval are kept for small intervals.
85    #[test]
86    fn boundaries_kept_small() {
87        let lower_bound = Z::from(17);
88        let upper_bound = Z::from(32);
89        for _ in 0..32 {
90            let matrix = MatZ::sample_uniform(1, 1, &lower_bound, &upper_bound).unwrap();
91            let sample = matrix.get_entry(0, 0).unwrap();
92            assert!(lower_bound <= sample);
93            assert!(sample < upper_bound);
94        }
95    }
96
97    /// Checks whether the boundaries of the interval are kept for large intervals.
98    #[test]
99    fn boundaries_kept_large() {
100        let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX);
101        let upper_bound = Z::from(i64::MIN);
102        for _ in 0..256 {
103            let matrix = MatZ::sample_uniform(1, 1, &lower_bound, &upper_bound).unwrap();
104            let sample = matrix.get_entry(0, 0).unwrap();
105            assert!(lower_bound <= sample);
106            assert!(sample < upper_bound);
107        }
108    }
109
110    /// Checks whether matrices with at least one dimension chosen smaller than `1`
111    /// or too large for an [`i64`] results in an error.
112    #[should_panic]
113    #[test]
114    fn false_size() {
115        let lower_bound = Z::from(-15);
116        let upper_bound = Z::from(15);
117
118        let _ = MatZ::sample_uniform(0, 3, &lower_bound, &upper_bound);
119    }
120
121    /// Checks whether providing an invalid interval results in an error.
122    #[test]
123    fn invalid_interval() {
124        let lb_0 = Z::from(i64::MIN);
125        let lb_1 = Z::from(i64::MIN);
126        let lb_2 = Z::ZERO;
127        let upper_bound = Z::from(i64::MIN);
128
129        let mat_0 = MatZ::sample_uniform(3, 3, &lb_0, &upper_bound);
130        let mat_1 = MatZ::sample_uniform(4, 1, &lb_1, &upper_bound);
131        let mat_2 = MatZ::sample_uniform(1, 5, &lb_2, &upper_bound);
132
133        assert!(mat_0.is_err());
134        assert!(mat_1.is_err());
135        assert!(mat_2.is_err());
136    }
137
138    /// Checks whether `sample_uniform` is available for all types
139    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
140    #[test]
141    fn availability() {
142        let modulus = Modulus::from(7);
143        let z = Z::from(7);
144
145        let _ = MatZ::sample_uniform(1, 1, 0u16, 7u8);
146        let _ = MatZ::sample_uniform(1, 1, 0u32, 7u16);
147        let _ = MatZ::sample_uniform(1, 1, 0u64, 7u32);
148        let _ = MatZ::sample_uniform(1, 1, 0i8, 7u64);
149        let _ = MatZ::sample_uniform(1, 1, 0i16, 7i8);
150        let _ = MatZ::sample_uniform(1, 1, 0i32, 7i16);
151        let _ = MatZ::sample_uniform(1, 1, 0i64, 7i32);
152        let _ = MatZ::sample_uniform(1, 1, &Z::ZERO, 7i64);
153        let _ = MatZ::sample_uniform(1, 1, 0u8, &modulus);
154        let _ = MatZ::sample_uniform(1, 1, 0, &z);
155    }
156
157    /// Checks whether the size of uniformly random sampled matrices
158    /// fits the specified dimensions.
159    #[test]
160    fn matrix_size() {
161        let lower_bound = Z::from(-15);
162        let upper_bound = Z::from(15);
163
164        let mat_0 = MatZ::sample_uniform(3, 3, &lower_bound, &upper_bound).unwrap();
165        let mat_1 = MatZ::sample_uniform(4, 1, &lower_bound, &upper_bound).unwrap();
166        let mat_2 = MatZ::sample_uniform(1, 5, &lower_bound, &upper_bound).unwrap();
167        let mat_3 = MatZ::sample_uniform(15, 20, &lower_bound, &upper_bound).unwrap();
168
169        assert_eq!(3, mat_0.get_num_rows());
170        assert_eq!(3, mat_0.get_num_columns());
171        assert_eq!(4, mat_1.get_num_rows());
172        assert_eq!(1, mat_1.get_num_columns());
173        assert_eq!(1, mat_2.get_num_rows());
174        assert_eq!(5, mat_2.get_num_columns());
175        assert_eq!(15, mat_3.get_num_rows());
176        assert_eq!(20, mat_3.get_num_columns());
177    }
178}