rand_simple/distributions/
t.rs

1use crate::create_state;
2use crate::standard_distributions::{
3    standard_cauchy, standard_exponential, standard_gamma, standard_normal,
4};
5
6/// t-distribution (Student's t-distribution)
7/// # Usage Example
8/// ```
9/// // Create a new TDistribution instance with the provided seeds.
10/// let mut t = rand_simple::TDistribution::new([1192u32, 765u32, 1543u32, 2003u32, 1867u32]);
11/// // Check if the default degree of freedom is correctly set to 1.
12/// assert_eq!(format!("{t}"), "T(Degree of Freedom parameter) = T(1)");
13/// // Generate and print a random number based on the t-distribution with 1 degree of freedom.
14/// println!("Generates a random number with degree of freedom 1 -> {}", t.sample());
15///
16/// // If you need to modify the parameters of the t-distribution (degree of freedom).
17/// let degree_of_freedom: u64 = 3_u64;
18/// // Set the new degree of freedom and check if the operation was successful.
19/// let result: Result<u64, &str> = t.try_set_params(degree_of_freedom);
20/// // Verify that the degree of freedom has been correctly updated to 3.
21/// assert_eq!(format!("{t}"), "T(Degree of Freedom parameter) = T(3)");
22/// // Generate and print a random number with the updated degree of freedom (3).
23/// println!("Generates a random number with degree of freedom {} -> {}", degree_of_freedom, t.sample());
24/// ```
25///
26/// This example demonstrates how to initialize a t-distribution instance, change the degree of freedom,
27/// and generate random numbers following the t-distribution.
28pub struct TDistribution {
29    xyzuv_n_0: [u32; 5], // 状態変数
30    xyzuv_n_1: [u32; 5], // 状態変数
31
32    xyzuv_u_gamma: [u32; 5],   // 状態変数
33    xyzuv_n_0_gamma: [u32; 5], // 状態変数
34    xyzuv_n_1_gamma: [u32; 5], // 状態変数
35
36    degree_of_freedom: u64, // 自由度 r ∈ N
37}
38
39impl TDistribution {
40    /// Constructor for the `TDistribution` struct.
41    /// This method initializes a new instance of the t-distribution with the provided random seeds.
42    ///
43    /// # Arguments
44    /// * `seeds` - An array of 5 unsigned 32-bit integers (u32) that serve as seeds for generating the random numbers.
45    ///
46    /// The constructor processes the seeds through an adjustment function (`adjust_seeds!`)
47    /// and assigns them to internal state variables used for generating random numbers
48    /// that follow a t-distribution.
49    ///
50    /// The degree of freedom (DoF) is initialized to 1, but can be changed later via other methods.
51    ///
52    /// # Returns
53    /// A new instance of the `TDistribution` struct.
54    pub fn new(seeds: [u32; 5_usize]) -> Self {
55        let adjusted_seeds = crate::adjust_seeds!(seeds);
56
57        Self {
58            xyzuv_n_0: create_state(adjusted_seeds[0]),
59            xyzuv_n_1: create_state(adjusted_seeds[1]),
60
61            xyzuv_u_gamma: create_state(adjusted_seeds[2]),
62            xyzuv_n_0_gamma: create_state(adjusted_seeds[3]),
63            xyzuv_n_1_gamma: create_state(adjusted_seeds[4]),
64
65            degree_of_freedom: 1_u64,
66        }
67    }
68
69    /// Generates a random sample from the t-distribution based on the current degree of freedom (DoF).
70    ///
71    /// # Algorithm Description:
72    /// The function generates a random number following a t-distribution using different algorithms
73    /// depending on the value of the degree of freedom (DoF):
74    ///
75    /// - For `DoF = 1`: It generates a sample from the standard Cauchy distribution.
76    /// - For `DoF = 2`: It follows Algorithm 3.84, which uses a combination of normal and exponential
77    ///   distributions to generate the sample.
78    /// - For `DoF > 2`: It follows Algorithm 3.85, using a combination of normal and gamma distributions
79    ///   to generate the sample.
80    ///
81    /// # Returns
82    /// * `f64` - A floating-point value representing the generated random sample.
83    ///
84    pub fn sample(&mut self) -> f64 {
85        match self.degree_of_freedom as usize {
86            1_usize => standard_cauchy(&mut self.xyzuv_u_gamma),
87            2_usize => {
88                // アルゴリズム 3.84 r: = 2
89                // step 1
90                let mut z = 0_f64;
91                let mut w = 0_f64;
92                while w == 0_f64 {
93                    z = standard_normal(&mut self.xyzuv_n_0, &mut self.xyzuv_n_1);
94                    w = standard_exponential(&mut self.xyzuv_u_gamma);
95                }
96                // step 2
97                z / w.sqrt()
98            }
99            _ => {
100                // アルゴリズム 3.85: r > 2
101                // step 1
102                let z = standard_normal(&mut self.xyzuv_n_0, &mut self.xyzuv_n_1);
103                let w = standard_gamma(
104                    &mut self.xyzuv_u_gamma,
105                    &mut self.xyzuv_n_0_gamma,
106                    &mut self.xyzuv_n_1_gamma,
107                    &(self.degree_of_freedom as f64 / 2_f64),
108                );
109                // step 2
110                (self.degree_of_freedom as f64).sqrt() * z / w.sqrt()
111            }
112        }
113    }
114
115    /// Updates the degree of freedom parameter for the random variable.
116    ///
117    /// # Parameters:
118    /// * `degree_of_freedom` - The new degree of freedom to set, must be a natural number (r ≥ 1).
119    ///
120    /// # Returns:
121    /// * `Ok(u64)` - If the input `degree_of_freedom` is valid, returns the updated value.
122    /// * `Err(&str)` - If the input `degree_of_freedom` is invalid (r < 1), returns an error message.
123    ///
124    /// # Description:
125    /// This method is responsible for setting the degree of freedom (DoF) for the random variable.
126    /// The DoF must be a positive integer (natural number) to be valid. If the input value is less
127    /// than 1, the function will return an error message and maintain the previous DoF setting.
128    /// Otherwise, it updates the degree of freedom with the new valid value and returns it.
129    ///
130    /// # Example:
131    /// ```
132    /// let mut dist = rand_simple::TDistribution::new([1192u32, 765u32, 1543u32, 2003u32, 1867u32]);
133    /// let result = dist.try_set_params(3_u64);
134    /// assert_eq!(result.unwrap(), 3_u64); // Successfully updates the degree of freedom.
135    /// ```
136    pub fn try_set_params(&mut self, degree_of_freedom: u64) -> Result<u64, &str> {
137        // Check if the provided degree_of_freedom is a valid natural number (r ≥ 1).
138        if degree_of_freedom < 1_u64 {
139            // Return an error if the input is invalid, without changing the current parameter.
140            Err("The degree of freedom must be a natural number. The parameter remains unchanged.")
141        } else {
142            // Update the degree of freedom with the valid input and return the updated value.
143            self.degree_of_freedom = degree_of_freedom;
144            Ok(degree_of_freedom)
145        }
146    }
147}
148
149impl std::fmt::Display for TDistribution {
150    /// Formatter implementation for the TDistribution struct.
151    /// This allows the `println!` macro or other formatting macros
152    /// to display the t-distribution in a human-readable format.
153    ///
154    /// # Arguments
155    /// * `f` - A mutable reference to the formatter used by the `println!` macro.
156    ///
157    /// The implementation writes a formatted string representing the current state of the
158    /// `TDistribution` instance, specifically displaying the degree of freedom.
159    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
160        // Use the `write!` macro to format the output string, specifying the degree of freedom.
161        // This will output something like: "T(Degree of Freedom parameter) = T(3)"
162        write!(
163            f,
164            "T(Degree of Freedom parameter) = T({})",
165            self.degree_of_freedom
166        )?;
167        // Return `Ok(())` to indicate that the formatting was successful.
168        Ok(())
169    }
170}