use std::fmt;
use crate::api::context::Context;
use crate::api::expr::Ex;
use crate::base::errors::SymplexError;
use super::data::binomial_q;
use super::family::{Distribution, Family, Sampler, same_family};
use super::sample::Rng;
use super::support::{Kind, Support, is_neg_inf, is_pos_inf};
const OP: &str = "stats::order";
fn invalid(reason: impl Into<String>) -> SymplexError {
SymplexError::invalid_argument(OP, reason)
}
const MAX_ENUMERATED: i64 = 10_000;
#[derive(Clone, Debug, PartialEq)]
pub struct OrderStatistic {
pub inner: Distribution,
pub n: usize,
pub k: usize,
}
impl OrderStatistic {
pub fn try_new(inner: Distribution, n: usize, k: usize) -> Result<Self, SymplexError> {
if n == 0 {
return Err(invalid("an order statistic needs a sample of size n ≥ 1"));
}
if k == 0 || k > n {
return Err(invalid(format!(
"the rank k must satisfy 1 ≤ k ≤ n, got k = {k}, n = {n}"
)));
}
Ok(OrderStatistic { inner, n, k })
}
fn ctx(&self) -> Context {
self.inner.context()
}
fn nk(&self) -> (Ex, Ex) {
let ctx = self.ctx();
(ctx.int(usize_to_i64(self.n)), ctx.int(usize_to_i64(self.k)))
}
fn parent_cdf(&self, x: &Ex) -> Ex {
let ctx = self.ctx();
let support = self.inner.support();
if let Some((lo, hi, _, _)) = support.as_interval() {
if !is_neg_inf(lo) && (x - lo).is_negative() == Some(true) {
return ctx.zero();
}
if !is_pos_inf(hi) && (x - hi).is_nonnegative() == Some(true) {
return ctx.one();
}
}
if let Some(c) = self.inner.family().cdf(x) {
return c;
}
if support.as_points().is_some() {
return self.inner.cdf(x);
}
let t = self.inner.fresh_var("t", &[x]);
let dens = self.inner.density(&t);
let lo = support
.as_interval()
.map_or_else(|| ctx.neg_infinity(), |(lo, _, _, _)| lo.clone());
match support.kind() {
Kind::Continuous => dens.integrate_definite(&t, &lo, x),
Kind::Discrete => dens.summation(&t, &lo, &x.floor()),
}
}
fn beta_cdf(&self, u: &Ex) -> Ex {
let ctx = self.ctx();
let (n, k) = self.nk();
u.betainc_regularized(&k, &(n - &k + 1), &ctx.zero())
}
}
fn usize_to_i64(n: usize) -> i64 {
i64::try_from(n).unwrap_or(i64::MAX)
}
impl Family for OrderStatistic {
fn name(&self) -> &str {
"OrderStatistic"
}
fn context(&self) -> Context {
self.ctx()
}
fn parameters(&self) -> Vec<(&'static str, Ex)> {
let (n, k) = self.nk();
let mut p = vec![("n", n), ("k", k)];
p.extend(self.inner.parameters());
p
}
fn eq_family(&self, other: &dyn Family) -> bool {
same_family(self, other)
}
fn support(&self) -> Support {
self.inner.support()
}
fn density(&self, x: &Ex) -> Ex {
let ctx = self.ctx();
let f_x = self.parent_cdf(x);
match self.inner.kind() {
Kind::Continuous => {
let (_, k) = self.nk();
let choose = ctx.from_ratio(binomial_q(self.n, self.k));
let mut acc = k * choose * self.inner.density(x);
if self.k > 1 {
acc *= f_x.powi(usize_to_i64(self.k - 1));
}
if self.n > self.k {
acc *= (ctx.one() - &f_x).powi(usize_to_i64(self.n - self.k));
}
acc
}
Kind::Discrete => {
let below = self.parent_cdf(&(x - 1));
self.beta_cdf(&f_x) - self.beta_cdf(&below)
}
}
}
fn cdf(&self, x: &Ex) -> Option<Ex> {
Some(self.beta_cdf(&self.parent_cdf(x)))
}
fn sampler(&self) -> Option<Result<Sampler, SymplexError>> {
let mut inner = match self.inner.sampler() {
Ok(s) => s,
Err(e) => return Some(Err(e)),
};
let (n, k) = (self.n, self.k);
let mut buf = vec![0.0_f64; n];
Some(Ok(Box::new(move |rng: &mut Rng| {
for slot in buf.iter_mut() {
*slot = inner(rng);
}
buf.sort_by(f64::total_cmp);
buf.get(k.saturating_sub(1)).copied().unwrap_or(f64::NAN)
})))
}
fn fmt_display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"OrderStatistic(k={}, n={}, {})",
self.k, self.n, self.inner
)
}
}
pub fn order_statistic(
dist: &Distribution,
n: usize,
k: usize,
) -> Result<Distribution, SymplexError> {
let family = OrderStatistic::try_new(dist.clone(), n, k)?;
if dist.kind() == Kind::Discrete
&& let Some(table) = finite_table(dist)?
{
return enumerate(dist, &table, n, k);
}
Ok(Distribution::from_family(family))
}
pub fn minimum_of(dist: &Distribution, n: usize) -> Result<Distribution, SymplexError> {
order_statistic(dist, n, 1)
}
pub fn maximum_of(dist: &Distribution, n: usize) -> Result<Distribution, SymplexError> {
order_statistic(dist, n, n)
}
fn finite_table(dist: &Distribution) -> Result<Option<Vec<(Ex, Ex)>>, SymplexError> {
let support = dist.support();
let values: Vec<Ex> = if let Some(points) = support.as_points() {
points
} else if let Some((lo, hi, _, _)) = support.as_interval() {
let (Some(lo), Some(hi)) = (lo.eval().as_i64(), hi.eval().as_i64()) else {
return Ok(None);
};
if hi < lo || hi - lo >= MAX_ENUMERATED {
return Ok(None);
}
let ctx = dist.context();
(lo..=hi).map(|v| ctx.int(v)).collect()
} else {
return Ok(None);
};
let mut keyed: Vec<(f64, Ex)> = Vec::with_capacity(values.len());
for v in values {
let key = v.eval_f64().map_err(|_| {
invalid(format!(
"the values of a finite parent must be numeric to order them, got `{v}`"
))
})?;
keyed.push((key, v));
}
keyed.sort_by(|a, b| a.0.total_cmp(&b.0));
let table = keyed
.into_iter()
.map(|(_, v)| {
let p = dist.density(&v).eval();
(v, p)
})
.collect();
Ok(Some(table))
}
fn enumerate(
dist: &Distribution,
table: &[(Ex, Ex)],
n: usize,
k: usize,
) -> Result<Distribution, SymplexError> {
let ctx = dist.context();
let g = |u: &Ex| -> Ex {
let mut acc = ctx.zero();
for j in k..=n {
let mut term = ctx.from_ratio(binomial_q(n, j));
if j > 0 {
term *= u.powi(usize_to_i64(j));
}
if n > j {
term *= (ctx.one() - u).powi(usize_to_i64(n - j));
}
acc += term;
}
acc.eval()
};
let mut cumulative = ctx.zero();
let mut previous = ctx.zero();
let mut out = Vec::with_capacity(table.len());
for (v, p) in table {
cumulative = (&cumulative + p).eval();
let current = g(&cumulative);
let mass = (¤t - &previous).simplify();
previous = current;
out.push((v.clone(), mass));
}
Ok(Distribution::finite(&ctx, out))
}