view service/reasoning/inference.py @ 1685:6b80a6c58907

minor changes to several services
author drewp@bigasterisk.com
date Mon, 27 Sep 2021 23:12:43 -0700
parents c8562ace4917
children
line wrap: on
line source

"""
see ./reasoning for usage
"""

import os
import logging

from prometheus_client import Summary
from rdflib import Graph, Namespace
from rdflib.graph import ConjunctiveGraph
from rdflib.parser import StringInputSource

from escapeoutputstatements import escapeOutputStatements

log = logging.getLogger()

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#')


def _loadAndEscape(ruleStore: ConjunctiveGraph, n3: bytes, outputPatterns):
    ruleStore.parse(StringInputSource(n3), format='n3')
    return
    ruleGraph = Graph(ruleStore)

    # Can't escapeOutputStatements in the ruleStore since it
    # doesn't support removals. Can't copy plainGraph into
    # ruleGraph since something went wrong with traversing the
    # triples inside quoted graphs, and I lose all the bodies
    # of my rules. This serialize/parse version is very slow (400ms),
    # but it only runs when the file changes.
    plainGraph = Graph()
    plainGraph.parse(StringInputSource(n3.encode('utf8')), format='n3')  # for inference
    escapeOutputStatements(plainGraph, outputPatterns=outputPatterns)
    expandedN3 = plainGraph.serialize(format='n3')

    ruleGraph.parse(StringInputSource(expandedN3), format='n3')


_rulesCache = (None, None, None, None)


def readRules(rulesPath, outputPatterns):
    """
    returns (rulesN3, ruleStore)

    This includes escaping certain statements in the output
    (implied) subgraaphs so they're not confused with input
    statements.
    """
    global _rulesCache

    with READ_RULES_CALLS.time():
        mtime = os.path.getmtime(rulesPath)
        key = (rulesPath, mtime)
        if _rulesCache[:2] == key:
            _, _, rulesN3, ruleStore = _rulesCache
        else:
            rulesN3 = open(rulesPath, 'rb').read()  # for web display

            ruleStore = ConjunctiveGraph()
            _loadAndEscape(ruleStore, rulesN3, outputPatterns)
            log.debug('%s rules' % len(ruleStore))

            _rulesCache = key + (rulesN3, ruleStore)
        return rulesN3, ruleStore


def infer(graph: ConjunctiveGraph, rules: ConjunctiveGraph):
    """
    returns new graph of inferred statements.
    """
    log.info(f'Begin inference of graph len={len(graph)} with rules len={len(rules)}:')

    workingSet = ConjunctiveGraph()
    workingSet.addN(graph.quads())

    implied = ConjunctiveGraph()

    bailout_iterations = 100
    delta = 1
    while delta > 0 and bailout_iterations > 0:
        bailout_iterations -= 1
        delta = -len(implied)

        for r in rules:
            if r[1] == LOG['implies']:
                containsSetup = all(st in workingSet for st in r[0])
                if containsSetup:
                    log.info(f'  Rule {r[0]} -> present={containsSetup}')
                    for st in r[0]:
                        log.info(f'     {st[0].n3()} {st[1].n3()} {st[2].n3()}')

                    log.info(f'  ...implies {len(r[2])} statements')
                if containsSetup:
                    for st in r[2]:
                        workingSet.add(st)
                        implied.add(st)
            else:
                log.info(f'  {r}')
        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