From ef6de887f8c838dfc520bcf8ac1b7eec50762a81 Mon Sep 17 00:00:00 2001 From: Orion Yeung <11580988+orionyeung001@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:59:05 -0500 Subject: [PATCH 1/3] lint: `use` should not fully qualify scope --- src/distribution/geometric.rs | 2 +- src/distribution/laplace.rs | 10 +++++----- src/distribution/mod.rs | 5 ++--- src/statistics/traits.rs | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/distribution/geometric.rs b/src/distribution/geometric.rs index 82af5eef..1833abce 100644 --- a/src/distribution/geometric.rs +++ b/src/distribution/geometric.rs @@ -93,7 +93,7 @@ impl std::fmt::Display for Geometric { #[cfg(feature = "rand")] impl ::rand::distributions::Distribution for Geometric { fn sample(&self, r: &mut R) -> f64 { - use ::rand::distributions::OpenClosed01; + use rand::distributions::OpenClosed01; if ulps_eq!(self.p, 1.0) { 1.0 diff --git a/src/distribution/laplace.rs b/src/distribution/laplace.rs index 168859ea..06bae5de 100644 --- a/src/distribution/laplace.rs +++ b/src/distribution/laplace.rs @@ -553,8 +553,8 @@ mod tests { #[cfg(feature = "rand")] #[test] fn test_sample() { - use ::rand::distributions::Distribution; - use ::rand::thread_rng; + use rand::distributions::Distribution; + use rand::thread_rng; let l = create_ok(0.1, 0.5); l.sample(&mut thread_rng()); @@ -563,9 +563,9 @@ mod tests { #[cfg(feature = "rand")] #[test] fn test_sample_distribution() { - use ::rand::distributions::Distribution; - use ::rand::rngs::StdRng; - use ::rand::SeedableRng; + use rand::distributions::Distribution; + use rand::rngs::StdRng; + use rand::SeedableRng; // sanity check sampling let location = 0.0; diff --git a/src/distribution/mod.rs b/src/distribution/mod.rs index e8c9574c..839d2384 100644 --- a/src/distribution/mod.rs +++ b/src/distribution/mod.rs @@ -1,9 +1,8 @@ //! Defines common interfaces for interacting with statistical distributions //! and provides //! concrete implementations for a variety of distributions. -use super::statistics::{Max, Min}; -use ::num_traits::{Float, Num}; -use num_traits::NumAssignOps; +use crate::statistics::{Max, Min}; +use num_traits::{Float, Num, NumAssignOps}; pub use self::bernoulli::Bernoulli; pub use self::beta::{Beta, BetaError}; diff --git a/src/statistics/traits.rs b/src/statistics/traits.rs index 9140eab4..e4b5c38b 100644 --- a/src/statistics/traits.rs +++ b/src/statistics/traits.rs @@ -1,4 +1,4 @@ -use ::num_traits::float::Float; +use num_traits::float::Float; /// The `Min` trait specifies than an object has a minimum value pub trait Min { From 969f56c5ba56c48157949b52a8e974f00dd89cc2 Mon Sep 17 00:00:00 2001 From: Orion Yeung <11580988+orionyeung001@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:00:04 -0500 Subject: [PATCH 2/3] feat: add skeleton of noncentral students t distribution --- src/distribution/mod.rs | 4 + src/distribution/noncentral_students_t.rs | 174 ++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 src/distribution/noncentral_students_t.rs diff --git a/src/distribution/mod.rs b/src/distribution/mod.rs index 839d2384..d927adbd 100644 --- a/src/distribution/mod.rs +++ b/src/distribution/mod.rs @@ -40,6 +40,8 @@ pub use self::triangular::{Triangular, TriangularError}; pub use self::uniform::{Uniform, UniformError}; pub use self::weibull::{Weibull, WeibullError}; +pub use self::noncentral_students_t::{NoncentralStudentsT, NoncentralStudentsTError}; + mod bernoulli; mod beta; mod binomial; @@ -82,6 +84,8 @@ mod ziggurat; #[cfg(feature = "rand")] mod ziggurat_tables; +mod noncentral_students_t; + /// The `ContinuousCDF` trait is used to specify an interface for univariate /// distributions for which cdf float arguments are sensible. pub trait ContinuousCDF: Min + Max { diff --git a/src/distribution/noncentral_students_t.rs b/src/distribution/noncentral_students_t.rs new file mode 100644 index 00000000..8bd8a8e0 --- /dev/null +++ b/src/distribution/noncentral_students_t.rs @@ -0,0 +1,174 @@ +use crate::distribution::{Continuous, ContinuousCDF}; +use crate::function::{beta, gamma}; +use crate::statistics::{Max, Min}; + +/// the non-central students T distribution +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct NoncentralStudentsT { + location: f64, + scale: f64, + freedom: f64, +} + +/// Represents the errors that can occur when creating a [`NoncentralStudentsT`]. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +#[non_exhaustive] +pub enum NoncentralStudentsTError { + /// The location is NaN. + LocationInvalid, + + /// The scale is NaN, zero or less than zero. + ScaleInvalid, + + /// The degrees of freedom are NaN, zero or less than zero. + FreedomInvalid, +} + +impl std::fmt::Display for NoncentralStudentsTError { + #[cfg_attr(coverage_nightly, coverage(off))] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + NoncentralStudentsTError::LocationInvalid => write!(f, "Location is NaN"), + NoncentralStudentsTError::ScaleInvalid => { + write!(f, "Scale is NaN, zero or less than zero") + } + NoncentralStudentsTError::FreedomInvalid => { + write!(f, "Degrees of freedom are NaN, zero or less than zero") + } + } + } +} + +impl std::error::Error for NoncentralStudentsTError {} + +impl NoncentralStudentsT { + /// Constructs a new student's t-distribution with location `location`, + /// scale `scale`, and `freedom` freedom. + /// + /// # Errors + /// + /// Returns an error if any of `location`, `scale`, or `freedom` are `NaN`. + /// Returns an error if `scale <= 0.0` or `freedom <= 0.0`. + /// + /// # Examples + /// + /// ``` + /// use statrs::distribution::NoncentralStudentsT; + /// + /// let mut result = NoncentralStudentsT::new(0.0, 1.0, 2.0); + /// assert!(result.is_ok()); + /// + /// result = NoncentralStudentsT::new(0.0, 0.0, 0.0); + /// assert!(result.is_err()); + /// ``` + pub fn new( + location: f64, + scale: f64, + freedom: f64, + ) -> Result { + if location.is_nan() { + return Err(NoncentralStudentsTError::LocationInvalid); + } + + if scale.is_nan() || scale <= 0.0 { + return Err(NoncentralStudentsTError::ScaleInvalid); + } + + if freedom.is_nan() || freedom <= 0.0 { + return Err(NoncentralStudentsTError::FreedomInvalid); + } + + Ok(NoncentralStudentsT { + location, + scale, + freedom, + }) + } + + /// Returns the location of the student's t-distribution + /// + /// # Examples + /// + /// ``` + /// use statrs::distribution::NoncentralStudentsT; + /// + /// let n = NoncentralStudentsT::new(0.0, 1.0, 2.0).unwrap(); + /// assert_eq!(n.location(), 0.0); + /// ``` + pub fn location(&self) -> f64 { + self.location + } + + /// Returns the scale of the student's t-distribution + /// + /// # Examples + /// + /// ``` + /// use statrs::distribution::NoncentralStudentsT; + /// + /// let n = NoncentralStudentsT::new(0.0, 1.0, 2.0).unwrap(); + /// assert_eq!(n.scale(), 1.0); + /// ``` + pub fn scale(&self) -> f64 { + self.scale + } + + /// Returns the freedom of the student's t-distribution + /// + /// # Examples + /// + /// ``` + /// use statrs::distribution::NoncentralStudentsT; + /// + /// let n = NoncentralStudentsT::new(0.0, 1.0, 2.0).unwrap(); + /// assert_eq!(n.freedom(), 2.0); + /// ``` + pub fn freedom(&self) -> f64 { + self.freedom + } +} + +impl std::fmt::Display for NoncentralStudentsT { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "t_{}({},{})", self.freedom, self.location, self.scale) + } +} + +impl ContinuousCDF for NoncentralStudentsT { + /// computes the distribution function for noncentral students t distribution + /// + /// ```math + /// F(t;\nu,\delta) = \Phi(-\delta) + \frac{1}{2}\sum_{i=0}^\infty{\left[P_i I_x(i+ 1/2, n/2) + Q_i I_x(i+ 1/2, n/2)\right]} + /// ``` + fn cdf(&self, x: f64) -> f64 { + unimplemented!() + } +} + +impl Min for NoncentralStudentsT { + /// Returns the minimum value in the domain of the student's t-distribution + /// representable by a double precision float + /// + /// # Formula + /// + /// ```text + /// f64::NEG_INFINITY + /// ``` + fn min(&self) -> f64 { + f64::NEG_INFINITY + } +} + +impl Max for NoncentralStudentsT { + /// Returns the maximum value in the domain of the student's t-distribution + /// representable by a double precision float + /// + /// # Formula + /// + /// ```text + /// f64::INFINITY + /// ``` + fn max(&self) -> f64 { + f64::INFINITY + } +} From 1b4e1c55f9ba105dcdee82a23680cd5e73b11d0b Mon Sep 17 00:00:00 2001 From: Orion Yeung <11580988+orionyeung001@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:00:19 -0500 Subject: [PATCH 3/3] doc: support KaTeX in docs --- Cargo.toml | 6 +++++ Makefile.toml | 4 ++++ katex-header.html | 13 +++++++++++ src/distribution/noncentral_students_t.rs | 28 ++++++++++++++++++----- src/function/beta.rs | 11 ++++++++- 5 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 Makefile.toml create mode 100644 katex-header.html diff --git a/Cargo.toml b/Cargo.toml index 8625bd99..e1c03e45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,3 +56,9 @@ features = ["macros"] level = "warn" # Set by cargo-llvm-cov when running on nightly check-cfg = ['cfg(coverage_nightly)'] + +[package.metadata.docs.rs] +rustdoc-args = [ + "--html-in-header", + "./katex-header.html" +] diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 00000000..985672ed --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,4 @@ +[tasks.doc-katex] +env = { "RUSTDOCFLAGS" = "--html-in-header katex-header.html" } +command = "cargo" +args = ["doc", "--no-deps"] diff --git a/katex-header.html b/katex-header.html new file mode 100644 index 00000000..578dbeaa --- /dev/null +++ b/katex-header.html @@ -0,0 +1,13 @@ + + + + diff --git a/src/distribution/noncentral_students_t.rs b/src/distribution/noncentral_students_t.rs index 8bd8a8e0..a9c1df97 100644 --- a/src/distribution/noncentral_students_t.rs +++ b/src/distribution/noncentral_students_t.rs @@ -135,14 +135,30 @@ impl std::fmt::Display for NoncentralStudentsT { } impl ContinuousCDF for NoncentralStudentsT { - /// computes the distribution function for noncentral students t distribution - /// - /// ```math - /// F(t;\nu,\delta) = \Phi(-\delta) + \frac{1}{2}\sum_{i=0}^\infty{\left[P_i I_x(i+ 1/2, n/2) + Q_i I_x(i+ 1/2, n/2)\right]} - /// ``` - fn cdf(&self, x: f64) -> f64 { + /// Calculates the cumulative distribution function for the noncentral students t distribution at `t` + /// + /// # Definition + /// \\(F(t;\nu,\delta) = \textrm{Prob}(t_\nu(\delta) < t)\\) + /// \\[ + /// \Phi(-\delta) + \frac{1}{2}\sum\_{i=0}^\infty{[P\_i I\_x(i+1/2,n/2) + Q\_i I\_x(i+1/2,n/2)]}\textrm{ where}\\\\\[1.5em\] + /// x = \frac{t^2}{\nu + t^2}, \quad + /// P_i = e^{-\delta^2/2}\\,\frac{(\delta^2/2)^i}{i!}, \quad + /// Q_i = e^{-\delta^2/2}\\,\frac{(\delta^2/2)^i}{\Gamma(i + 3/2)} + /// \\] + /// where \\(I_x\\) denotes the incomplete regularized beta function, same as \\(I_x(a,b)\\) [here](https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function), unregularized is implemented as [`beta_inc`](crate::function::beta::beta_inc) + fn cdf(&self, t: f64) -> f64 { + unimplemented!() + } +} + +impl Continuous for NoncentralStudentsT { + fn pdf(&self, x: f64) -> f64 { unimplemented!() } + + fn ln_pdf(&self, x: f64) -> f64 { + self.pdf(x).ln() + } } impl Min for NoncentralStudentsT { diff --git a/src/function/beta.rs b/src/function/beta.rs index 794a8504..0a4f4b33 100644 --- a/src/function/beta.rs +++ b/src/function/beta.rs @@ -43,6 +43,11 @@ pub fn checked_ln_beta(a: f64, b: f64) -> Result { /// where `a` is the first beta parameter /// and `b` is the second beta parameter. /// +/// # Definition +/// $$ +/// B(a,b) = \int\_0^1{t^{a-1}(1-t)^{b-1}}\textrm{d}t +/// $$ +/// /// /// # Panics /// @@ -64,10 +69,14 @@ pub fn checked_beta(a: f64, b: f64) -> Result { } /// Computes the lower incomplete (unregularized) beta function -/// `B(a,b,x) = int(t^(a-1)*(1-t)^(b-1),t=0..x)` for `a > 0, b > 0, 1 >= x >= 0` /// where `a` is the first beta parameter, `b` is the second beta parameter, and /// `x` is the upper limit of the integral /// +/// # Definition +/// ```math +/// B(x;a,b) = B\_x(a,b) = \int\_0^x t^{a-1}*(1-t)^{b-1} \textrm{d}t +/// x \in [0,1] \textrm{ and } a,b > 0 +/// ``` /// # Panics /// /// If `a <= 0.0`, `b <= 0.0`, `x < 0.0`, or `x > 1.0`