changeset 1587:9a3a18c494f9

WIP new inferencer. no vars yet.
author drewp@bigasterisk.com
date Sun, 29 Aug 2021 23:59:09 -0700
parents b459b5c3c33a
children 0757fafbfdab
files service/mqtt_to_rdf/inference.py service/mqtt_to_rdf/inference_test.py
diffstat 2 files changed, 175 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/inference.py	Sun Aug 29 23:59:09 2021 -0700
@@ -0,0 +1,83 @@
+"""
+copied from reasoning 2021-08-29. probably same api. should
+be able to lib/ this out
+"""
+
+import logging
+from typing import Dict, Tuple
+from dataclasses import dataclass
+from prometheus_client import Summary
+from rdflib import Graph, Namespace
+from rdflib.graph import ConjunctiveGraph
+from rdflib.term import Node, Variable
+
+log = logging.getLogger('infer')
+
+Triple = Tuple[Node, Node, Node]
+Rule = Tuple[Graph, Node, Graph]
+
+READ_RULES_CALLS = Summary('read_rules_calls', 'calls')
+
+ROOM = Namespace("http://projects.bigasterisk.com/room/")
+LOG = Namespace('http://www.w3.org/2000/10/swap/log#')
+MATH = Namespace('http://www.w3.org/2000/10/swap/math#')
+
+
+@dataclass
+class _RuleMatch:
+    """one way that a rule can match the working set"""
+    vars: Dict[Variable, Node]
+
+
+class Inference:
+
+    def __init__(self) -> None:
+        self.rules = ConjunctiveGraph()
+
+    def setRules(self, g: ConjunctiveGraph):
+        self.rules = g
+
+    def infer(self, graph: Graph):
+        """
+        returns new graph of inferred statements.
+        """
+        log.info(f'Begin inference of graph len={len(graph)} with rules len={len(self.rules)}:')
+
+        workingSet = ConjunctiveGraph()
+        if isinstance(graph, ConjunctiveGraph):
+            workingSet.addN(graph.quads())
+        else:
+            for triple in graph:
+                workingSet.add(triple)
+
+        implied = ConjunctiveGraph()
+
+        bailout_iterations = 100
+        delta = 1
+        while delta > 0 and bailout_iterations > 0:
+            bailout_iterations -= 1
+            delta = -len(implied)
+            self._iterateRules(workingSet, implied)
+            delta += len(implied)
+            log.info(f'  this inference round added {delta} more implied stmts')
+        log.info(f'{len(implied)} stmts implied:')
+        for st in implied:
+            log.info(f'  {st}')
+        return implied
+
+    def _iterateRules(self, workingSet, implied):
+        for r in self.rules:
+            if r[1] == LOG['implies']:
+                self._applyRule(r[0], r[2], workingSet, implied)
+            else:
+                log.info(f'  {r} not a rule?')
+
+    def _applyRule(self, lhs, rhs, workingSet, implied):
+        containsSetup = self._containsSetup(lhs, workingSet)
+        if containsSetup:
+            for st in rhs:
+                workingSet.add(st)
+                implied.add(st)
+
+    def _containsSetup(self, lhs, workingSet):
+        return all(st in workingSet for st in lhs)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/service/mqtt_to_rdf/inference_test.py	Sun Aug 29 23:59:09 2021 -0700
@@ -0,0 +1,92 @@
+import unittest
+
+from rdflib import ConjunctiveGraph, Namespace, Graph
+from rdflib.parser import StringInputSource
+
+from inference import Inference
+
+ROOM = Namespace('http://projects.bigasterisk.com/room/')
+
+
+def N3(txt: str):
+    g = ConjunctiveGraph()
+    prefix = "@prefix : <http://example.com/> .\n"
+    g.parse(StringInputSource((prefix + txt).encode('utf8')), format='n3')
+    return g
+
+
+def makeInferenceWithRules(n3):
+    inf = Inference()
+    inf.setRules(N3(n3))
+    return inf
+
+
+class WithGraphEqual(unittest.TestCase):
+
+    def assertGraphEqual(self, g: Graph, expected: Graph):
+        stmts1 = list(g.triples((None, None, None)))
+        stmts2 = list(expected.triples((None, None, None)))
+        self.assertCountEqual(stmts1, stmts2)
+
+
+class TestInferenceWithoutVars(WithGraphEqual):
+
+    def testEmitNothing(self):
+        inf = makeInferenceWithRules("")
+        implied = inf.infer(N3(":a :b :c ."))
+        self.assertEqual(len(implied), 0)
+
+    def testSimple(self):
+        inf = makeInferenceWithRules("{ :a :b :c . } => { :a :b :new . } .")
+        implied = inf.infer(N3(":a :b :c ."))
+        self.assertGraphEqual(implied, N3(":a :b :new ."))
+
+    def testTwoRounds(self):
+        inf = makeInferenceWithRules("""
+        { :a :b :c . } => { :a :b :new1 . } .
+        { :a :b :new1 . } => { :a :b :new2 . } .
+        """)
+
+        implied = inf.infer(N3(":a :b :c ."))
+        self.assertGraphEqual(implied, N3(":a :b :new1, :new2 ."))
+
+
+class TestInferenceWithVars(WithGraphEqual):
+
+    def testVarInSubject(self):
+        inf = makeInferenceWithRules("{ ?x :b :c . } => { :new :stmt ?x } .")
+        implied = inf.infer(N3(":a :b :c ."))
+        self.assertGraphEqual(implied, N3(":new :stmt :a ."))
+
+    def testVarInObject(self):
+        inf = makeInferenceWithRules("{ :a :b ?x . } => { :new :stmt ?x } .")
+        implied = inf.infer(N3(":a :b :c ."))
+        self.assertGraphEqual(implied, N3(":new :stmt :c ."))
+
+    def testVarMatchesTwice(self):
+        inf = makeInferenceWithRules("{ :a :b ?x . } => { :new :stmt ?x } .")
+        implied = inf.infer(N3(":a :b :c, :d ."))
+        self.assertGraphEqual(implied, N3(":new :stmt :c, :d ."))
+
+    def testTwoRulesWithVars(self):
+        inf = makeInferenceWithRules("""
+        { :a :b ?x . } => { :new :stmt ?x } .
+        { ?y :stmt ?z . } => { :new :stmt2 ?z }
+        """)
+        implied = inf.infer(N3(":a :b :c ."))
+        self.assertGraphEqual(implied, N3(":new :stmt :c; :stmt2 :new ."))
+
+
+class TestInferenceWithMathFunctions(WithGraphEqual):
+
+    def test1(self):
+        inf = makeInferenceWithRules("{ :a :b ?x . ?x math:greaterThan 5 } => { :new :stmt ?x } .")
+        self.assertGraphEqual(inf.infer(N3(":a :b 3 .")), N3(""))
+        self.assertGraphEqual(inf.infer(N3(":a :b 5 .")), N3(""))
+        self.assertGraphEqual(inf.infer(N3(":a :b 6 .")), N3(":new :stmt :a ."))
+
+class TestInferenceWithCustomFunctions(WithGraphEqual):
+
+    def testAsFarenheit(self):
+        inf = makeInferenceWithRules("{ :a :b ?x . ?x :asFarenheit ?f } => { :new :stmt ?f } .")
+        self.assertGraphEqual(inf.infer(N3(":a :b 0 .")), N3(":new :stmt -32 ."))