use crate::api::context::Context;
use crate::api::expr::Ex;
use crate::base::errors::SymplexError;
use super::family::{Distribution, Family, same_family};
use super::support::Support;
fn invalid(reason: impl Into<String>) -> SymplexError {
SymplexError::invalid_argument("stats", reason)
}
pub(crate) fn require_positive(e: &Ex, what: &str) -> Result<(), SymplexError> {
match e.is_positive() {
Some(false) => Err(invalid(format!("{what} must be positive, got `{e}`"))),
_ => Ok(()),
}
}
fn require_nonnegative(e: &Ex, what: &str) -> Result<(), SymplexError> {
match e.is_negative() {
Some(true) => Err(invalid(format!("{what} must be non-negative, got `{e}`"))),
_ => Ok(()),
}
}
fn exceeds(param: &Ex, bound: u32) -> bool {
(param - i64::from(bound)).is_positive() != Some(false)
}
fn raw_from_even_central(mean: &Ex, n: u32, ctx: &Context, central: impl Fn(u32) -> Ex) -> Ex {
let mut acc = ctx.zero();
for j in 0..=n / 2 {
let binom = ctx.int(i64::from(n)).binomial(&ctx.int(i64::from(2 * j)));
let c = if j == 0 { ctx.one() } else { central(2 * j) };
acc += binom * mean.powi(i64::from(n - 2 * j)) * c;
}
acc.simplify()
}
macro_rules! family_boilerplate {
($ty:ident, $name:literal, [$($field:ident),+]) => {
fn name(&self) -> &str {
$name
}
fn context(&self) -> Context {
first_ctx!(self, $($field),+)
}
fn parameters(&self) -> Vec<(&'static str, Ex)> {
vec![$((stringify!($field), self.$field.clone())),+]
}
fn eq_family(&self, other: &dyn Family) -> bool {
same_family(self, other)
}
};
}
macro_rules! first_ctx {
($self:ident, $first:ident $(, $rest:ident)*) => {
$self.$first.context()
};
}
#[derive(Clone, Debug, PartialEq)]
pub struct Normal {
pub mean: Ex,
pub std: Ex,
}
impl Family for Normal {
family_boilerplate!(Normal, "Normal", [mean, std]);
fn support(&self) -> Support {
Support::reals(&self.context())
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.context();
let two = ctx.int(2);
let z = (x - &self.mean) / &self.std;
(-(z.powi(2)) / &two).exp() / (&self.std * (&two * ctx.pi()).sqrt())
}
fn mean(&self) -> Option<Ex> {
Some(self.mean.clone())
}
fn variance(&self) -> Option<Ex> {
Some(self.std.powi(2))
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let ctx = self.context();
let mut acc = ctx.zero();
for k in 0..=n / 2 {
let binom = ctx.int(i64::from(n)).binomial(&ctx.int(i64::from(2 * k)));
let dfact = ctx.int(i64::from(2 * k)).factorial()
/ (ctx.int(2).powi(i64::from(k)) * ctx.int(i64::from(k)).factorial());
acc += binom
* dfact
* self.mean.powi(i64::from(n - 2 * k))
* self.std.powi(i64::from(2 * k));
}
Some(acc.simplify())
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
let half = ctx.rational(1, 2);
let arg = (x - &self.mean) / (&self.std * ctx.int(2).sqrt());
Some(&half + &half * arg.erf())
}
fn mgf(&self, t: &Ex) -> Option<Ex> {
let ctx = self.context();
Some((&self.mean * t + self.std.powi(2) * t.powi(2) / ctx.int(2)).exp())
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
let ctx = self.context();
Some(&self.mean + &self.std * ctx.int(2).sqrt() * (ctx.int(2) * p - 1).erfinv())
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
Some(ctx.rational(1, 2) * (ctx.int(2) * ctx.pi() * ctx.e() * self.std.powi(2)).ln())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Uniform {
pub lo: Ex,
pub hi: Ex,
}
impl Family for Uniform {
family_boilerplate!(Uniform, "Uniform", [lo, hi]);
fn support(&self) -> Support {
Support::interval(self.lo.clone(), self.hi.clone())
}
fn density(&self, _x: &Ex) -> Ex {
self.context().one() / (&self.hi - &self.lo)
}
fn mean(&self) -> Option<Ex> {
Some(((&self.lo + &self.hi) / self.context().int(2)).simplify())
}
fn variance(&self) -> Option<Ex> {
Some(((&self.hi - &self.lo).powi(2) / self.context().int(12)).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let n1 = i64::from(n) + 1;
Some(
((self.hi.powi(n1) - self.lo.powi(n1))
/ (self.context().int(n1) * (&self.hi - &self.lo)))
.simplify(),
)
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
Some((x - &self.lo) / (&self.hi - &self.lo))
}
fn mgf(&self, t: &Ex) -> Option<Ex> {
Some(((&self.hi * t).exp() - (&self.lo * t).exp()) / ((&self.hi - &self.lo) * t))
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
Some(&self.lo + p * (&self.hi - &self.lo))
}
fn entropy(&self) -> Option<Ex> {
Some((&self.hi - &self.lo).ln())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Exponential {
pub rate: Ex,
}
impl Family for Exponential {
family_boilerplate!(Exponential, "Exponential", [rate]);
fn support(&self) -> Support {
Support::half_line(self.context().zero())
}
fn density(&self, x: &Ex) -> Ex {
&self.rate * (-(&self.rate * x)).exp()
}
fn mean(&self) -> Option<Ex> {
Some(self.context().one() / &self.rate)
}
fn variance(&self) -> Option<Ex> {
Some(self.context().one() / self.rate.powi(2))
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let ctx = self.context();
Some((ctx.int(i64::from(n)).factorial() / self.rate.powi(i64::from(n))).simplify())
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
Some(self.context().one() - (-(&self.rate * x)).exp())
}
fn mgf(&self, t: &Ex) -> Option<Ex> {
Some(&self.rate / (&self.rate - t))
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
Some(-(self.context().one() - p).ln() / &self.rate)
}
fn entropy(&self) -> Option<Ex> {
Some(self.context().one() - self.rate.ln())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Gamma {
pub shape: Ex,
pub scale: Ex,
}
impl Family for Gamma {
family_boilerplate!(Gamma, "Gamma", [shape, scale]);
fn support(&self) -> Support {
Support::half_line(self.context().zero())
}
fn density(&self, x: &Ex) -> Ex {
x.pow(&(&self.shape - 1)) * (-(x / &self.scale)).exp()
/ (self.shape.gamma() * self.scale.pow(&self.shape))
}
fn mean(&self) -> Option<Ex> {
Some((&self.shape * &self.scale).simplify())
}
fn variance(&self) -> Option<Ex> {
Some((&self.shape * self.scale.powi(2)).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let n_ex = self.context().int(i64::from(n));
Some((self.scale.powi(i64::from(n)) * self.shape.rising_factorial(&n_ex)).simplify())
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
Some(((x / &self.scale).lowergamma(&self.shape) / self.shape.gamma()).eval())
}
fn mgf(&self, t: &Ex) -> Option<Ex> {
Some((self.context().one() - &self.scale * t).pow(&(-&self.shape)))
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
Some(
&self.shape
+ self.scale.ln()
+ self.shape.gamma().ln()
+ (ctx.one() - &self.shape) * self.shape.digamma(),
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ChiSquared {
pub dof: Ex,
}
impl ChiSquared {
fn as_gamma(&self) -> Gamma {
let ctx = self.context();
Gamma {
shape: &self.dof / ctx.int(2),
scale: ctx.int(2),
}
}
}
impl Family for ChiSquared {
family_boilerplate!(ChiSquared, "ChiSquared", [dof]);
fn support(&self) -> Support {
Support::half_line(self.context().zero())
}
fn density(&self, x: &Ex) -> Ex {
self.as_gamma().density(x)
}
fn mean(&self) -> Option<Ex> {
Some(self.dof.clone())
}
fn variance(&self) -> Option<Ex> {
Some((self.context().int(2) * &self.dof).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
self.as_gamma().raw_moment(n)
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
self.as_gamma().cdf(x)
}
fn mgf(&self, t: &Ex) -> Option<Ex> {
self.as_gamma().mgf(t)
}
fn entropy(&self) -> Option<Ex> {
self.as_gamma().entropy()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Beta {
pub alpha: Ex,
pub beta: Ex,
}
impl Family for Beta {
family_boilerplate!(Beta, "Beta", [alpha, beta]);
fn support(&self) -> Support {
let ctx = self.context();
Support::interval(ctx.zero(), ctx.one())
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.context();
x.pow(&(&self.alpha - 1)) * (ctx.one() - x).pow(&(&self.beta - 1))
/ self.alpha.beta(&self.beta)
}
fn mean(&self) -> Option<Ex> {
Some((&self.alpha / (&self.alpha + &self.beta)).simplify())
}
fn variance(&self) -> Option<Ex> {
let s = &self.alpha + &self.beta;
Some((&self.alpha * &self.beta / (s.powi(2) * (&s + 1))).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let n_ex = self.context().int(i64::from(n));
Some(
(self.alpha.rising_factorial(&n_ex)
/ (&self.alpha + &self.beta).rising_factorial(&n_ex))
.simplify(),
)
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
Some(
x.betainc_regularized(&self.alpha, &self.beta, &ctx.zero())
.eval(),
)
}
fn entropy(&self) -> Option<Ex> {
let s = &self.alpha + &self.beta;
Some(
self.alpha.beta(&self.beta).ln()
- (&self.alpha - 1) * self.alpha.digamma()
- (&self.beta - 1) * self.beta.digamma()
+ (&s - 2) * s.digamma(),
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Cauchy {
pub location: Ex,
pub scale: Ex,
}
impl Family for Cauchy {
family_boilerplate!(Cauchy, "Cauchy", [location, scale]);
fn support(&self) -> Support {
Support::reals(&self.context())
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.context();
ctx.one()
/ (ctx.pi() * &self.scale * (ctx.one() + ((x - &self.location) / &self.scale).powi(2)))
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
Some(ctx.rational(1, 2) + ((x - &self.location) / &self.scale).atan() / ctx.pi())
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
let ctx = self.context();
Some(&self.location + &self.scale * (ctx.pi() * (p - ctx.rational(1, 2))).tan())
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
Some((ctx.int(4) * ctx.pi() * &self.scale).ln())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Laplace {
pub mean: Ex,
pub scale: Ex,
}
impl Family for Laplace {
family_boilerplate!(Laplace, "Laplace", [mean, scale]);
fn support(&self) -> Support {
Support::reals(&self.context())
}
fn density(&self, x: &Ex) -> Ex {
(-((x - &self.mean).abs() / &self.scale)).exp() / (self.context().int(2) * &self.scale)
}
fn mean(&self) -> Option<Ex> {
Some(self.mean.clone())
}
fn variance(&self) -> Option<Ex> {
Some((self.context().int(2) * self.scale.powi(2)).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let ctx = self.context();
Some(raw_from_even_central(&self.mean, n, &ctx, |k| {
ctx.int(i64::from(k)).factorial() * self.scale.powi(i64::from(k))
}))
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
let half = ctx.rational(1, 2);
let z = (x - &self.mean) / &self.scale;
let below = &half * z.exp();
let above = ctx.one() - &half * (-z).exp();
Some(Ex::piecewise(&[
(&below, &x.lt(&self.mean)),
(&above, &x.ge(&self.mean)),
]))
}
fn mgf(&self, t: &Ex) -> Option<Ex> {
Some((&self.mean * t).exp() / (self.context().one() - self.scale.powi(2) * t.powi(2)))
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
let ctx = self.context();
let d = p - ctx.rational(1, 2);
Some(&self.mean - &self.scale * d.sign() * (ctx.one() - ctx.int(2) * d.abs()).ln())
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
Some(ctx.one() + (ctx.int(2) * &self.scale).ln())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Logistic {
pub mean: Ex,
pub scale: Ex,
}
impl Family for Logistic {
family_boilerplate!(Logistic, "Logistic", [mean, scale]);
fn support(&self) -> Support {
Support::reals(&self.context())
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.context();
let e = (-((x - &self.mean) / &self.scale)).exp();
&e / (&self.scale * (ctx.one() + &e).powi(2))
}
fn mean(&self) -> Option<Ex> {
Some(self.mean.clone())
}
fn variance(&self) -> Option<Ex> {
let ctx = self.context();
Some((self.scale.powi(2) * ctx.pi().powi(2) / ctx.int(3)).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let ctx = self.context();
Some(raw_from_even_central(&self.mean, n, &ctx, |k| {
let sign = if (k / 2) % 2 == 1 { 1 } else { -1 };
let k_ex = ctx.int(i64::from(k));
ctx.int(sign)
* (ctx.int(2).powi(i64::from(k)) - 2)
* k_ex.bernoulli_number()
* (ctx.pi() * &self.scale).powi(i64::from(k))
}))
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
Some(ctx.one() / (ctx.one() + (-((x - &self.mean) / &self.scale)).exp()))
}
fn mgf(&self, t: &Ex) -> Option<Ex> {
let ctx = self.context();
let st = &self.scale * t;
Some((&self.mean * t).exp() * (ctx.one() - &st).beta(&(ctx.one() + &st)))
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
Some(&self.mean + &self.scale * (p / (self.context().one() - p)).ln())
}
fn entropy(&self) -> Option<Ex> {
Some(self.scale.ln() + 2)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct LogNormal {
pub mu: Ex,
pub sigma: Ex,
}
impl Family for LogNormal {
family_boilerplate!(LogNormal, "LogNormal", [mu, sigma]);
fn support(&self) -> Support {
Support::half_line(self.context().zero())
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.context();
let two = ctx.int(2);
(-((x.ln() - &self.mu).powi(2)) / (&two * self.sigma.powi(2))).exp()
/ (x * &self.sigma * (&two * ctx.pi()).sqrt())
}
fn mean(&self) -> Option<Ex> {
Some((&self.mu + self.sigma.powi(2) / self.context().int(2)).exp())
}
fn variance(&self) -> Option<Ex> {
let ctx = self.context();
let s2 = self.sigma.powi(2);
Some(((s2.exp() - 1) * (ctx.int(2) * &self.mu + &s2).exp()).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let ctx = self.context();
let n_ex = ctx.int(i64::from(n));
Some((&n_ex * &self.mu + n_ex.powi(2) * self.sigma.powi(2) / ctx.int(2)).exp())
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
let half = ctx.rational(1, 2);
let arg = (x.ln() - &self.mu) / (&self.sigma * ctx.int(2).sqrt());
Some(&half + &half * arg.erf())
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
let ctx = self.context();
Some((&self.mu + &self.sigma * ctx.int(2).sqrt() * (ctx.int(2) * p - 1).erfinv()).exp())
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
Some(
&self.mu
+ ctx.rational(1, 2) * (ctx.int(2) * ctx.pi() * ctx.e() * self.sigma.powi(2)).ln(),
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct StudentT {
pub dof: Ex,
}
impl Family for StudentT {
family_boilerplate!(StudentT, "StudentT", [dof]);
fn support(&self) -> Support {
Support::reals(&self.context())
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.context();
let half = ctx.rational(1, 2);
let nu1 = (&self.dof + 1) * ½
nu1.gamma() / ((&self.dof * ctx.pi()).sqrt() * (&self.dof * &half).gamma())
* (ctx.one() + x.powi(2) / &self.dof).pow(&(-nu1))
}
fn mean(&self) -> Option<Ex> {
exceeds(&self.dof, 1).then(|| self.context().zero())
}
fn variance(&self) -> Option<Ex> {
exceeds(&self.dof, 2).then(|| (&self.dof / (&self.dof - 2)).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
if !exceeds(&self.dof, n) {
return None;
}
let ctx = self.context();
if n % 2 == 1 {
return Some(ctx.zero());
}
let m = n / 2;
let mut acc = self.dof.powi(i64::from(m));
for i in 1..=m {
acc *= ctx.int(i64::from(2 * i - 1)) / (&self.dof - i64::from(2 * i));
}
Some(acc.simplify())
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
let half = ctx.rational(1, 2);
let z = &self.dof / (x.powi(2) + &self.dof);
let tail = &half * z.betainc_regularized(&(&self.dof * &half), &half, &ctx.zero());
let above = ctx.one() - &tail;
Some(Ex::piecewise(&[
(&tail, &x.lt(&ctx.zero())),
(&above, &x.ge(&ctx.zero())),
]))
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
let half = ctx.rational(1, 2);
let a = (&self.dof + 1) * ½
let b = &self.dof * ½
Some(&a * (a.digamma() - b.digamma()) + (self.dof.sqrt() * b.beta(&half)).ln())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FDistribution {
pub d1: Ex,
pub d2: Ex,
}
impl Family for FDistribution {
family_boilerplate!(FDistribution, "FDistribution", [d1, d2]);
fn support(&self) -> Support {
Support::half_line(self.context().zero())
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.context();
let half = ctx.rational(1, 2);
let num = (&self.d1 * x).pow(&self.d1) * self.d2.pow(&self.d2)
/ (&self.d1 * x + &self.d2).pow(&(&self.d1 + &self.d2));
num.sqrt() / (x * (&self.d1 * &half).beta(&(&self.d2 * &half)))
}
fn mean(&self) -> Option<Ex> {
exceeds(&self.d2, 2).then(|| (&self.d2 / (&self.d2 - 2)).simplify())
}
fn variance(&self) -> Option<Ex> {
exceeds(&self.d2, 4).then(|| {
let ctx = self.context();
(ctx.int(2) * self.d2.powi(2) * (&self.d1 + &self.d2 - 2)
/ (&self.d1 * (&self.d2 - 2).powi(2) * (&self.d2 - 4)))
.simplify()
})
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
if !exceeds(&self.d2, 2 * n) {
return None;
}
let ctx = self.context();
let half = ctx.rational(1, 2);
let n_ex = ctx.int(i64::from(n));
let a = &self.d1 * ½
let b = &self.d2 * ½
Some(
((&self.d2 / &self.d1).powi(i64::from(n))
* (&a + &n_ex).gamma()
* (&b - &n_ex).gamma()
/ (a.gamma() * b.gamma()))
.simplify(),
)
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
let half = ctx.rational(1, 2);
let z = &self.d1 * x / (&self.d1 * x + &self.d2);
Some(z.betainc_regularized(&(&self.d1 * &half), &(&self.d2 * &half), &ctx.zero()))
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Weibull {
pub scale: Ex,
pub shape: Ex,
}
impl Family for Weibull {
family_boilerplate!(Weibull, "Weibull", [scale, shape]);
fn support(&self) -> Support {
Support::half_line(self.context().zero())
}
fn density(&self, x: &Ex) -> Ex {
let z = x / &self.scale;
(&self.shape / &self.scale) * z.pow(&(&self.shape - 1)) * (-(z.pow(&self.shape))).exp()
}
fn mean(&self) -> Option<Ex> {
let ctx = self.context();
Some((&self.scale * (ctx.one() + ctx.one() / &self.shape).gamma()).simplify())
}
fn variance(&self) -> Option<Ex> {
let ctx = self.context();
let g1 = (ctx.one() + ctx.one() / &self.shape).gamma();
let g2 = (ctx.one() + ctx.int(2) / &self.shape).gamma();
Some((self.scale.powi(2) * (g2 - g1.powi(2))).simplify())
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let ctx = self.context();
let n_ex = ctx.int(i64::from(n));
Some((self.scale.powi(i64::from(n)) * (ctx.one() + &n_ex / &self.shape).gamma()).simplify())
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
Some(self.context().one() - (-((x / &self.scale).pow(&self.shape))).exp())
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
let ctx = self.context();
Some(&self.scale * (-(ctx.one() - p).ln()).pow(&(ctx.one() / &self.shape)))
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
Some(
ctx.euler_gamma() * (ctx.one() - ctx.one() / &self.shape)
+ (&self.scale / &self.shape).ln()
+ 1,
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Pareto {
pub scale: Ex,
pub shape: Ex,
}
impl Family for Pareto {
family_boilerplate!(Pareto, "Pareto", [scale, shape]);
fn support(&self) -> Support {
Support::half_line(self.scale.clone())
}
fn density(&self, x: &Ex) -> Ex {
&self.shape * self.scale.pow(&self.shape) / x.pow(&(&self.shape + 1))
}
fn mean(&self) -> Option<Ex> {
exceeds(&self.shape, 1).then(|| (&self.shape * &self.scale / (&self.shape - 1)).simplify())
}
fn variance(&self) -> Option<Ex> {
exceeds(&self.shape, 2).then(|| {
(self.scale.powi(2) * &self.shape / ((&self.shape - 1).powi(2) * (&self.shape - 2)))
.simplify()
})
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
let n_ex = self.context().int(i64::from(n));
exceeds(&self.shape, n).then(|| {
(&self.shape * self.scale.powi(i64::from(n)) / (&self.shape - &n_ex)).simplify()
})
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
Some(self.context().one() - (&self.scale / x).pow(&self.shape))
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
let ctx = self.context();
Some(&self.scale * (ctx.one() - p).pow(&(-(ctx.one() / &self.shape))))
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
Some((&self.scale / &self.shape).ln() + ctx.one() / &self.shape + 1)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Triangular {
pub lo: Ex,
pub hi: Ex,
pub mode: Ex,
}
impl Triangular {
fn mode_at_lo(&self) -> bool {
(&self.mode - &self.lo).is_zero() == Some(true)
}
fn mode_at_hi(&self) -> bool {
(&self.hi - &self.mode).is_zero() == Some(true)
}
fn two_pieces(&self, x: &Ex, rising: Ex, falling: Ex) -> Ex {
if self.mode_at_lo() {
falling
} else if self.mode_at_hi() {
rising
} else {
Ex::piecewise(&[(&rising, &x.le(&self.mode)), (&falling, &x.gt(&self.mode))])
}
}
}
impl Family for Triangular {
family_boilerplate!(Triangular, "Triangular", [lo, hi, mode]);
fn support(&self) -> Support {
Support::interval(self.lo.clone(), self.hi.clone())
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.context();
let two = ctx.int(2);
let width = &self.hi - &self.lo;
let rising = &two * (x - &self.lo) / (&width * (&self.mode - &self.lo));
let falling = &two * (&self.hi - x) / (&width * (&self.hi - &self.mode));
self.two_pieces(x, rising, falling)
}
fn mean(&self) -> Option<Ex> {
Some(((&self.lo + &self.hi + &self.mode) / self.context().int(3)).simplify())
}
fn variance(&self) -> Option<Ex> {
let (a, b, c) = (&self.lo, &self.hi, &self.mode);
Some(
((a.powi(2) + b.powi(2) + c.powi(2) - a * b - a * c - b * c) / self.context().int(18))
.simplify(),
)
}
fn raw_moment(&self, n: u32) -> Option<Ex> {
if self.mode_at_lo() || self.mode_at_hi() {
return None;
}
let ctx = self.context();
let (a, b, c) = (&self.lo, &self.hi, &self.mode);
let e = i64::from(n) + 2;
let num = ctx.int(2) * (a.powi(e) * (b - c) - b.powi(e) * (a - c) + c.powi(e) * (a - b));
let den = ctx.int(e - 1) * ctx.int(e) * (a - b) * (a - c) * (b - c);
Some((num / den).simplify())
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
let ctx = self.context();
let width = &self.hi - &self.lo;
let rising = (x - &self.lo).powi(2) / (&width * (&self.mode - &self.lo));
let falling = ctx.one() - (&self.hi - x).powi(2) / (&width * (&self.hi - &self.mode));
Some(self.two_pieces(x, rising, falling))
}
fn mgf(&self, t: &Ex) -> Option<Ex> {
let ctx = self.context();
let (a, b, c) = (&self.lo, &self.hi, &self.mode);
let width = b - a;
if self.mode_at_lo() {
let num = ctx.int(2) * ((b * t).exp() - (a * t).exp() - &width * t * (a * t).exp());
return Some(num / (width.powi(2) * t.powi(2)));
}
if self.mode_at_hi() {
let num = ctx.int(2) * (&width * t * (b * t).exp() - (b * t).exp() + (a * t).exp());
return Some(num / (width.powi(2) * t.powi(2)));
}
let num = ctx.int(2)
* ((b - c) * (a * t).exp() - (b - a) * (c * t).exp() + (c - a) * (b * t).exp());
let den = (b - a) * (c - a) * (b - c) * t.powi(2);
Some(num / den)
}
fn quantile(&self, p: &Ex) -> Option<Ex> {
let ctx = self.context();
let width = &self.hi - &self.lo;
let rising = &self.lo + (p * &width * (&self.mode - &self.lo)).sqrt();
let falling = &self.hi - ((ctx.one() - p) * &width * (&self.hi - &self.mode)).sqrt();
if self.mode_at_lo() {
return Some(falling);
}
if self.mode_at_hi() {
return Some(rising);
}
let threshold = (&self.mode - &self.lo) / &width;
Some(Ex::piecewise(&[
(&rising, &p.lt(&threshold)),
(&falling, &p.ge(&threshold)),
]))
}
fn entropy(&self) -> Option<Ex> {
let ctx = self.context();
Some(ctx.rational(1, 2) + ((&self.hi - &self.lo) / ctx.int(2)).ln())
}
}
impl Distribution {
pub fn try_normal(mean: Ex, std: Ex) -> Result<Distribution, SymplexError> {
require_positive(&std, "the standard deviation")?;
Ok(Distribution::normal(mean, std))
}
pub fn normal(mean: Ex, std: Ex) -> Distribution {
Distribution::from_family(Normal { mean, std })
}
pub fn try_uniform(lo: Ex, hi: Ex) -> Result<Distribution, SymplexError> {
require_positive(&(&hi - &lo), "the width `b − a`")?;
Ok(Distribution::uniform(lo, hi))
}
pub fn uniform(lo: Ex, hi: Ex) -> Distribution {
Distribution::from_family(Uniform { lo, hi })
}
pub fn try_exponential(rate: Ex) -> Result<Distribution, SymplexError> {
require_positive(&rate, "the rate")?;
Ok(Distribution::exponential(rate))
}
pub fn exponential(rate: Ex) -> Distribution {
Distribution::from_family(Exponential { rate })
}
pub fn try_gamma(shape: Ex, scale: Ex) -> Result<Distribution, SymplexError> {
require_positive(&shape, "the shape")?;
require_positive(&scale, "the scale")?;
Ok(Distribution::gamma(shape, scale))
}
pub fn gamma(shape: Ex, scale: Ex) -> Distribution {
Distribution::from_family(Gamma { shape, scale })
}
pub fn try_chi_squared(dof: Ex) -> Result<Distribution, SymplexError> {
require_positive(&dof, "the degrees of freedom")?;
Ok(Distribution::chi_squared(dof))
}
pub fn chi_squared(dof: Ex) -> Distribution {
Distribution::from_family(ChiSquared { dof })
}
pub fn try_beta(alpha: Ex, beta: Ex) -> Result<Distribution, SymplexError> {
require_positive(&alpha, "the shape α")?;
require_positive(&beta, "the shape β")?;
Ok(Distribution::beta(alpha, beta))
}
pub fn beta(alpha: Ex, beta: Ex) -> Distribution {
Distribution::from_family(Beta { alpha, beta })
}
pub fn try_cauchy(location: Ex, scale: Ex) -> Result<Distribution, SymplexError> {
require_positive(&scale, "the scale")?;
Ok(Distribution::cauchy(location, scale))
}
pub fn cauchy(location: Ex, scale: Ex) -> Distribution {
Distribution::from_family(Cauchy { location, scale })
}
pub fn try_laplace(mean: Ex, scale: Ex) -> Result<Distribution, SymplexError> {
require_positive(&scale, "the scale")?;
Ok(Distribution::laplace(mean, scale))
}
pub fn laplace(mean: Ex, scale: Ex) -> Distribution {
Distribution::from_family(Laplace { mean, scale })
}
pub fn try_logistic(mean: Ex, scale: Ex) -> Result<Distribution, SymplexError> {
require_positive(&scale, "the scale")?;
Ok(Distribution::logistic(mean, scale))
}
pub fn logistic(mean: Ex, scale: Ex) -> Distribution {
Distribution::from_family(Logistic { mean, scale })
}
pub fn try_log_normal(mu: Ex, sigma: Ex) -> Result<Distribution, SymplexError> {
require_positive(&sigma, "the standard deviation of ln X")?;
Ok(Distribution::log_normal(mu, sigma))
}
pub fn log_normal(mu: Ex, sigma: Ex) -> Distribution {
Distribution::from_family(LogNormal { mu, sigma })
}
pub fn try_student_t(dof: Ex) -> Result<Distribution, SymplexError> {
require_positive(&dof, "the degrees of freedom")?;
Ok(Distribution::student_t(dof))
}
pub fn student_t(dof: Ex) -> Distribution {
Distribution::from_family(StudentT { dof })
}
pub fn try_f_distribution(d1: Ex, d2: Ex) -> Result<Distribution, SymplexError> {
require_positive(&d1, "the numerator degrees of freedom")?;
require_positive(&d2, "the denominator degrees of freedom")?;
Ok(Distribution::f_distribution(d1, d2))
}
pub fn f_distribution(d1: Ex, d2: Ex) -> Distribution {
Distribution::from_family(FDistribution { d1, d2 })
}
pub fn try_weibull(scale: Ex, shape: Ex) -> Result<Distribution, SymplexError> {
require_positive(&scale, "the scale")?;
require_positive(&shape, "the shape")?;
Ok(Distribution::weibull(scale, shape))
}
pub fn weibull(scale: Ex, shape: Ex) -> Distribution {
Distribution::from_family(Weibull { scale, shape })
}
pub fn try_pareto(scale: Ex, shape: Ex) -> Result<Distribution, SymplexError> {
require_positive(&scale, "the scale x_m")?;
require_positive(&shape, "the shape α")?;
Ok(Distribution::pareto(scale, shape))
}
pub fn pareto(scale: Ex, shape: Ex) -> Distribution {
Distribution::from_family(Pareto { scale, shape })
}
pub fn try_triangular(lo: Ex, hi: Ex, mode: Ex) -> Result<Distribution, SymplexError> {
require_positive(&(&hi - &lo), "the width `b − a`")?;
require_nonnegative(&(&mode - &lo), "`c − a` (the mode must lie in [a, b])")?;
require_nonnegative(&(&hi - &mode), "`b − c` (the mode must lie in [a, b])")?;
Ok(Distribution::triangular(lo, hi, mode))
}
pub fn triangular(lo: Ex, hi: Ex, mode: Ex) -> Distribution {
Distribution::from_family(Triangular { lo, hi, mode })
}
}