1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#![cfg(feature = "host-io")]
//! Engine-level semantics for the Stage-B existential join.
//!
//! The pyxlog neural template path grounds a neural predicate over a real join
//! domain and lets the deterministic provenance machinery OR-aggregate the per-
//! domain contributions at each head binding. This test locks that foundation at
//! the `xlog-prob` exact layer — with the neural predicate stood in by per-event
//! probabilistic facts — so the engine semantics the Python layer relies on are
//! guarded against a future refactor, independent of the neural/template code.
use xlog_prob::exact::{ExactDdnnfProgram, ExactResult};
use xlog_prob::provenance::Value;
fn prob_of(result: &ExactResult, predicate: &str, args: &[Value]) -> f64 {
result
.query_probs
.iter()
.find(|q| q.atom.predicate == predicate && q.atom.args == args)
.unwrap_or_else(|| panic!("missing query result for {} {:?}", predicate, args))
.prob
}
/// A neural-predicate stand-in (`saliency_s`, one prob fact per event) joined to
/// an ordinary relation (`pre_before_post`) on the existential variable `Event`,
/// gated by a guard, must yield the guard-gated noisy-OR over the events joined
/// to each head binding `Edge`:
/// P(plastic(e)) = P(guard) * (1 - prod_{v : pre_before_post(v,e)} (1 - p_v)).
#[test]
fn existential_join_or_aggregation_semantics() {
let source = r#"
0.7::saliency_s(0).
0.6::saliency_s(1).
0.8::saliency_s(2).
pre_before_post(0, 0).
pre_before_post(1, 0).
pre_before_post(2, 1).
0.8807970779778823::guard().
plastic(Edge) :- saliency_s(Event), pre_before_post(Event, Edge), guard().
query(plastic(0)).
query(plastic(1)).
"#;
let compiled = ExactDdnnfProgram::compile_source(source).unwrap();
let result = compiled.evaluate().unwrap();
let g = 0.8807970779778823_f64; // sigmoid(2.0)
let expected0 = g * (1.0 - (1.0 - 0.7) * (1.0 - 0.6)); // events {0,1} -> edge 0
let expected1 = g * 0.8; // event {2} -> edge 1
let got0 = prob_of(&result, "plastic", &[Value::I64(0)]);
let got1 = prob_of(&result, "plastic", &[Value::I64(1)]);
assert!(
(got0 - expected0).abs() < 1e-9,
"plastic(0)={} expected {}",
got0,
expected0
);
assert!(
(got1 - expected1).abs() < 1e-9,
"plastic(1)={} expected {}",
got1,
expected1
);
}