view service/reasoning/inference.py @ 1556:d36d3b9ae516

python 3! and some types and cleanups Ignore-this: 3453a547ee745fa83668f36956c835cd darcs-hash:d9bd8e8a584681078c5de1b6e06c894995400f1f
author drewp <drewp@bigasterisk.com>
date Fri, 14 Feb 2020 00:07:23 -0800
parents e3ae71fbd3bd
children c8562ace4917
line wrap: on
line source

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

import os, contextlib
try:
    from rdflib.Graph import Graph
except ImportError:
    from rdflib import Graph

from rdflib import Namespace
from rdflib.parser import StringInputSource

from FuXi.Rete.Util import generateTokenSet
from FuXi.Rete import ReteNetwork
from FuXi.Rete.RuleStore import N3RuleStore

from greplin import scales
STATS = scales.collection('/web',
                          scales.PmfStat('readRules'))

from escapeoutputstatements import escapeOutputStatements
ROOM = Namespace("http://projects.bigasterisk.com/room/")

def _loadAndEscape(ruleStore, n3, outputPatterns):
    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 STATS.readRules.time():
        mtime = os.path.getmtime(rulesPath)
        key = (rulesPath, mtime)
        if _rulesCache[:2] == key:
            _, _, rulesN3, ruleStore = _rulesCache
        else:
            rulesN3 = open(rulesPath).read() # for web display

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

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

def infer(graph, rules):
    """
    returns new graph of inferred statements. Plain rete api seems to
    alter rules.formulae and rules.rules, but this function does not
    alter the incoming rules object, so you can cache it.
    """
    # based on fuxi/tools/rdfpipe.py
    target = Graph()
    tokenSet = generateTokenSet(graph)
    with _dontChangeRulesStore(rules):
        network = ReteNetwork(rules, inferredTarget=target)
        network.feedFactsToAdd(tokenSet)

    return target

@contextlib.contextmanager
def _dontChangeRulesStore(rules):
    if not hasattr(rules, '_stashOriginalRules'):
        rules._stashOriginalRules = rules.rules[:]
    yield
    for k in list(rules.formulae.keys()):
        if not k.startswith('_:Formula'):
            del rules.formulae[k]
    rules.rules = rules._stashOriginalRules[:]

import time, logging
log = logging.getLogger()
def logTime(func):
    def inner(*args, **kw):
        t1 = time.time()
        try:
            ret = func(*args, **kw)
        finally:
            log.info("Call to %s took %.1f ms" % (
                func.__name__, 1000 * (time.time() - t1)))
        return ret
    return inner