qualia_core_db/modalities/abductive/
probabilistic.rs1#[derive(Debug, Clone, Copy)]
10pub struct Hypothesis {
11 pub id: u64,
12 pub prior: f32,
13 pub likelihood: f32,
14}
15
16pub fn bayesian_posteriors(hyps: &[Hypothesis], out: &mut [f32]) -> f32 {
21 if out.len() < hyps.len() {
22 return 0.0;
23 }
24 let mut evidence = 0.0f32;
25 for h in hyps {
26 evidence += h.prior * h.likelihood;
27 }
28 if evidence.abs() < 1e-12 {
29 for o in out.iter_mut().take(hyps.len()) {
30 *o = 0.0;
31 }
32 return 0.0;
33 }
34 for (i, h) in hyps.iter().enumerate() {
35 out[i] = (h.prior * h.likelihood) / evidence;
36 }
37 evidence
38}
39
40pub fn best_hypothesis(hyps: &[Hypothesis]) -> Option<u64> {
44 let mut best: Option<(u64, f32)> = None;
45 for h in hyps {
46 let score = h.prior * h.likelihood;
47 if score > 0.0 && best.map(|(_, s)| score > s).unwrap_or(true) {
48 best = Some((h.id, score));
49 }
50 }
51 best.map(|(id, _)| id)
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 fn close(a: f32, b: f32) -> bool {
59 (a - b).abs() < 1e-5
60 }
61
62 #[test]
63 fn posteriors_normalise_and_rank() {
64 let hyps = [
66 Hypothesis {
67 id: 1,
68 prior: 0.2,
69 likelihood: 0.9,
70 }, Hypothesis {
72 id: 2,
73 prior: 0.8,
74 likelihood: 0.1,
75 }, ];
77 let mut out = [0.0f32; 2];
78 let evidence = bayesian_posteriors(&hyps, &mut out);
79 assert!(close(evidence, 0.26));
80 assert!(close(out[0], 0.18 / 0.26));
81 assert!(close(out[1], 0.08 / 0.26));
82 assert!(close(out[0] + out[1], 1.0), "posteriors sum to 1");
83 assert_eq!(best_hypothesis(&hyps), Some(1));
85 }
86
87 #[test]
88 fn no_mass_yields_none_and_zero_evidence() {
89 let hyps = [Hypothesis {
90 id: 1,
91 prior: 0.0,
92 likelihood: 0.9,
93 }];
94 let mut out = [9.0f32; 1];
95 assert_eq!(bayesian_posteriors(&hyps, &mut out), 0.0);
96 assert_eq!(out[0], 0.0, "zeroed when no mass");
97 assert_eq!(best_hypothesis(&hyps), None);
98 assert_eq!(best_hypothesis(&[]), None);
99 }
100}