fix: remove "likely" param from Normal (because it's symmetric)

This commit is contained in:
swaits committed 2024-08-27 13:32:53 -06:00
1 parent 765b02ceef
commit 2508c929c3
2 files changed
+29 -34

No files matched your search

+28 -33
View File
@@ -12,7 +12,7 @@
//! use distimate::Normal; //! use distimate::Normal;
//! use approx::assert_relative_eq; //! 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_eq!(normal.mode(), 2.0);
//! assert_relative_eq!(normal.mean().unwrap(), 2.0, epsilon = 1e-6); //! 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 distimate::Normal;
/// use approx::assert_relative_eq; /// 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.min(), 1.0);
/// assert_eq!(normal.mode(), 2.0); /// assert_eq!(normal.mode(), 2.0);
/// assert_eq!(normal.max(), 3.0); /// assert_eq!(normal.max(), 3.0);
@@ -52,9 +52,10 @@ impl Normal {
/// # Arguments /// # Arguments
/// ///
/// * `min` - The minimum value of the distribution /// * `min` - The minimum value of the distribution
/// * `likely` - The most likely value (mode) of the distribution
/// * `max` - The maximum value 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
/// ///
/// Returns a `Result` containing the new `Normal` instance if the parameters are valid, /// Returns a `Result` containing the new `Normal` instance if the parameters are valid,
@@ -64,27 +65,23 @@ impl Normal {
/// ///
/// Returns an error if: /// Returns an error if:
/// - `min` is greater than or equal to `max` /// - `min` is greater than or equal to `max`
/// - `likely` is less than `min` or greater than `max`
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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.min(), 1.0);
/// assert_eq!(normal.mode(), 2.0); /// assert_eq!(normal.mode(), 2.0);
/// assert_eq!(normal.max(), 3.0); /// assert_eq!(normal.max(), 3.0);
/// ``` /// ```
pub fn new(min: f64, likely: f64, max: f64) -> Result<Self> { pub fn new(min: f64, max: f64) -> Result<Self> {
if min >= max { if min >= max {
return Err(Error::InvalidRange { 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 mean = likely;
let std_dev = (max - min) / 6.0; // Assuming 99.7% of values fall within the range 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::prelude::*;
/// use distimate::Normal; /// 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 /// // The Normal distribution can now be used in contexts requiring an EstimationDistribution
/// ``` /// ```
impl EstimationDistribution for Normal {} impl EstimationDistribution for Normal {}
@@ -150,7 +147,7 @@ impl Distribution<f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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)); /// assert_eq!(normal.mean(), Some(2.0));
/// ``` /// ```
fn mean(&self) -> Option<f64> { fn mean(&self) -> Option<f64> {
@@ -172,7 +169,7 @@ impl Distribution<f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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 /// assert_eq!(normal.variance().unwrap(), 0.1111111111111111); // (1/3)^2
/// ``` /// ```
fn variance(&self) -> Option<f64> { fn variance(&self) -> Option<f64> {
@@ -193,7 +190,7 @@ impl Distribution<f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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)); /// assert_eq!(normal.skewness(), Some(0.0));
/// ``` /// ```
fn skewness(&self) -> Option<f64> { fn skewness(&self) -> Option<f64> {
@@ -216,7 +213,7 @@ impl Distribution<f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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()); /// assert!(normal.entropy().is_some());
/// ``` /// ```
fn entropy(&self) -> Option<f64> { fn entropy(&self) -> Option<f64> {
@@ -246,7 +243,7 @@ impl Median<f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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); /// assert_eq!(normal.median(), 2.0);
/// ``` /// ```
/// ///
@@ -282,7 +279,7 @@ impl Mode<f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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); /// assert_eq!(normal.mode(), 2.0);
/// ``` /// ```
/// ///
@@ -321,7 +318,7 @@ impl Continuous<f64, f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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!(normal.pdf(2.0) > 0.0);
/// assert_eq!(normal.pdf(0.0), 0.0); // Outside the distribution's range /// assert_eq!(normal.pdf(0.0), 0.0); // Outside the distribution's range
/// ``` /// ```
@@ -353,7 +350,7 @@ impl Continuous<f64, f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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!(normal.ln_pdf(2.0).is_finite());
/// assert_eq!(normal.ln_pdf(0.0), f64::NEG_INFINITY); // Outside the distribution's range /// assert_eq!(normal.ln_pdf(0.0), f64::NEG_INFINITY); // Outside the distribution's range
/// ``` /// ```
@@ -393,7 +390,7 @@ impl ContinuousCDF<f64, f64> for Normal {
/// use distimate::Normal; /// use distimate::Normal;
/// use approx::assert_relative_eq; /// 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_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(0.0), 0.0); // Below minimum
/// assert_eq!(normal.cdf(4.0), 1.0); // Above maximum /// assert_eq!(normal.cdf(4.0), 1.0); // Above maximum
@@ -431,7 +428,7 @@ impl ContinuousCDF<f64, f64> for Normal {
/// use distimate::Normal; /// use distimate::Normal;
/// use approx::assert_relative_eq; /// 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_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(0.0), 1.0); // Clamped to minimum
/// assert_eq!(normal.inverse_cdf(1.0), 3.0); // Clamped to maximum /// assert_eq!(normal.inverse_cdf(1.0), 3.0); // Clamped to maximum
@@ -463,7 +460,7 @@ impl Min<f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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.min(), 1.0);
/// ``` /// ```
/// ///
@@ -500,7 +497,7 @@ impl Max<f64> for Normal {
/// use distimate::prelude::*; /// use distimate::prelude::*;
/// use distimate::Normal; /// 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); /// assert_eq!(normal.max(), 3.0);
/// ``` /// ```
/// ///
@@ -544,7 +541,7 @@ impl RandDistribution<f64> for Normal {
/// use distimate::Normal; /// use distimate::Normal;
/// use rand::prelude::*; /// 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 mut rng = StdRng::seed_from_u64(42); // For reproducibility
/// ///
/// let sample = normal.sample(&mut rng); /// let sample = normal.sample(&mut rng);
@@ -573,7 +570,7 @@ mod tests {
#[test] #[test]
fn test_new() { 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.min(), 1.0);
assert_eq!(normal.mode(), 2.0); assert_eq!(normal.mode(), 2.0);
assert_eq!(normal.max(), 3.0); assert_eq!(normal.max(), 3.0);
@@ -583,14 +580,12 @@ mod tests {
#[test] #[test]
fn test_invalid_parameters() { fn test_invalid_parameters() {
assert!(Normal::new(3.0, 2.0, 1.0).is_err()); // min > max assert!(Normal::new(3.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
} }
#[test] #[test]
fn test_pdf() { 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_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(0.5), 0.0); // Below min
assert_eq!(normal.pdf(3.5), 0.0); // Above max assert_eq!(normal.pdf(3.5), 0.0); // Above max
@@ -598,7 +593,7 @@ mod tests {
#[test] #[test]
fn test_cdf() { 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_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(0.5), 0.0); // Below min
assert_eq!(normal.cdf(3.5), 1.0); // Above max assert_eq!(normal.cdf(3.5), 1.0); // Above max
@@ -606,7 +601,7 @@ mod tests {
#[test] #[test]
fn test_inverse_cdf() { 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_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(0.0), 1.0); // Clamped to min
assert_eq!(normal.inverse_cdf(1.0), 3.0); // Clamped to max assert_eq!(normal.inverse_cdf(1.0), 3.0); // Clamped to max
@@ -614,7 +609,7 @@ mod tests {
#[test] #[test]
fn test_sampling() { 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 mut rng = StdRng::seed_from_u64(42);
let samples: Vec<f64> = (0..1000).map(|_| normal.sample(&mut rng)).collect(); let samples: Vec<f64> = (0..1000).map(|_| normal.sample(&mut rng)).collect();
@@ -633,7 +628,7 @@ mod tests {
]; ];
for (min, likely, max) in test_cases { 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 n = 100_000; // Number of samples
let mut rng = StdRng::seed_from_u64(42); // Fixed seed for reproducibility let mut rng = StdRng::seed_from_u64(42); // Fixed seed for reproducibility
+1 -1
View File
@@ -167,7 +167,7 @@ pub trait EstimationDistribution:
/// use approx::assert_relative_eq; /// use approx::assert_relative_eq;
/// ///
/// let pert = Pert::new(1.0, 2.0, 3.0).unwrap(); /// 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 { fn probability_of_completion(&self, estimate: f64) -> f64 {
self.cdf(estimate) self.cdf(estimate)