use crate::DType;
use super::special;
use crate::stats::distribution::{ContinuousDistribution, Distribution};
use crate::stats::error::{StatsError, StatsResult};
use numr::algorithm::special::SpecialFunctions;
use numr::error::Result;
use numr::ops::{ScalarOps, TensorOps};
use numr::runtime::{Runtime, RuntimeClient};
use numr::tensor::Tensor;
#[derive(Debug, Clone, Copy)]
pub struct Beta {
alpha: f64,
beta: f64,
log_norm: f64,
}
impl Beta {
pub fn new(alpha: f64, beta: f64) -> StatsResult<Self> {
if alpha <= 0.0 {
return Err(StatsError::InvalidParameter {
name: "alpha".to_string(),
value: alpha,
reason: "must be positive".to_string(),
});
}
if beta <= 0.0 {
return Err(StatsError::InvalidParameter {
name: "beta".to_string(),
value: beta,
reason: "must be positive".to_string(),
});
}
if !alpha.is_finite() || !beta.is_finite() {
return Err(StatsError::InvalidParameter {
name: "alpha/beta".to_string(),
value: alpha,
reason: "parameters must be finite".to_string(),
});
}
let log_norm = -special::lbeta(alpha, beta);
Ok(Self {
alpha,
beta,
log_norm,
})
}
pub fn alpha(&self) -> f64 {
self.alpha
}
pub fn beta(&self) -> f64 {
self.beta
}
}
impl Distribution for Beta {
fn mean(&self) -> f64 {
self.alpha / (self.alpha + self.beta)
}
fn var(&self) -> f64 {
let sum = self.alpha + self.beta;
(self.alpha * self.beta) / (sum * sum * (sum + 1.0))
}
fn entropy(&self) -> f64 {
let sum = self.alpha + self.beta;
special::lbeta(self.alpha, self.beta)
- (self.alpha - 1.0) * special::digamma(self.alpha)
- (self.beta - 1.0) * special::digamma(self.beta)
+ (sum - 2.0) * special::digamma(sum)
}
fn median(&self) -> f64 {
if self.alpha > 1.0 && self.beta > 1.0 {
(self.alpha - 1.0 / 3.0) / (self.alpha + self.beta - 2.0 / 3.0)
} else {
self.ppf(0.5).unwrap_or(self.mean())
}
}
fn mode(&self) -> f64 {
if self.alpha > 1.0 && self.beta > 1.0 {
(self.alpha - 1.0) / (self.alpha + self.beta - 2.0)
} else if self.alpha <= 1.0 && self.beta > 1.0 {
0.0
} else if self.alpha > 1.0 && self.beta <= 1.0 {
1.0
} else {
f64::NAN
}
}
fn skewness(&self) -> f64 {
let sum = self.alpha + self.beta;
2.0 * (self.beta - self.alpha) * (sum + 1.0).sqrt()
/ ((sum + 2.0) * (self.alpha * self.beta).sqrt())
}
fn kurtosis(&self) -> f64 {
let sum = self.alpha + self.beta;
let num = 6.0
* ((self.alpha - self.beta).powi(2) * (sum + 1.0)
- self.alpha * self.beta * (sum + 2.0));
let denom = self.alpha * self.beta * (sum + 2.0) * (sum + 3.0);
num / denom
}
}
impl ContinuousDistribution for Beta {
fn pdf(&self, x: f64) -> f64 {
if x <= 0.0 || x >= 1.0 {
if x == 0.0 && self.alpha < 1.0 {
return f64::INFINITY;
}
if x == 1.0 && self.beta < 1.0 {
return f64::INFINITY;
}
if x == 0.0 && self.alpha == 1.0 {
return self.log_norm.exp();
}
if x == 1.0 && self.beta == 1.0 {
return self.log_norm.exp();
}
return 0.0;
}
self.log_pdf(x).exp()
}
fn log_pdf(&self, x: f64) -> f64 {
if x <= 0.0 || x >= 1.0 {
return f64::NEG_INFINITY;
}
self.log_norm + (self.alpha - 1.0) * x.ln() + (self.beta - 1.0) * (1.0 - x).ln()
}
fn cdf(&self, x: f64) -> f64 {
if x <= 0.0 {
0.0
} else if x >= 1.0 {
1.0
} else {
special::betainc(self.alpha, self.beta, x)
}
}
fn sf(&self, x: f64) -> f64 {
if x <= 0.0 {
1.0
} else if x >= 1.0 {
0.0
} else {
special::betainc(self.beta, self.alpha, 1.0 - x)
}
}
fn ppf(&self, p: f64) -> StatsResult<f64> {
if !(0.0..=1.0).contains(&p) {
return Err(StatsError::InvalidProbability { value: p });
}
if p == 0.0 {
return Ok(0.0);
}
if p == 1.0 {
return Ok(1.0);
}
Ok(special::betaincinv(self.alpha, self.beta, p))
}
fn pdf_tensor<R: Runtime<DType = DType>, C>(
&self,
x: &Tensor<R>,
client: &C,
) -> Result<Tensor<R>>
where
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
self.log_pdf_tensor(x, client)
.and_then(|log_pdf| client.exp(&log_pdf))
}
fn log_pdf_tensor<R: Runtime<DType = DType>, C>(
&self,
x: &Tensor<R>,
client: &C,
) -> Result<Tensor<R>>
where
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
let ln_x = client.log(x)?;
let term1 = client.mul_scalar(&ln_x, self.alpha - 1.0)?;
let neg_x = client.mul_scalar(x, -1.0)?;
let one_minus_x = client.add_scalar(&neg_x, 1.0)?;
let ln_one_minus_x = client.log(&one_minus_x)?;
let term2 = client.mul_scalar(&ln_one_minus_x, self.beta - 1.0)?;
let result = client.add(&term1, &term2)?;
client.add_scalar(&result, self.log_norm)
}
fn cdf_tensor<R: Runtime<DType = DType>, C>(
&self,
x: &Tensor<R>,
client: &C,
) -> Result<Tensor<R>>
where
C: TensorOps<R> + ScalarOps<R> + SpecialFunctions<R> + RuntimeClient<R>,
{
let alpha_t = Tensor::<R>::full_scalar(x.shape(), x.dtype(), self.alpha, client.device());
let beta_t = Tensor::<R>::full_scalar(x.shape(), x.dtype(), self.beta, client.device());
client.betainc(&alpha_t, &beta_t, x)
}
fn sf_tensor<R: Runtime<DType = DType>, C>(
&self,
x: &Tensor<R>,
client: &C,
) -> Result<Tensor<R>>
where
C: TensorOps<R> + ScalarOps<R> + SpecialFunctions<R> + RuntimeClient<R>,
{
let one_minus_x = client.mul_scalar(x, -1.0)?;
let one_minus_x = client.add_scalar(&one_minus_x, 1.0)?;
let alpha_t = Tensor::<R>::full_scalar(x.shape(), x.dtype(), self.alpha, client.device());
let beta_t = Tensor::<R>::full_scalar(x.shape(), x.dtype(), self.beta, client.device());
client.betainc(&beta_t, &alpha_t, &one_minus_x)
}
fn log_cdf_tensor<R: Runtime<DType = DType>, C>(
&self,
x: &Tensor<R>,
client: &C,
) -> Result<Tensor<R>>
where
C: TensorOps<R> + ScalarOps<R> + SpecialFunctions<R> + RuntimeClient<R>,
{
let cdf = self.cdf_tensor(x, client)?;
client.log(&cdf)
}
fn ppf_tensor<R: Runtime<DType = DType>, C>(
&self,
p: &Tensor<R>,
client: &C,
) -> Result<Tensor<R>>
where
C: TensorOps<R> + ScalarOps<R> + SpecialFunctions<R> + RuntimeClient<R>,
{
let alpha_t = Tensor::<R>::full_scalar(p.shape(), p.dtype(), self.alpha, client.device());
let beta_t = Tensor::<R>::full_scalar(p.shape(), p.dtype(), self.beta, client.device());
client.betaincinv(&alpha_t, &beta_t, p)
}
fn isf_tensor<R: Runtime<DType = DType>, C>(
&self,
p: &Tensor<R>,
client: &C,
) -> Result<Tensor<R>>
where
C: TensorOps<R> + ScalarOps<R> + SpecialFunctions<R> + RuntimeClient<R>,
{
let one_minus_p = client.mul_scalar(p, -1.0)?;
let one_minus_p = client.add_scalar(&one_minus_p, 1.0)?;
let alpha_t = Tensor::<R>::full_scalar(p.shape(), p.dtype(), self.alpha, client.device());
let beta_t = Tensor::<R>::full_scalar(p.shape(), p.dtype(), self.beta, client.device());
client.betaincinv(&alpha_t, &beta_t, &one_minus_p)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_beta_creation() {
let b = Beta::new(2.0, 3.0).unwrap();
assert!((b.alpha() - 2.0).abs() < 1e-10);
assert!((b.beta() - 3.0).abs() < 1e-10);
assert!(Beta::new(0.0, 1.0).is_err());
assert!(Beta::new(1.0, 0.0).is_err());
assert!(Beta::new(-1.0, 1.0).is_err());
}
#[test]
fn test_beta_uniform() {
let b = Beta::new(1.0, 1.0).unwrap();
assert!((b.mean() - 0.5).abs() < 1e-10);
assert!((b.var() - 1.0 / 12.0).abs() < 1e-10);
assert!((b.pdf(0.5) - 1.0).abs() < 1e-10);
assert!((b.cdf(0.5) - 0.5).abs() < 1e-10);
}
#[test]
fn test_beta_moments() {
let b = Beta::new(2.0, 5.0).unwrap();
assert!((b.mean() - 2.0 / 7.0).abs() < 1e-10);
assert!((b.var() - 10.0 / 392.0).abs() < 1e-10);
assert!((b.mode() - 0.2).abs() < 1e-10);
}
#[test]
fn test_beta_pdf() {
let b = Beta::new(2.0, 2.0).unwrap();
assert!((b.pdf(0.3) - b.pdf(0.7)).abs() < 1e-10);
let mode_pdf = b.pdf(0.5);
assert!(b.pdf(0.3) < mode_pdf);
assert!(b.pdf(0.7) < mode_pdf);
assert!((b.pdf(-0.1) - 0.0).abs() < 1e-10);
assert!((b.pdf(1.1) - 0.0).abs() < 1e-10);
}
#[test]
fn test_beta_cdf() {
let b = Beta::new(2.0, 2.0).unwrap();
assert!((b.cdf(0.0) - 0.0).abs() < 1e-10);
assert!((b.cdf(0.5) - 0.5).abs() < 1e-6); assert!((b.cdf(1.0) - 1.0).abs() < 1e-10);
assert!(b.cdf(0.3) < b.cdf(0.5));
assert!(b.cdf(0.5) < b.cdf(0.7));
}
#[test]
fn test_beta_ppf() {
let b = Beta::new(2.0, 5.0).unwrap();
for p in [0.1, 0.25, 0.5, 0.75, 0.9] {
let x = b.ppf(p).unwrap();
assert!((b.cdf(x) - p).abs() < 1e-6, "Failed for p={}", p);
}
assert!(b.ppf(-0.1).is_err());
assert!(b.ppf(1.1).is_err());
}
#[test]
fn test_beta_sf() {
let b = Beta::new(2.0, 3.0).unwrap();
for x in [0.2, 0.4, 0.6, 0.8] {
assert!((b.sf(x) + b.cdf(x) - 1.0).abs() < 1e-10);
}
}
}