qualia_core_db/modalities/
probabilistic.rs1use crate::NQuin;
2
3pub fn evaluate_threshold(weight: f32, threshold: f32) -> bool {
4 weight >= threshold
5}
6
7pub const MAX_BAYESIAN_NODES: usize = 32;
8
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct BayesianNode {
11 pub id: u64, pub parent_ids: [u64; 4], pub num_parents: usize,
14 pub probabilities: [f32; 16], pub evidence: Option<bool>, }
17
18impl Default for BayesianNode {
19 fn default() -> Self {
20 Self {
21 id: 0,
22 parent_ids: [0; 4],
23 num_parents: 0,
24 probabilities: [0.0; 16],
25 evidence: None,
26 }
27 }
28}
29
30pub struct BayesianNetwork {
31 pub nodes: [BayesianNode; MAX_BAYESIAN_NODES],
32 pub num_nodes: usize,
33}
34
35impl BayesianNetwork {
36 pub fn new() -> Self {
37 Self {
38 nodes: [BayesianNode::default(); MAX_BAYESIAN_NODES],
39 num_nodes: 0,
40 }
41 }
42
43 pub fn add_node(&mut self, node: BayesianNode) -> Result<(), &'static str> {
44 if self.num_nodes >= MAX_BAYESIAN_NODES {
45 return Err("Max nodes exceeded");
46 }
47 self.nodes[self.num_nodes] = node;
48 self.num_nodes += 1;
49 Ok(())
50 }
51
52 pub fn extract_weight(quin: &NQuin) -> f32 {
55 crate::frame_layout::truth_degree(quin.metadata)
56 }
57
58 pub fn update_beliefs(&self, target_id: u64) -> Option<f32> {
62 let mut target_idx = None;
63 for i in 0..self.num_nodes {
64 if self.nodes[i].id == target_id {
65 target_idx = Some(i);
66 break;
67 }
68 }
69 let target_idx = target_idx?;
70
71 let mut num_unobserved = 0;
72 let mut unobserved_indices = [0; MAX_BAYESIAN_NODES];
73 for i in 0..self.num_nodes {
74 if self.nodes[i].evidence.is_none() && i != target_idx {
75 unobserved_indices[num_unobserved] = i;
76 num_unobserved += 1;
77 }
78 }
79
80 if num_unobserved > 16 {
81 return None; }
83
84 let num_assignments = 1 << num_unobserved;
85
86 let mut prob_target_true = 0.0;
87 let mut prob_target_false = 0.0;
88
89 for assignment in 0..num_assignments {
90 for target_state in [true, false] {
91 let mut joint_prob = 1.0;
92
93 for i in 0..self.num_nodes {
94 let node = &self.nodes[i];
95
96 let state = if i == target_idx {
97 target_state
98 } else if let Some(e) = node.evidence {
99 e
100 } else {
101 let mut bit_idx = 0;
102 for j in 0..num_unobserved {
103 if unobserved_indices[j] == i {
104 bit_idx = j;
105 break;
106 }
107 }
108 ((assignment >> bit_idx) & 1) == 1
109 };
110
111 let mut parent_idx = 0;
112 for p in 0..node.num_parents {
113 let pid = node.parent_ids[p];
114 let mut p_state = false;
115 for j in 0..self.num_nodes {
116 if self.nodes[j].id == pid {
117 p_state = if j == target_idx {
118 target_state
119 } else if let Some(e) = self.nodes[j].evidence {
120 e
121 } else {
122 let mut bit_idx = 0;
123 for k in 0..num_unobserved {
124 if unobserved_indices[k] == j {
125 bit_idx = k;
126 break;
127 }
128 }
129 ((assignment >> bit_idx) & 1) == 1
130 };
131 break;
132 }
133 }
134 if p_state {
135 parent_idx |= 1 << p;
136 }
137 }
138
139 let p_node = if state {
140 node.probabilities[parent_idx]
141 } else {
142 1.0 - node.probabilities[parent_idx]
143 };
144
145 joint_prob *= p_node;
146 }
147
148 if target_state {
149 prob_target_true += joint_prob;
150 } else {
151 prob_target_false += joint_prob;
152 }
153 }
154 }
155
156 let total = prob_target_true + prob_target_false;
157 if total > 0.0 {
158 Some(prob_target_true / total)
159 } else {
160 None
161 }
162 }
163
164 pub fn markov_blanket(&self, node_id: u64, out: &mut [u64]) -> usize {
169 let mut n = 0usize;
170 let add = |id: u64, out: &mut [u64], n: &mut usize| {
171 if id != node_id && id != 0 && !out[..*n].contains(&id) && *n < out.len() {
172 out[*n] = id;
173 *n += 1;
174 }
175 };
176 for i in 0..self.num_nodes {
177 if self.nodes[i].id == node_id {
178 for p in 0..self.nodes[i].num_parents {
179 add(self.nodes[i].parent_ids[p], out, &mut n);
180 }
181 }
182 }
183 for i in 0..self.num_nodes {
184 let child = self.nodes[i];
185 if child.parent_ids[..child.num_parents].contains(&node_id) {
186 add(child.id, out, &mut n); for p in 0..child.num_parents {
188 add(child.parent_ids[p], out, &mut n); }
190 }
191 }
192 n
193 }
194
195 fn node_cpt(&self, node_idx: usize, state: bool, assign: &[bool]) -> f32 {
198 let node = &self.nodes[node_idx];
199 let mut parent_idx = 0usize;
200 for p in 0..node.num_parents {
201 let pid = node.parent_ids[p];
202 for j in 0..self.num_nodes {
203 if self.nodes[j].id == pid {
204 if assign[j] {
205 parent_idx |= 1 << p;
206 }
207 break;
208 }
209 }
210 }
211 let pt = node.probabilities[parent_idx];
212 if state {
213 pt
214 } else {
215 1.0 - pt
216 }
217 }
218
219 fn gibbs_conditional(&self, node_idx: usize, state: bool, assign: &[bool]) -> f32 {
222 let mut a = [false; MAX_BAYESIAN_NODES];
223 a[..self.num_nodes].copy_from_slice(&assign[..self.num_nodes]);
224 a[node_idx] = state;
225 let mut prob = self.node_cpt(node_idx, state, &a);
226 let this_id = self.nodes[node_idx].id;
227 for c in 0..self.num_nodes {
228 let child = self.nodes[c];
229 if child.parent_ids[..child.num_parents].contains(&this_id) {
230 prob *= self.node_cpt(c, a[c], &a);
231 }
232 }
233 prob
234 }
235
236 pub fn gibbs_estimate(&self, target_id: u64, samples: u32, seed: u64) -> Option<f32> {
241 let mut target_idx = None;
242 for i in 0..self.num_nodes {
243 if self.nodes[i].id == target_id {
244 target_idx = Some(i);
245 }
246 }
247 let target_idx = target_idx?;
248 let mut rng = seed | 1;
249 let mut assign = [false; MAX_BAYESIAN_NODES];
250 for i in 0..self.num_nodes {
251 assign[i] = match self.nodes[i].evidence {
252 Some(e) => e,
253 None => next_unit(&mut rng) < 0.5,
254 };
255 }
256 let mut count_true = 0u32;
257 for _ in 0..samples {
258 for i in 0..self.num_nodes {
259 if self.nodes[i].evidence.is_some() {
260 continue;
261 }
262 let pt = self.gibbs_conditional(i, true, &assign);
263 let pf = self.gibbs_conditional(i, false, &assign);
264 let denom = pt + pf;
265 let prob = if denom > 0.0 { pt / denom } else { 0.5 };
266 assign[i] = next_unit(&mut rng) < prob;
267 }
268 if assign[target_idx] {
269 count_true += 1;
270 }
271 }
272 if samples == 0 {
273 None
274 } else {
275 Some(count_true as f32 / samples as f32)
276 }
277 }
278}
279
280fn next_unit(state: &mut u64) -> f32 {
282 let mut x = *state;
283 x ^= x << 13;
284 x ^= x >> 7;
285 x ^= x << 17;
286 *state = x;
287 ((x >> 40) as f32) / ((1u32 << 24) as f32)
288}
289
290#[inline]
295pub fn pc_adjacent(correlation: f32, threshold: f32) -> bool {
296 correlation.abs() >= threshold
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[test]
304 fn test_bayesian_network() {
305 let mut network = BayesianNetwork::new();
306
307 let mut node0 = BayesianNode::default();
309 node0.id = 100;
310 node0.probabilities[0] = 0.2;
311 network.add_node(node0).unwrap();
312
313 let mut node1 = BayesianNode::default();
317 node1.id = 200;
318 node1.parent_ids[0] = 100;
319 node1.num_parents = 1;
320 node1.probabilities[1] = 0.01; node1.probabilities[0] = 0.40; network.add_node(node1).unwrap();
323
324 let mut node2 = BayesianNode::default();
331 node2.id = 300;
332 node2.parent_ids[0] = 100;
333 node2.parent_ids[1] = 200;
334 node2.num_parents = 2;
335 node2.probabilities[0] = 0.0;
336 node2.probabilities[1] = 0.8;
337 node2.probabilities[2] = 0.9;
338 node2.probabilities[3] = 0.99;
339
340 node2.evidence = Some(true);
342 network.add_node(node2).unwrap();
343
344 let p_rain = network.update_beliefs(100).unwrap();
346
347 assert!((p_rain - 0.3577).abs() < 0.001);
349 }
350
351 fn sprinkler(with_wet_evidence: bool) -> BayesianNetwork {
352 let mut net = BayesianNetwork::new();
353 let mut n0 = BayesianNode::default();
354 n0.id = 100;
355 n0.probabilities[0] = 0.2;
356 net.add_node(n0).unwrap();
357 let mut n1 = BayesianNode::default();
358 n1.id = 200;
359 n1.parent_ids[0] = 100;
360 n1.num_parents = 1;
361 n1.probabilities[1] = 0.01;
362 n1.probabilities[0] = 0.40;
363 net.add_node(n1).unwrap();
364 let mut n2 = BayesianNode::default();
365 n2.id = 300;
366 n2.parent_ids[0] = 100;
367 n2.parent_ids[1] = 200;
368 n2.num_parents = 2;
369 n2.probabilities = [
370 0.0, 0.8, 0.9, 0.99, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
371 ];
372 if with_wet_evidence {
373 n2.evidence = Some(true);
374 }
375 net.add_node(n2).unwrap();
376 net
377 }
378
379 #[test]
380 fn markov_blanket_of_a_node() {
381 let net = sprinkler(false);
382 let mut out = [0u64; 8];
384 let n = net.markov_blanket(100, &mut out);
385 assert_eq!(n, 2);
386 assert!(out[..n].contains(&200) && out[..n].contains(&300));
387 }
388
389 #[test]
390 fn gibbs_approximates_the_exact_posterior() {
391 let net = sprinkler(true);
392 let exact = net.update_beliefs(100).unwrap(); let approx = net.gibbs_estimate(100, 40_000, 0x9E3779B97F4A7C15).unwrap();
394 assert!(
395 (approx - exact).abs() < 0.08,
396 "Gibbs {approx} should approximate exact {exact}"
397 );
398 }
399
400 #[test]
401 fn pc_skeleton_uses_absolute_correlation() {
402 assert!(pc_adjacent(0.6, 0.3));
403 assert!(!pc_adjacent(0.1, 0.3));
404 assert!(pc_adjacent(-0.5, 0.3), "structure depends on |correlation|");
405 }
406}