Mercurial > code > home > repos > homeauto
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 ."))