Skip to main content

qualia_core_db/modalities/
fuzzy_rdf_schema.rs

1//! Fuzzy RDF schema — graded entailment (Ma, Li & Ma ch 3.3). RDFS reasoning with
2//! a degree: `subClassOf`/`type` hold to a degree in `[0,1]`, and degrees compose
3//! along the class hierarchy by a t-norm.
4//!
5//! Mission fit: "this guardianship relation is 0.6 a `MedicalProxy`" is a *degree of
6//! role-holding*; propagating it through the hierarchy with a t-norm reasons about
7//! **partial agency** without faking a crisp claim. Reuses the existing fuzzy
8//! operators ([`crate::modalities::fuzzy`]); kernel-class `Reduction`.
9
10use crate::modalities::fuzzy::{t_conorm_godel, t_norm_product};
11
12/// Graded transitive closure of `subClassOf`. Input edges `(sub, super, degree)`
13/// over `n` classes. The closure degree that `a ⊑ c` is the **best (t-conorm/max)
14/// over all paths** of the **t-norm (product) along each path**. Diagonal is `1.0`
15/// (every class is a subclass of itself with full degree). Returns the `n×n`
16/// row-major degree matrix.
17pub fn subclass_closure(n: usize, edges: &[(usize, usize, f64)]) -> Vec<f64> {
18    let mut m = vec![0.0f32; n * n];
19    for i in 0..n {
20        m[i * n + i] = 1.0;
21    }
22    for &(a, b, d) in edges {
23        if a < n && b < n {
24            let dd = d.clamp(0.0, 1.0) as f32;
25            // Keep the strongest direct assertion.
26            if dd > m[a * n + b] {
27                m[a * n + b] = dd;
28            }
29        }
30    }
31    // Fuzzy transitive closure (Floyd-Warshall with product t-norm + max t-conorm).
32    for k in 0..n {
33        for i in 0..n {
34            let ik = m[i * n + k];
35            if ik == 0.0 {
36                continue;
37            }
38            for j in 0..n {
39                let via = t_norm_product(ik, m[k * n + j]);
40                let cur = m[i * n + j];
41                m[i * n + j] = t_conorm_godel(cur, via);
42            }
43        }
44    }
45    m.iter().map(|&v| v as f64).collect()
46}
47
48/// Degree to which an instance is of class `c`, given it is of class `a` with degree
49/// `type_degree` and `a ⊑ c` with `subclass_degree`: `t-norm(type, subclass)`.
50pub fn type_entailment(type_degree: f64, subclass_degree: f64) -> f64 {
51    t_norm_product(
52        type_degree.clamp(0.0, 1.0) as f32,
53        subclass_degree.clamp(0.0, 1.0) as f32,
54    ) as f64
55}
56
57/// Convenience: given the closure matrix and an instance's direct `type` degrees per
58/// class (`type_of[c]`), the entailed degree of membership in class `c` =
59/// `max_a t-norm(type_of[a], closure[a][c])`. Returns the entailed type degrees.
60pub fn entailed_types(n: usize, closure: &[f64], type_of: &[f64]) -> Vec<f64> {
61    let mut out = vec![0.0; n];
62    for c in 0..n {
63        let mut best = 0.0f64;
64        for a in 0..n {
65            if type_of[a] > 0.0 {
66                let d = type_entailment(type_of[a], closure[a * n + c]);
67                if d > best {
68                    best = d;
69                }
70            }
71        }
72        out[c] = best;
73    }
74    out
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    const EPS: f64 = 1e-6;
81
82    #[test]
83    fn transitive_degree_composes_by_product() {
84        // A ⊑ B (0.8), B ⊑ C (0.7) → A ⊑ C = 0.8·0.7 = 0.56.
85        let m = subclass_closure(3, &[(0, 1, 0.8), (1, 2, 0.7)]);
86        assert!((m[0 * 3 + 1] - 0.8).abs() < EPS);
87        assert!((m[1 * 3 + 2] - 0.7).abs() < EPS);
88        assert!((m[0 * 3 + 2] - 0.56).abs() < EPS, "A⊑C {}", m[0 * 3 + 2]);
89        // Reflexive.
90        assert!((m[0 * 3 + 0] - 1.0).abs() < EPS);
91    }
92
93    #[test]
94    fn best_path_wins() {
95        // Two paths A→C: A→B→C (0.9·0.5=0.45) and A→D→C (0.6·0.9=0.54). Max = 0.54.
96        let edges = [(0, 1, 0.9), (1, 2, 0.5), (0, 3, 0.6), (3, 2, 0.9)];
97        let m = subclass_closure(4, &edges);
98        assert!(
99            (m[0 * 4 + 2] - 0.54).abs() < EPS,
100            "best path {}",
101            m[0 * 4 + 2]
102        );
103    }
104
105    #[test]
106    fn type_entailment_propagates() {
107        // type(x, A)=0.6, A ⊑ MedicalProxy(=class 1) with degree 0.8 → 0.48.
108        let m = subclass_closure(2, &[(0, 1, 0.8)]);
109        let types = entailed_types(2, &m, &[0.6, 0.0]);
110        assert!((types[1] - 0.48).abs() < EPS, "entailed {}", types[1]);
111        // x is still fully of its own class A.
112        assert!((types[0] - 0.6).abs() < EPS);
113    }
114
115    #[test]
116    fn type_entailment_pairwise() {
117        assert!((type_entailment(0.5, 0.4) - 0.2).abs() < EPS);
118    }
119}