1use crate::analytic::{Domain, Pieces};
10use crate::continuous::Family;
11use crate::dist::Budget;
12use crate::error::OpResult;
13use crate::quadrature::{self, Table};
14use crate::value::{family_key, float_key};
15use std::sync::Mutex;
16
17#[derive(Clone, Copy, Debug, PartialEq)]
19pub enum Law {
20 Normal,
21 Lognormal,
22 Uniform,
23 Beta,
24 Gamma,
25 Exponential,
26 Binomial,
27 Bernoulli,
28 Poisson,
29 Geometric,
30}
31
32impl Law {
33 pub fn name(self) -> &'static str {
34 match self {
35 Law::Normal => "normal",
36 Law::Lognormal => "lognormal",
37 Law::Uniform => "uniform",
38 Law::Beta => "beta",
39 Law::Gamma => "gamma",
40 Law::Exponential => "exponential",
41 Law::Binomial => "binomial",
42 Law::Bernoulli => "bernoulli",
43 Law::Poisson => "poisson",
44 Law::Geometric => "geometric",
45 }
46 }
47
48 pub fn is_density(self) -> bool {
50 !matches!(self, Law::Binomial | Law::Bernoulli | Law::Poisson | Law::Geometric)
51 }
52
53 pub fn ln_likelihood(self, params: &[f64], v: f64) -> f64 {
57 let continuous = |family: OpResult<Family>| family.map_or(f64::NAN, |f| f.ln_pdf(v));
58 let count = v >= 0.0 && v.fract() == 0.0;
59 match (self, params) {
60 (Law::Normal, &[m, s]) => continuous(Family::normal(m, s)),
61 (Law::Lognormal, &[m, s]) => continuous(Family::lognormal(m, s)),
62 (Law::Uniform, &[a, b]) => continuous(Family::uniform(a, b)),
63 (Law::Beta, &[a, b]) => continuous(Family::beta(a, b)),
64 (Law::Gamma, &[k, s]) => continuous(Family::gamma(k, s)),
65 (Law::Exponential, &[r]) => continuous(Family::exponential(r)),
66 (_, &[.., p])
67 if matches!(self, Law::Binomial | Law::Bernoulli | Law::Geometric) && !(0.0..=1.0).contains(&p) =>
68 {
69 f64::NAN
70 }
71 (Law::Bernoulli, &[p]) => match v {
72 1.0 => libm::log(p),
73 0.0 => libm::log1p(-p),
74 _ => f64::NEG_INFINITY,
75 },
76 (Law::Binomial, &[n, p]) => {
77 if !count || v > n {
78 return f64::NEG_INFINITY;
79 }
80 let ln_choose = libm::lgamma(n + 1.0) - libm::lgamma(v + 1.0) - libm::lgamma(n - v + 1.0);
81 let success = if v == 0.0 { 0.0 } else { v * libm::log(p) };
82 let failure = if v == n { 0.0 } else { (n - v) * libm::log1p(-p) };
83 ln_choose + success + failure
84 }
85 (Law::Poisson, &[rate]) if rate > 0.0 => {
86 if !count {
87 return f64::NEG_INFINITY;
88 }
89 v * libm::log(rate) - rate - libm::lgamma(v + 1.0)
90 }
91 (Law::Poisson, &[0.0]) => {
92 if v == 0.0 {
93 0.0
94 } else {
95 f64::NEG_INFINITY
96 }
97 }
98 (Law::Poisson, _) => f64::NAN,
99 (Law::Geometric, &[p]) => {
101 if !count || v < 1.0 {
102 return f64::NEG_INFINITY;
103 }
104 let failures = if v == 1.0 { 0.0 } else { (v - 1.0) * libm::log1p(-p) };
105 failures + libm::log(p)
106 }
107 _ => f64::NAN,
108 }
109 }
110}
111
112#[derive(Clone, Debug, PartialEq)]
115pub enum Param {
116 Plain(f64),
117 Outcome(Pieces),
118}
119
120impl Param {
121 fn at(&self, u: f64, x: f64) -> f64 {
122 match self {
123 Param::Plain(v) => *v,
124 Param::Outcome(pieces) => {
125 let i = pieces.partition_point(|(end, _)| *end < u).min(pieces.len() - 1);
126 pieces[i].1.at(x)
127 }
128 }
129 }
130}
131
132#[derive(Clone, Debug, PartialEq)]
134pub struct Factor {
135 pub law: Law,
136 pub params: Vec<Param>,
137 pub value: f64,
138}
139
140type FactorKey = (
141 &'static str,
142 Vec<Result<u64, Vec<(u64, (u8, u64, u64, u64, u64))>>>,
143 u64,
144);
145
146impl Factor {
147 fn key(&self) -> FactorKey {
148 (
149 self.law.name(),
150 self.params
151 .iter()
152 .map(|p| match p {
153 Param::Plain(v) => Ok(float_key(*v)),
154 Param::Outcome(pieces) => Err(pieces.iter().map(|(end, f)| (float_key(*end), f.key())).collect()),
155 })
156 .collect(),
157 float_key(self.value),
158 )
159 }
160
161 fn ln_at(&self, u: f64, x: f64) -> f64 {
162 let mut params = [0.0; 3];
163 for (i, p) in self.params.iter().enumerate() {
164 params[i] = p.at(u, x);
165 }
166 self.law.ln_likelihood(¶ms[..self.params.len()], self.value)
167 }
168
169 fn breaks(&self, out: &mut Vec<f64>) {
171 for p in &self.params {
172 if let Param::Outcome(pieces) = p {
173 out.extend(pieces.iter().map(|(end, _)| *end));
174 }
175 }
176 }
177}
178
179#[derive(Debug)]
182pub struct Likelihood {
183 pub family: Family,
185 pub factors: Vec<Factor>,
186 pub tolerance: f64,
188 pub shift: f64,
191 table: Table,
192 moments: Mutex<Vec<(MomentKey, Moments)>>,
195}
196
197type Moments = Option<(f64, f64)>;
199
200pub type MomentKey = (Vec<(u64, (u8, u64, u64, u64, u64))>, Vec<(u64, u64)>);
202
203impl Clone for Likelihood {
204 fn clone(&self) -> Likelihood {
205 Likelihood {
206 family: self.family,
207 factors: self.factors.clone(),
208 tolerance: self.tolerance,
209 shift: self.shift,
210 table: self.table.clone(),
211 moments: Mutex::new(Vec::new()),
212 }
213 }
214}
215
216const GRID: usize = 4096;
219
220impl Likelihood {
221 pub fn new(
225 family: Family,
226 factors: Vec<Factor>,
227 tolerance: f64,
228 budget: &mut Budget,
229 ) -> OpResult<Option<Likelihood>> {
230 let ln_at = |u: f64| {
231 let x = family.quantile(u);
232 factors.iter().map(|f| f.ln_at(u, x)).sum::<f64>()
233 };
234 budget.work((GRID * factors.len()) as u64)?;
235 let mut grid: Vec<(f64, f64)> = (0..GRID)
237 .map(|i| {
238 let u = (i as f64 + 0.5) / GRID as f64;
239 (u, nan_to_impossible(ln_at(u)))
240 })
241 .collect();
242 let shift = grid.iter().map(|g| g.1).fold(f64::NEG_INFINITY, f64::max);
243 if shift == f64::NEG_INFINITY {
244 return Ok(None);
245 }
246 let mut peaks: Vec<(f64, f64)> = local_peaks(&grid);
247 peaks.sort_by(|a, b| b.1.total_cmp(&a.1));
248 peaks.truncate(4);
249 let mut shift = shift;
250 let mut breaks = vec![0.0, 1.0];
251 for f in &factors {
252 f.breaks(&mut breaks);
253 }
254 for &(peak, _) in &peaks {
255 let (u, ln, width) = refine(&ln_at, peak, 1.0 / GRID as f64, budget, factors.len())?;
256 shift = shift.max(ln);
257 breaks.push(u);
260 let mut d = width;
261 while d < 1.0 {
262 breaks.extend([u - d, u + d]);
263 d *= 2.0;
264 }
265 }
266 grid.clear();
267 breaks.retain(|b| (0.0..=1.0).contains(b));
268 breaks.sort_by(f64::total_cmp);
269 breaks.dedup();
270 let mut f = |u: f64| relative(ln_at(u), shift);
271 let parts = quadrature::adaptive(&mut f, &breaks, tolerance, 0.0, budget)?;
272 let table = Table::new(parts);
273 if table.total() <= 0.0 {
274 return Ok(None);
275 }
276 Ok(Some(Likelihood {
277 family,
278 factors,
279 tolerance,
280 shift,
281 table,
282 moments: Mutex::new(Vec::new()),
283 }))
284 }
285
286 pub fn key(&self) -> (Vec<FactorKey>, (&'static str, Vec<u64>), u64) {
287 (
288 self.factors.iter().map(Factor::key).collect(),
289 family_key(&self.family),
290 float_key(self.tolerance),
291 )
292 }
293
294 pub fn ln_at(&self, u: f64) -> f64 {
296 let x = self.family.quantile(u);
297 nan_to_impossible(self.factors.iter().map(|f| f.ln_at(u, x)).sum())
298 }
299
300 pub fn at(&self, u: f64) -> f64 {
302 relative(self.ln_at(u), self.shift)
303 }
304
305 pub fn mass(&self, d: &Domain) -> f64 {
308 let mut budget = Budget::unlimited();
309 let mut f = |u: f64| self.at(u);
310 d.0.iter()
311 .map(|&(a, b)| self.table.between(&mut f, a, b, &mut budget).unwrap_or(0.0))
312 .sum()
313 }
314
315 pub fn inverse(&self, d: &Domain, share: f64) -> f64 {
318 let mut budget = Budget::unlimited();
319 let mut f = |u: f64| self.at(u);
320 let total = self.mass(d);
321 let mut left = share.clamp(0.0, 1.0) * total;
322 for &(a, b) in &d.0 {
323 let here = self.table.between(&mut f, a, b, &mut budget).unwrap_or(0.0);
324 if left <= here {
325 let start = self.table.up_to(&mut f, a, &mut budget).unwrap_or(0.0);
326 let u = self.table.inverse(&mut f, start + left, &mut budget).unwrap_or(a);
327 return u.clamp(a, b);
328 }
329 left -= here;
330 }
331 d.0.last().map_or(0.5, |&(_, b)| b)
332 }
333
334 pub fn moments(&self, key: MomentKey, compute: impl FnOnce() -> Option<(f64, f64)>) -> Option<(f64, f64)> {
337 if let Some((_, m)) = self.moments.lock().expect("moments").iter().find(|(k, _)| *k == key) {
338 return *m;
339 }
340 let m = compute();
341 let mut cache = self.moments.lock().expect("moments");
342 if cache.len() >= 64 {
343 cache.clear();
344 }
345 cache.push((key, m));
346 m
347 }
348
349 pub fn integral(&self, g: impl Fn(f64) -> f64, a: f64, b: f64) -> Option<f64> {
352 quadrature::moment(
353 |u| {
354 let w = self.at(u);
355 if w == 0.0 { 0.0 } else { g(u) * w }
356 },
357 a,
358 b,
359 self.tolerance,
360 )
361 }
362}
363
364impl PartialEq for Likelihood {
365 fn eq(&self, other: &Likelihood) -> bool {
366 self.key() == other.key()
367 }
368}
369
370fn nan_to_impossible(ln: f64) -> f64 {
371 if ln.is_nan() { f64::NEG_INFINITY } else { ln }
372}
373
374fn relative(ln: f64, shift: f64) -> f64 {
375 if ln == f64::NEG_INFINITY {
376 0.0
377 } else {
378 crate::math::exp(ln - shift)
379 }
380}
381
382fn local_peaks(grid: &[(f64, f64)]) -> Vec<(f64, f64)> {
385 let n = grid.len();
386 (0..n)
387 .filter(|&i| {
388 let v = grid[i].1;
389 v > f64::NEG_INFINITY && (i == 0 || grid[i - 1].1 <= v) && (i + 1 == n || grid[i + 1].1 < v)
390 })
391 .map(|i| grid[i])
392 .collect()
393}
394
395fn refine(
398 ln_at: &impl Fn(f64) -> f64,
399 u: f64,
400 h: f64,
401 budget: &mut Budget,
402 factors: usize,
403) -> OpResult<(f64, f64, f64)> {
404 let (mut best, mut ln, mut h) = (u, nan_to_impossible(ln_at(u)), h);
405 for _ in 0..6 {
406 budget.work((65 * factors) as u64)?;
407 for i in 0..=64 {
408 let v = (best - h + 2.0 * h * i as f64 / 64.0).clamp(1e-300, 1.0 - 1e-16);
409 let l = nan_to_impossible(ln_at(v));
410 if l > ln {
411 ln = l;
412 best = v;
413 }
414 }
415 h /= 16.0;
416 }
417 let mut width = h;
420 while width < 0.5 {
421 let lower = nan_to_impossible(ln_at((best - width).max(0.0)));
422 let upper = nan_to_impossible(ln_at((best + width).min(1.0)));
423 if lower < ln - 10.0 || upper < ln - 10.0 {
424 break;
425 }
426 width *= 2.0;
427 }
428 Ok((best, ln, (width / 8.0).max(1e-15)))
429}