r2rs_nmath/distribution/logis/mod.rs
1mod d;
2mod p;
3mod q;
4mod r;
5
6use strafe_type::{LogProbability64, Probability64, Rational64, Real64};
7
8pub(crate) use self::{d::*, p::*, q::*, r::*};
9use crate::traits::{Distribution, RNG};
10
11/// # The Logistic Distribution
12///
13/// ## Description
14///
15/// Density, distribution function, quantile function and random generation for the logistic
16/// distribution with parameters location and scale.
17///
18/// ## Arguments
19///
20/// * location, scale: location and scale parameters.
21///
22/// ## Details
23///
24/// If location or scale are omitted, they assume the default values of 0 and 1 respectively.
25///
26/// The Logistic distribution with location = m and scale = s has distribution function
27///
28/// $ F(x) = \frac{1}{1 + exp(-\frac{x-m}{s})} $
29///
30/// and density
31///
32/// $ f(x) = \frac{1}{s} exp(\frac{x-m}{s}) (1 + exp(\frac{x-m}{s}))^{-2} $.
33///
34/// It is a long-tailed distribution with mean m and variance $ \frac{\pi^2}{3} s^2 $.
35///
36/// ## Value
37///
38/// dlogis gives the density, plogis gives the distribution function, qlogis gives the quantile
39/// function, and rlogis generates random deviates.
40///
41/// The length of the result is determined by n for rlogis, and is the maximum of the lengths of
42/// the numerical arguments for the other functions.
43///
44/// The numerical arguments other than n are recycled to the length of the result. Only the first
45/// elements of the logical arguments are used.
46///
47/// ## Density Plot
48///
49/// ```rust
50/// # use r2rs_base::traits::StatisticalSlice;
51/// # use r2rs_nmath::{distribution::LogisticBuilder, traits::Distribution};
52/// # use strafe_plot::prelude::{IntoDrawingArea, Line, Plot, PlotOptions, SVGBackend, BLACK};
53/// # use strafe_type::FloatConstraint;
54/// let logis = LogisticBuilder::new().build();
55/// let x = <[f64]>::sequence(-6.0, 6.0, 1000);
56/// let y = x
57/// .iter()
58/// .map(|x| logis.density(x).unwrap())
59/// .collect::<Vec<_>>();
60///
61/// let root = SVGBackend::new("density.svg", (1024, 768)).into_drawing_area();
62/// Plot::new()
63/// .with_options(PlotOptions {
64/// x_axis_label: "x".to_string(),
65/// y_axis_label: "density".to_string(),
66/// ..Default::default()
67/// })
68/// .with_plottable(Line {
69/// x,
70/// y,
71/// color: BLACK,
72/// ..Default::default()
73/// })
74/// .plot(&root)
75/// .unwrap();
76/// # use std::fs::rename;
77/// # drop(root);
78/// # rename(
79/// # format!("density.svg"),
80/// # format!("src/distribution/logis/doctest_out/density.svg"),
81/// # )
82/// # .unwrap();
83/// ```
84#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = embed_doc_image::embed_image!("density", "src/distribution/logis/doctest_out/density.svg")))]
85#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = "![Density][density]"))]
86///
87/// ## Note
88///
89/// qlogis(p) is the same as the well known ‘logit’ function, $ logit(p) = log(p/(1-p)) $, and
90/// plogis(x) has consequently been called the ‘inverse logit’.
91///
92/// The distribution function is a rescaled hyperbolic tangent, $ plogis(x) == (1+ tanh(x/2))/2 $,
93/// and it is called a sigmoid function in contexts such as neural networks.
94///
95/// ## Source
96///
97/// \[dpq\]logis are calculated directly from the definitions.
98///
99/// rlogis uses inversion.
100///
101/// # References
102///
103/// Becker, R. A., Chambers, J. M. and Wilks, A. R. (1988) The New S Language. Wadsworth &
104/// Brooks/Cole.
105///
106/// Johnson, N. L., Kotz, S. and Balakrishnan, N. (1995) Continuous Univariate Distributions,
107/// volume 2, chapter 23. Wiley, New York.
108///
109/// ## See Also
110///
111/// Distributions for other standard distributions.
112///
113/// ## Examples
114///
115/// Approximately equal (+/- 3)
116/// ```rust
117/// # use num_traits::FloatConst;
118/// # use r2rs_nmath::{
119/// # distribution::LogisticBuilder,
120/// # rng::MersenneTwister,
121/// # traits::{Distribution, RNG},
122/// # };
123/// # use r2rs_stats::funcs::variance;
124/// # use strafe_type::FloatConstraint;
125/// let logis = LogisticBuilder::new()
126/// .with_location(0)
127/// .with_scale(5)
128/// .build();
129///
130/// let mut rng = MersenneTwister::new();
131/// rng.set_seed(1);
132///
133/// let r = (0..4000)
134/// .map(|_| logis.random_sample(&mut rng).unwrap())
135/// .collect::<Vec<_>>();
136/// println!("{}", variance(&r));
137/// println!("{}", f64::PI().powi(2) / 3.0 * 5.0_f64.powi(2));
138/// # use std::{fs::File, io::Write};
139/// # let mut f = File::create("src/distribution/logis/doctest_out/rand.md").unwrap();
140/// # writeln!(f, "```output").unwrap();
141/// # writeln!(f, "{}", variance(&r)).unwrap();
142/// # writeln!(f, "{}", f64::PI().powi(2) / 3.0 * 5.0_f64.powi(2)).unwrap();
143/// # writeln!(f, "```").unwrap();
144/// ```
145#[cfg_attr(feature = "doc_outputs", cfg_attr(all(), doc = include_str!("doctest_out/rand.md")))]
146pub struct Logistic {
147 location: Real64,
148 scale: Rational64,
149}
150
151impl Distribution for Logistic {
152 fn density<R: Into<Real64>>(&self, x: R) -> Real64 {
153 dlogis(x, self.location, self.scale, false)
154 }
155
156 fn log_density<R: Into<Real64>>(&self, x: R) -> Real64 {
157 dlogis(x, self.location, self.scale, true)
158 }
159
160 fn probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> Probability64 {
161 plogis(q, self.location, self.scale, lower_tail)
162 }
163
164 fn log_probability<R: Into<Real64>>(&self, q: R, lower_tail: bool) -> LogProbability64 {
165 log_plogis(q, self.location, self.scale, lower_tail)
166 }
167
168 fn quantile<P: Into<Probability64>>(&self, p: P, lower_tail: bool) -> Real64 {
169 qlogis(p, self.location, self.scale, lower_tail)
170 }
171
172 fn log_quantile<LP: Into<LogProbability64>>(&self, p: LP, lower_tail: bool) -> Real64 {
173 log_qlogis(p, self.location, self.scale, lower_tail)
174 }
175
176 fn random_sample<R: RNG>(&self, rng: &mut R) -> Real64 {
177 rlogis(self.location, self.scale, rng)
178 }
179}
180
181pub struct LogisticBuilder {
182 location: Option<Real64>,
183 scale: Option<Rational64>,
184}
185
186impl LogisticBuilder {
187 pub fn new() -> Self {
188 Self {
189 location: None,
190 scale: None,
191 }
192 }
193
194 pub fn with_location<R: Into<Real64>>(&mut self, location: R) -> &mut Self {
195 self.location = Some(location.into());
196 self
197 }
198
199 pub fn with_scale<R: Into<Rational64>>(&mut self, scale: R) -> &mut Self {
200 self.scale = Some(scale.into());
201 self
202 }
203
204 pub fn build(&self) -> Logistic {
205 let location = self.location.unwrap_or(0.0.into());
206 let scale = self.scale.unwrap_or(1.0.into());
207
208 Logistic { location, scale }
209 }
210}
211
212#[cfg(test)]
213mod tests;
214
215#[cfg(all(test, feature = "enable_proptest"))]
216mod proptests;
217
218#[cfg(all(test, feature = "enable_covtest"))]
219mod covtests;