use std::any::Any;
use std::fmt;
use std::sync::Arc;
use crate::api::context::Context;
use crate::api::expr::Ex;
use crate::api::poly_ex::Poly;
use crate::base::errors::SymplexError;
use super::sample::Rng;
use super::support::{Kind, Piece, Support, is_neg_inf, is_pos_inf};
pub type Sampler = Box<dyn FnMut(&mut Rng) -> f64 + Send>;
pub trait Family: Any + Send + Sync + fmt::Debug {
fn name(&self) -> &str;
fn context(&self) -> Context;
fn support(&self) -> Support;
fn density(&self, x: &Ex) -> Ex;
fn parameters(&self) -> Vec<(&'static str, Ex)>;
fn eq_family(&self, other: &dyn Family) -> bool;
fn mean(&self) -> Option<Ex> {
None
}
fn variance(&self) -> Option<Ex> {
None
}
fn raw_moment(&self, _n: u32) -> Option<Ex> {
None
}
fn cdf(&self, _x: &Ex) -> Option<Ex> {
None
}
fn mgf(&self, _t: &Ex) -> Option<Ex> {
None
}
fn quantile(&self, _p: &Ex) -> Option<Ex> {
None
}
fn entropy(&self) -> Option<Ex> {
None
}
fn probability_of(&self, _region: &Support) -> Option<Result<Ex, SymplexError>> {
None
}
fn expectation_over(
&self,
_g: &Ex,
_x: &Ex,
_region: &Support,
) -> Option<Result<Ex, SymplexError>> {
None
}
fn sampler(&self) -> Option<Result<Sampler, SymplexError>> {
None
}
fn fmt_display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(", self.name())?;
for (i, (_, p)) in self.parameters().iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{p}")?;
}
f.write_str(")")
}
}
pub fn same_family<T: Family + PartialEq>(a: &T, other: &dyn Family) -> bool {
(other as &dyn Any)
.downcast_ref::<T>()
.is_some_and(|b| a == b)
}
#[derive(Clone)]
pub struct Distribution(Arc<dyn Family>);
impl PartialEq for Distribution {
fn eq(&self, other: &Self) -> bool {
self.0.eq_family(other.0.as_ref())
}
}
impl fmt::Debug for Distribution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&*self.0, f)
}
}
impl fmt::Display for Distribution {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_display(f)
}
}
pub(crate) fn fresh_symbol(ctx: &Context, prefix: &str, avoid: &[&Ex]) -> Ex {
let mut n = 0u32;
loop {
let name = if n == 0 {
format!("_{prefix}")
} else {
format!("_{prefix}{n}")
};
let s = ctx.symbol(&name);
if !avoid.iter().any(|e| e.contains(&s)) {
return s;
}
n += 1;
}
}
fn not_implemented(msg: impl Into<String>) -> SymplexError {
SymplexError::NotImplemented(msg.into())
}
impl Distribution {
pub fn from_family(family: impl Family) -> Self {
Distribution(Arc::new(family))
}
pub fn family(&self) -> &dyn Family {
self.0.as_ref()
}
pub fn downcast_ref<T: Family>(&self) -> Option<&T> {
(self.0.as_ref() as &dyn Any).downcast_ref::<T>()
}
pub fn context(&self) -> Context {
self.0.context()
}
pub fn name(&self) -> &str {
self.0.name()
}
pub fn parameters(&self) -> Vec<(&'static str, Ex)> {
self.0.parameters()
}
pub fn support(&self) -> Support {
self.0.support()
}
pub fn kind(&self) -> Kind {
self.0.support().kind()
}
pub fn is_continuous(&self) -> bool {
self.kind() == Kind::Continuous
}
pub fn density(&self, x: &Ex) -> Ex {
self.0.density(x)
}
fn param_exprs(&self) -> Vec<Ex> {
self.0.parameters().into_iter().map(|(_, e)| e).collect()
}
pub(crate) fn fresh_var(&self, prefix: &str, also: &[&Ex]) -> Ex {
let params = self.param_exprs();
let mut avoid: Vec<&Ex> = params.iter().collect();
avoid.extend_from_slice(also);
fresh_symbol(&self.context(), prefix, &avoid)
}
pub fn mean(&self) -> Ex {
match self.0.mean() {
Some(m) => m,
None => {
let x = self.fresh_var("x", &[]);
self.expectation(&x, &x)
}
}
}
pub fn variance(&self) -> Ex {
match self.0.variance() {
Some(v) => v,
None => {
let m = self.mean();
(self.moment(2) - m.powi(2)).simplify()
}
}
}
pub fn std(&self) -> Ex {
self.variance().sqrt()
}
pub fn moment(&self, n: u32) -> Ex {
if n == 0 {
return self.context().one();
}
match self.0.raw_moment(n) {
Some(m) => m,
None => {
let x = self.fresh_var("x", &[]);
let integrand = x.powi(i64::from(n)) * self.0.density(&x);
self.integrate_over(&integrand, &x, &self.support())
}
}
}
pub fn central_moment(&self, n: u32) -> Ex {
let mu = self.mean();
let x = self.fresh_var("x", &[&mu]);
self.expectation(&(&x - mu).powi(i64::from(n)), &x)
}
pub fn skewness(&self) -> Ex {
(self.central_moment(3) / self.std().powi(3)).simplify()
}
pub fn kurtosis(&self) -> Ex {
(self.central_moment(4) / self.variance().powi(2)).simplify()
}
fn cdf_on_support(&self, x: &Ex) -> Ex {
if let Some(c) = self.0.cdf(x) {
return c;
}
let support = self.support();
if let Some(values) = support.as_points() {
let mut acc = self.context().zero();
for v in &values {
let p = self.0.density(v);
match (x - v).is_nonnegative() {
Some(true) => acc += p,
Some(false) => {}
None => acc += p * (x - v).heaviside(),
}
}
return acc.simplify();
}
let t = self.fresh_var("t", &[x]);
let dens = self.0.density(&t);
let ctx = self.context();
let lo = match support.as_interval() {
Some((lo, _, _, _)) => lo.clone(),
None => ctx.neg_infinity(),
};
match support.kind() {
Kind::Continuous => dens.integrate_definite(&t, &lo, x),
Kind::Discrete => dens.summation(&t, &lo, &x.floor()),
}
}
pub fn cdf(&self, x: &Ex) -> Ex {
let ctx = self.context();
let support = self.support();
let Some((lo, hi, _, _)) = support.as_interval() else {
return self.cdf_on_support(x);
};
let lo_finite = !is_neg_inf(lo);
let hi_finite = !is_pos_inf(hi);
if lo_finite && (x - lo).is_negative() == Some(true) {
return ctx.zero();
}
if hi_finite && (x - hi).is_nonnegative() == Some(true) {
return ctx.one();
}
let on = self.cdf_on_support(x);
if (!lo_finite || (x - lo).is_nonnegative() == Some(true))
&& (!hi_finite || (hi - x).is_positive() == Some(true))
{
return if x.free_symbols().is_empty() {
on.eval()
} else {
on
};
}
let mut pairs: Vec<(Ex, crate::api::expr::BoolEx)> = Vec::new();
if lo_finite {
pairs.push((ctx.zero(), x.lt(lo)));
}
if hi_finite {
pairs.push((on, x.lt(hi)));
pairs.push((ctx.one(), ctx.bool_true()));
} else {
pairs.push((on, ctx.bool_true()));
}
let refs: Vec<(&Ex, &crate::api::expr::BoolEx)> =
pairs.iter().map(|(a, b)| (a, b)).collect();
Ex::piecewise(&refs)
}
pub fn mgf(&self, t: &Ex) -> Ex {
match self.0.mgf(t) {
Some(m) => m,
None => {
let x = self.fresh_var("x", &[t]);
self.expectation(&(t * &x).exp(), &x)
}
}
}
pub fn characteristic_function(&self, t: &Ex) -> Ex {
let it = self.context().i_unit() * t;
match self.0.mgf(&it) {
Some(m) => m,
None => {
let x = self.fresh_var("x", &[t]);
self.expectation(&(&it * &x).exp(), &x)
}
}
}
pub fn quantile(&self, p: &Ex) -> Option<Ex> {
self.0.quantile(p)
}
pub fn median(&self) -> Option<Ex> {
self.quantile(&self.context().rational(1, 2))
}
pub fn quantile_f64(&self, p: f64) -> Result<f64, SymplexError> {
if !(p > 0.0 && p < 1.0) {
return Err(SymplexError::invalid_argument(
"quantile_f64",
format!("p must lie strictly between 0 and 1, got {p}"),
));
}
let ctx = self.context();
if let Some(q) = self.0.quantile(&ctx.from_f64(p)?) {
return q.eval_f64();
}
let x = self.fresh_var("x", &[]);
let name = x.to_string();
let cdf_ex = self.cdf(&x);
let compiled = cdf_ex.compile(&[name.as_str()]).ok();
let cdf = |v: f64| -> f64 {
match &compiled {
Some(f) => f.call(&[v]),
None => ctx
.from_f64(v)
.and_then(|vv| cdf_ex.subs(&x, &vv).eval_f64())
.unwrap_or(f64::NAN),
}
};
let support = self.support();
let (lo, hi) = match support.as_interval() {
Some((lo, hi, _, _)) => (
if is_neg_inf(lo) {
f64::NEG_INFINITY
} else {
lo.eval_f64()?
},
if is_pos_inf(hi) {
f64::INFINITY
} else {
hi.eval_f64()?
},
),
None => {
let values = support.as_points().ok_or_else(|| {
SymplexError::computation_failed("quantile_f64", "unsupported support shape")
})?;
let mut pts: Vec<(f64, f64)> = values
.iter()
.map(|v| Ok((v.eval_f64()?, self.0.density(v).eval_f64()?)))
.collect::<Result<_, SymplexError>>()?;
let _ = &cdf;
pts.sort_by(|a, b| a.0.total_cmp(&b.0));
let mut acc = 0.0;
for (v, m) in pts {
acc += m;
if acc >= p - 1e-12 {
return Ok(v);
}
}
return Err(SymplexError::computation_failed(
"quantile_f64",
"the masses do not reach p",
));
}
};
let g = |v: f64| cdf(v) - p;
let (mut a, mut b) = match (lo.is_finite(), hi.is_finite()) {
(true, true) => (lo, hi),
(true, false) => (lo, lo + 1.0),
(false, true) => (hi - 1.0, hi),
(false, false) => (-1.0, 1.0),
};
let mut step = 1.0;
for _ in 0..200 {
if g(a) <= 0.0 && g(b) >= 0.0 {
break;
}
step *= 2.0;
if g(a) > 0.0 && !lo.is_finite() {
a -= step;
}
if g(b) < 0.0 && !hi.is_finite() {
b += step;
}
if (lo.is_finite() && g(a) > 0.0) || (hi.is_finite() && g(b) < 0.0) {
break;
}
}
let opts = crate::domains::optimize::RootOpts::default();
let root = crate::domains::optimize::brent_root(g, a, b, &opts)
.map_err(|e| SymplexError::computation_failed("quantile_f64", e.to_string()))?;
Ok(match support.kind() {
Kind::Continuous => root,
Kind::Discrete => {
let mut k = root.floor();
if cdf(k) < p - 1e-12 {
k += 1.0;
}
k
}
})
}
pub fn entropy(&self) -> Ex {
if let Some(h) = self.0.entropy() {
return h;
}
let x = self.fresh_var("x", &[]);
let neg_log_density = (-self.0.density(&x).ln().expand_log()).simplify();
self.expectation(&neg_log_density, &x)
}
pub fn expectation(&self, g: &Ex, x: &Ex) -> Ex {
if let Some(by_moments) = self.expectation_by_moments(g, x) {
return by_moments;
}
let integrand = g * self.0.density(x);
self.integrate_over(&integrand, x, &self.support())
}
fn expectation_by_moments(&self, g: &Ex, x: &Ex) -> Option<Ex> {
let poly = Poly::new(&g.expand(), &[x])?;
let mut acc = self.context().zero();
for (exps, coeff) in poly.terms() {
let n = *exps.first()?;
let m = if n == 0 {
self.context().one()
} else {
self.0.raw_moment(n)?
};
acc += coeff * m;
}
Some(acc.simplify())
}
pub fn probability_of(&self, region: &Support) -> Result<Ex, SymplexError> {
if let Some(direct) = self.0.probability_of(region) {
return direct;
}
let ctx = self.context();
let support = self.support();
let clipped = support.intersect(region).ok_or_else(|| {
not_implemented(format!(
"probability on {support}: cannot decide which listed values lie in {region}"
))
})?;
let mut total = ctx.zero();
for piece in clipped.pieces() {
total += match piece {
Piece::Interval {
lo,
hi,
lo_open,
hi_open,
} => self.interval_mass(lo, hi, *lo_open, *hi_open, &support),
Piece::Point(v) => match support.kind() {
Kind::Continuous => ctx.zero(),
Kind::Discrete => self.0.density(v).eval(),
},
};
}
Ok(total.simplify())
}
fn interval_mass(
&self,
lo: &Ex,
hi: &Ex,
lo_open: bool,
hi_open: bool,
support: &Support,
) -> Ex {
let ctx = self.context();
let (slo, shi) = match support.as_interval() {
Some((a, b, _, _)) => (Some(a), Some(b)),
None => (None, None),
};
let at_lower_end = is_neg_inf(lo) || slo.is_some_and(|s| (lo - s).is_zero() == Some(true));
let at_upper_end = is_pos_inf(hi) || shi.is_some_and(|s| (hi - s).is_zero() == Some(true));
let probe = self.fresh_var("t", &[lo, hi]);
if self.0.cdf(&probe).is_some() {
let upper = if at_upper_end {
Some(ctx.one())
} else {
match support.kind() {
Kind::Continuous => self.0.cdf(hi),
Kind::Discrete => self.0.cdf(hi),
}
};
let lower = if at_lower_end {
Some(ctx.zero())
} else {
match support.kind() {
Kind::Continuous => self.0.cdf(lo),
Kind::Discrete => self.0.cdf(&(lo - ctx.one())),
}
};
if let (Some(u), Some(l)) = (upper, lower) {
return (u - l).simplify();
}
}
let _ = (lo_open, hi_open); let t = probe;
let dens = self.0.density(&t);
match support.kind() {
Kind::Continuous => dens.integrate_definite(&t, lo, hi).simplify(),
Kind::Discrete => dens.summation(&t, lo, hi).simplify(),
}
}
pub fn expectation_over(&self, g: &Ex, x: &Ex, region: &Support) -> Result<Ex, SymplexError> {
if let Some(direct) = self.0.expectation_over(g, x, region) {
return direct;
}
let support = self.support();
let clipped = support.intersect(region).ok_or_else(|| {
not_implemented(format!(
"expectation on {support}: cannot decide which listed values lie in {region}"
))
})?;
let integrand = g * self.0.density(x);
Ok(self.integrate_over(&integrand, x, &clipped))
}
pub(crate) fn integrate_over(&self, integrand: &Ex, x: &Ex, region: &Support) -> Ex {
let ctx = self.context();
let integrand = integrand.simplify();
let region = region.normalize_lattice();
let mut acc = ctx.zero();
for piece in region.pieces() {
acc += match piece {
Piece::Interval { lo, hi, .. } => match region.kind() {
Kind::Continuous => integrand.integrate_definite(x, lo, hi),
Kind::Discrete => integrand.summation(x, lo, hi),
},
Piece::Point(v) => integrand.subs(x, v).eval(),
};
}
acc.simplify()
}
pub fn sample(&self, n: usize, rng: &mut Rng) -> Result<Vec<f64>, SymplexError> {
let mut sampler = self.sampler()?;
Ok((0..n).map(|_| sampler(rng)).collect())
}
pub fn sample_one(&self, rng: &mut Rng) -> Result<f64, SymplexError> {
Ok(self.sampler()?(rng))
}
pub fn sampler(&self) -> Result<Sampler, SymplexError> {
if let Some(own) = self.0.sampler() {
return own;
}
let ctx = self.context();
let support = self.support();
match support.kind() {
Kind::Continuous => {
let p = self.fresh_var("p", &[]);
let q = self.0.quantile(&p).ok_or_else(|| {
not_implemented(format!(
"sampling {}: no closed-form quantile function",
self.name()
))
})?;
let name = p.to_string();
let q = q.compile(&[name.as_str()])?;
Ok(Box::new(move |rng: &mut Rng| q.call(&[rng.next_f64()])))
}
Kind::Discrete => {
if let Some(values) = support.as_points() {
let mut cumulative = Vec::with_capacity(values.len());
let mut points = Vec::with_capacity(values.len());
let mut acc = 0.0;
for v in &values {
acc += self.0.density(v).eval_f64()?;
cumulative.push(acc);
points.push(v.eval_f64()?);
}
return Ok(Box::new(move |rng: &mut Rng| {
let u = rng.next_f64() * acc;
let idx = cumulative.partition_point(|c| *c < u);
points[idx.min(points.len().saturating_sub(1))]
}));
}
let Some((lo, hi, _, _)) = support.as_interval() else {
return Err(not_implemented(format!(
"sampling {}: the support is not a single integer range",
self.name()
)));
};
if is_neg_inf(lo) || is_pos_inf(hi) {
return Err(not_implemented(format!(
"sampling {}: no sampling route for an infinite discrete support",
self.name()
)));
}
let lo_v = lo.eval_f64()?;
let hi_v = hi.eval_f64()?;
if !(lo_v.is_finite() && hi_v.is_finite() && hi_v >= lo_v) {
return Err(not_implemented(format!(
"sampling {}: the support is not a finite integer range",
self.name()
)));
}
let x = self.fresh_var("x", &[]);
let name = x.to_string();
let pmf = self.0.density(&x).compile(&[name.as_str()])?;
let mut values = Vec::new();
let mut k = lo_v;
while k <= hi_v && values.len() < 1_000_000 {
values.push(k);
k += 1.0;
}
let mut cumulative = Vec::with_capacity(values.len());
let mut acc = 0.0;
for &k in &values {
acc += pmf.call(&[k]);
cumulative.push(acc);
}
let _ = ctx;
Ok(Box::new(move |rng: &mut Rng| {
let u = rng.next_f64() * acc;
let idx = cumulative.partition_point(|c| *c < u);
values[idx.min(values.len().saturating_sub(1))]
}))
}
}
}
}