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/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..d927adbd 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}; @@ -41,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; @@ -83,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..a9c1df97 --- /dev/null +++ b/src/distribution/noncentral_students_t.rs @@ -0,0 +1,190 @@ +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 { + /// 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 { + /// 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 + } +} 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` 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 {