qfall_math/rational/q/sample/gauss.rs
1// Copyright © 2024 Marvin Beckmann
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 gaussian distributions over [`Q`].
10
11use crate::{error::MathError, rational::Q};
12use probability::{
13 distribution::{Gaussian, Sample},
14 source,
15};
16use rand::RngCore;
17
18impl Q {
19 /// Chooses a [`Q`] instance according to the continuous Gaussian distribution.
20 ///
21 /// Parameters:
22 /// - `center`: specifies the position of the center
23 /// - `sigma`: specifies the standard deviation
24 ///
25 /// Returns new [`Q`] sample chosen according to the specified continuous Gaussian
26 /// distribution or a [`MathError`] if the specified parameters were not chosen
27 /// appropriately (`sigma > 0`).
28 ///
29 /// # Examples
30 /// ```
31 /// use qfall_math::rational::Q;
32 ///
33 /// let sample = Q::sample_gauss(0, 1).unwrap();
34 /// ```
35 ///
36 /// # Errors and Failures
37 /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive)
38 /// if `sigma <= 0`.
39 pub fn sample_gauss(center: impl Into<Q>, sigma: impl Into<f64>) -> Result<Q, MathError> {
40 let center = center.into();
41 let sigma = sigma.into();
42 if sigma <= 0.0 {
43 return Err(MathError::NonPositive(format!(
44 "The sigma has to be positive and not zero, but the provided value is {sigma}."
45 )));
46 }
47 let mut rng = rand::rng();
48 let mut source = source::default(rng.next_u64());
49
50 // Instead of sampling with a center of c, we sample with center 0 and add the
51 // center later. These are equivalent and this way we can sample in larger ranges
52 let sampler = Gaussian::new(0.0, sigma);
53 let sample = center + Q::from(sampler.sample(&mut source));
54
55 Ok(sample)
56 }
57}
58
59#[cfg(test)]
60mod test_sample_gauss {
61 use crate::rational::Q;
62
63 /// Test correct distribution with a confidence level of 99.7% -> 3 standard
64 /// deviations.
65 #[test]
66 fn in_concentration_bound() {
67 let range = 3;
68 for (mu, sigma) in [(i64::MAX, 1), (0, 20), (i64::MIN, 100)] {
69 assert!(range * sigma >= (Q::from(mu) - Q::sample_gauss(mu, sigma).unwrap()).abs())
70 }
71 }
72
73 /// Ensure that an error is returned if `sigma` is not positive
74 #[test]
75 fn non_positive_sigma() {
76 for (mu, sigma) in [(0, 0), (0, -1)] {
77 assert!(Q::sample_gauss(mu, sigma).is_err())
78 }
79 }
80}