From 2508c929c3babbee390478292bb89b163d5ac9a5 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 27 Aug 2024 13:32:53 -0600 Subject: [PATCH] fix: remove "likely" param from Normal (because it's symmetric) --- src/distributions/normal.rs | 61 ++++++++++++++++------------------ src/estimation_distribution.rs | 2 +- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/src/distributions/normal.rs b/src/distributions/normal.rs index 41f1cf8..0a67b4e 100644 --- a/src/distributions/normal.rs +++ b/src/distributions/normal.rs @@ -12,7 +12,7 @@ //! use distimate::Normal; //! use approx::assert_relative_eq; //! -//! let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); +//! let normal = Normal::new(1.0, 3.0).unwrap(); //! assert_eq!(normal.mode(), 2.0); //! assert_relative_eq!(normal.mean().unwrap(), 2.0, epsilon = 1e-6); //! ``` @@ -33,7 +33,7 @@ use statrs::distribution::Normal as StatrsNormal; /// use distimate::Normal; /// use approx::assert_relative_eq; /// -/// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); +/// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.min(), 1.0); /// assert_eq!(normal.mode(), 2.0); /// assert_eq!(normal.max(), 3.0); @@ -52,9 +52,10 @@ impl Normal { /// # Arguments /// /// * `min` - The minimum value of the distribution - /// * `likely` - The most likely value (mode) of the distribution /// * `max` - The maximum value of the distribution /// + /// Note that the `likely` value is implied as the center of the distribution. + /// /// # Returns /// /// Returns a `Result` containing the new `Normal` instance if the parameters are valid, @@ -64,27 +65,23 @@ impl Normal { /// /// Returns an error if: /// - `min` is greater than or equal to `max` - /// - `likely` is less than `min` or greater than `max` - /// /// # Examples /// /// ``` /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.min(), 1.0); /// assert_eq!(normal.mode(), 2.0); /// assert_eq!(normal.max(), 3.0); /// ``` - pub fn new(min: f64, likely: f64, max: f64) -> Result { + pub fn new(min: f64, max: f64) -> Result { if min >= max { return Err(Error::InvalidRange { min, max }); } - if likely < min || likely > max { - return Err(Error::InvalidMode { min, likely, max }); - } + let likely = (min + max) / 2.0; let mean = likely; let std_dev = (max - min) / 6.0; // Assuming 99.7% of values fall within the range @@ -119,7 +116,7 @@ impl Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// -/// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); +/// let normal = Normal::new(1.0, 3.0).unwrap(); /// // The Normal distribution can now be used in contexts requiring an EstimationDistribution /// ``` impl EstimationDistribution for Normal {} @@ -150,7 +147,7 @@ impl Distribution for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.mean(), Some(2.0)); /// ``` fn mean(&self) -> Option { @@ -172,7 +169,7 @@ impl Distribution for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.variance().unwrap(), 0.1111111111111111); // (1/3)^2 /// ``` fn variance(&self) -> Option { @@ -193,7 +190,7 @@ impl Distribution for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.skewness(), Some(0.0)); /// ``` fn skewness(&self) -> Option { @@ -216,7 +213,7 @@ impl Distribution for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert!(normal.entropy().is_some()); /// ``` fn entropy(&self) -> Option { @@ -246,7 +243,7 @@ impl Median for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.median(), 2.0); /// ``` /// @@ -282,7 +279,7 @@ impl Mode for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.mode(), 2.0); /// ``` /// @@ -321,7 +318,7 @@ impl Continuous for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert!(normal.pdf(2.0) > 0.0); /// assert_eq!(normal.pdf(0.0), 0.0); // Outside the distribution's range /// ``` @@ -353,7 +350,7 @@ impl Continuous for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert!(normal.ln_pdf(2.0).is_finite()); /// assert_eq!(normal.ln_pdf(0.0), f64::NEG_INFINITY); // Outside the distribution's range /// ``` @@ -393,7 +390,7 @@ impl ContinuousCDF for Normal { /// use distimate::Normal; /// use approx::assert_relative_eq; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_relative_eq!(normal.cdf(2.0), 0.5, epsilon = 1e-6); /// assert_eq!(normal.cdf(0.0), 0.0); // Below minimum /// assert_eq!(normal.cdf(4.0), 1.0); // Above maximum @@ -431,7 +428,7 @@ impl ContinuousCDF for Normal { /// use distimate::Normal; /// use approx::assert_relative_eq; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_relative_eq!(normal.inverse_cdf(0.5), 2.0, epsilon = 1e-6); /// assert_eq!(normal.inverse_cdf(0.0), 1.0); // Clamped to minimum /// assert_eq!(normal.inverse_cdf(1.0), 3.0); // Clamped to maximum @@ -463,7 +460,7 @@ impl Min for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.min(), 1.0); /// ``` /// @@ -500,7 +497,7 @@ impl Max for Normal { /// use distimate::prelude::*; /// use distimate::Normal; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// assert_eq!(normal.max(), 3.0); /// ``` /// @@ -544,7 +541,7 @@ impl RandDistribution for Normal { /// use distimate::Normal; /// use rand::prelude::*; /// - /// let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + /// let normal = Normal::new(1.0, 3.0).unwrap(); /// let mut rng = StdRng::seed_from_u64(42); // For reproducibility /// /// let sample = normal.sample(&mut rng); @@ -573,7 +570,7 @@ mod tests { #[test] fn test_new() { - let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + let normal = Normal::new(1.0, 3.0).unwrap(); assert_eq!(normal.min(), 1.0); assert_eq!(normal.mode(), 2.0); assert_eq!(normal.max(), 3.0); @@ -583,14 +580,12 @@ mod tests { #[test] fn test_invalid_parameters() { - assert!(Normal::new(3.0, 2.0, 1.0).is_err()); // min > max - assert!(Normal::new(1.0, 0.5, 3.0).is_err()); // likely < min - assert!(Normal::new(1.0, 3.5, 3.0).is_err()); // likely > max + assert!(Normal::new(3.0, 1.0).is_err()); // min > max } #[test] fn test_pdf() { - let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + let normal = Normal::new(1.0, 3.0).unwrap(); assert_relative_eq!(normal.pdf(2.0), normal.inner.pdf(2.0), epsilon = 1e-6); assert_eq!(normal.pdf(0.5), 0.0); // Below min assert_eq!(normal.pdf(3.5), 0.0); // Above max @@ -598,7 +593,7 @@ mod tests { #[test] fn test_cdf() { - let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + let normal = Normal::new(1.0, 3.0).unwrap(); assert_relative_eq!(normal.cdf(2.0), 0.5, epsilon = 1e-6); assert_eq!(normal.cdf(0.5), 0.0); // Below min assert_eq!(normal.cdf(3.5), 1.0); // Above max @@ -606,7 +601,7 @@ mod tests { #[test] fn test_inverse_cdf() { - let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + let normal = Normal::new(1.0, 3.0).unwrap(); assert_relative_eq!(normal.inverse_cdf(0.5), 2.0, epsilon = 1e-6); assert_eq!(normal.inverse_cdf(0.0), 1.0); // Clamped to min assert_eq!(normal.inverse_cdf(1.0), 3.0); // Clamped to max @@ -614,7 +609,7 @@ mod tests { #[test] fn test_sampling() { - let normal = Normal::new(1.0, 2.0, 3.0).unwrap(); + let normal = Normal::new(1.0, 3.0).unwrap(); let mut rng = StdRng::seed_from_u64(42); let samples: Vec = (0..1000).map(|_| normal.sample(&mut rng)).collect(); @@ -633,7 +628,7 @@ mod tests { ]; for (min, likely, max) in test_cases { - let normal = Normal::new(min, likely, max).unwrap(); + let normal = Normal::new(min, max).unwrap(); let n = 100_000; // Number of samples let mut rng = StdRng::seed_from_u64(42); // Fixed seed for reproducibility diff --git a/src/estimation_distribution.rs b/src/estimation_distribution.rs index 69f8145..9a81d34 100644 --- a/src/estimation_distribution.rs +++ b/src/estimation_distribution.rs @@ -167,7 +167,7 @@ pub trait EstimationDistribution: /// use approx::assert_relative_eq; /// /// let pert = Pert::new(1.0, 2.0, 3.0).unwrap(); - /// assert_relative_eq!(pert.probability_of_completion(2.5), 0.8436, epsilon = 1e-4); + /// assert_relative_eq!(pert.probability_of_completion(2.5), 0.89648, epsilon = 1e-4); /// ``` fn probability_of_completion(&self, estimate: f64) -> f64 { self.cdf(estimate)