def PrepareHornClauseForRETE(horn_clause): if isinstance(horn_clause, Rule): horn_clause = horn_clause.formula def extractVariables(term, existential=True): if isinstance(term, existential and BNode or Variable): yield term elif isinstance(term, Uniterm): for t in term.toRDFTuple(): if isinstance(t, existential and BNode or Variable): yield t from FuXi.Rete.SidewaysInformationPassing import iterCondition, GetArgs #first we identify body variables bodyVars = set( reduce(lambda x, y: x + y, [ list(extractVariables(i, existential=False)) for i in iterCondition(horn_clause.body) ])) #then we identify head variables headVars = set( reduce(lambda x, y: x + y, [ list(extractVariables(i, existential=False)) for i in iterCondition(horn_clause.head) ])) #then we identify those variables that should (or should not) be converted to skolem terms updateDict = dict([(var, BNode()) for var in headVars if var not in bodyVars]) if set(updateDict.keys()).intersection(GetArgs(horn_clause.head)): #There are skolem terms in the head newHead = copy.deepcopy(horn_clause.head) for uniTerm in iterCondition(newHead): newArg = [updateDict.get(i, i) for i in uniTerm.arg] uniTerm.arg = newArg horn_clause.head = newHead skolemsInBody = [ list( itertools.ifilter(lambda term: isinstance(term, BNode), GetArgs(lit))) for lit in iterCondition(horn_clause.body) ] skolemsInBody = reduce(lambda x, y: x + y, skolemsInBody, []) if skolemsInBody: newBody = copy.deepcopy(horn_clause.body) _e = Exists(formula=newBody, declare=set(skolemsInBody)) horn_clause.body = _e PrepareHeadExistential(horn_clause)
def collapseMINUS(left, right): negVars = set() for pred in iterCondition(right): negVars.update( [term for term in GetArgs(pred) if isinstance(term, Variable)]) innerCopyPatternNeeded = not negVars.difference(positiveVars) #A copy pattern is needed if the negative literals don't introduce new vars if innerCopyPatternNeeded: innerCopyPatterns, innerVars, innerVarExprs = createCopyPattern( [right]) #We use an arbitrary new variable as for the outer FILTER(!BOUND(..)) outerFilterVariable = list(innerVars.values())[0] optionalPatterns = [right] + innerCopyPatterns negatedBGP = optional( *[formula.toRDFTuple() for formula in optionalPatterns]) negatedBGP.filter( *[k == v for k, v in list(innerVarExprs.items())]) positiveVars.update( [Variable(k.value[0:]) for k in list(innerVarExprs.keys())]) positiveVars.update(list(innerVarExprs.values())) else: #We use an arbitrary, 'independent' variable for the outer FILTER(!BOUND(..)) outerFilterVariable = negVars.difference(positiveVars).pop() optionalPatterns = [right] negatedBGP = optional( *[formula.toRDFTuple() for formula in optionalPatterns]) positiveVars.update(negVars) left = left.where(*[negatedBGP]) left = left.filter(~op.bound(outerFilterVariable)) return left
def literalIsGround(literal): """ Whether or not the given literal has any variables for terms """ return not [ term for term in GetArgs(literal, secondOrder=True) if isinstance(term, Variable) ]
def isSafe(self): """ A RIF-Core rule, r is safe if and only if - r is a rule implication, φ :- ψ, and all the variables that occur in φ are safe in ψ, and all the variables that occur in ψ are bound in ψ; - or r is a universal rule, Forall v1,...,vn (r'), n ≥ 1, and r' is safe. >>> clause1 = Clause(And([Uniterm(RDFS.subClassOf,[Variable('C'),Variable('SC')]), ... Uniterm(RDF.type,[Variable('M'),Variable('C')])]), ... Uniterm(RDF.type,[Variable('M'),Variable('SC')])) >>> r1 = Rule(clause1,[Variable('M'),Variable('SC'),Variable('C')]) >>> clause2 = Clause(And([Uniterm(RDFS.subClassOf,[Variable('C'),Variable('SC')])]), ... Uniterm(RDF.type,[Variable('M'),Variable('SC')])) >>> r2 = Rule(clause2,[Variable('M'),Variable('SC'),Variable('C')]) >>> r1.isSafe() True >>> r2.isSafe() False >>> skolemTerm = BNode() >>> e = Exists(Uniterm(RDFS.subClassOf,[skolemTerm,Variable('C')]),declare=[skolemTerm]) >>> r1.formula.head = e >>> r1.isSafe() False """ from FuXi.Rete.SidewaysInformationPassing import GetArgs, iterCondition assert isinstance(self.formula.head,(Exists,Atomic)),\ "Safety can only be checked on rules in normal form" for var in itertools.ifilter( lambda term: isinstance(term, (Variable, BNode)), GetArgs(self.formula.head)): if not self.formula.body.isSafeForVariable(var): return False for var in itertools.ifilter( lambda term: isinstance(term, (Variable, BNode)), reduce( lambda l, r: l + r, [GetArgs(lit) for lit in iterCondition(self.formula.body)])): if not self.formula.body.binds(var): return False return True
def __init__(self, clause, declare=None, nsMapping=None): decl = set() self.ruleStr = '' for pred in itertools.chain(iterCondition(clause.head), iterCondition(clause.body)): decl.update( [term for term in GetArgs(pred) if isinstance(term, Variable)]) if isinstance(pred, AdornedUniTerm): self.ruleStr += ''.join(pred.adornment) self.ruleStr += ''.join(pred.toRDFTuple()) super(AdornedRule, self).__init__(clause, decl, nsMapping)
def PrepareHeadExistential(clause): from FuXi.Rete.SidewaysInformationPassing import GetArgs skolemsInHead = [ list(filter(lambda term: isinstance(term, BNode), GetArgs(lit))) for lit in iterCondition(clause.head) ] skolemsInHead = reduce(lambda x, y: x + y, skolemsInHead, []) if skolemsInHead: newHead = copy.deepcopy(clause.head) _e = Exists(formula=newHead, declare=set(skolemsInHead)) clause.head = _e return clause
def getDistinguishedVariables(self, varsOnly=False): if self.op == RDF.type: for idx, term in enumerate(GetArgs(self)): if self.adornment[idx] == 'b': if not varsOnly or isinstance(term, Variable): yield term else: for idx, term in enumerate(self.arg): try: if self.adornment[idx] == 'b': if not varsOnly or isinstance(term, Variable): yield term except IndexError: pass
def SipStrategy(query, sipCollection, factGraph, derivedPreds, bindings={}, processedRules=None, network=None, debug=False, buildProof=False, memoizeMemory=None, proofLevel=1): """ Accordingly, we define a sip-strategy for computing the answers to a query expressed using a set of Datalog rules, and a set of sips, one for each adornment of a rule head, as follows... Each evaluation uses memoization (via Python decorators) but also relies on well-formed rewrites for using semi-naive bottom up method over large SPARQL data. """ memoizeMemory = memoizeMemory and memoizeMemory or {} queryLiteral = BuildUnitermFromTuple(query) processedRules = processedRules and processedRules or set() if bindings: #There are bindings. Apply them to the terms in the query queryLiteral.ground(bindings) if debug: print("%sSolving" % ('\t' * proofLevel), queryLiteral, bindings) # Only consider ground triple pattern isomorphism with matching bindings goalRDFStatement = queryLiteral.toRDFTuple() if queryLiteral in memoizeMemory: if debug: print("%sReturning previously calculated results for " % \ ('\t' * proofLevel), queryLiteral) for answers in memoizeMemory[queryLiteral]: yield answers elif AlphaNode(goalRDFStatement).alphaNetworkHash( True, skolemTerms=list(bindings.values())) in \ [AlphaNode(r.toRDFTuple()).alphaNetworkHash(True, skolemTerms=list(bindings.values())) for r in processedRules if AdornLiteral(goalRDFStatement).adornment == \ r.adornment]: if debug: print("%s Goal already processed..." % \ ('\t' * proofLevel)) else: isGround = literalIsGround(queryLiteral) if buildProof: ns = NodeSet(goalRDFStatement, network=network, identifier=BNode()) else: ns = None # adornedProgram = factGraph.adornedProgram queryPred = GetOp(queryLiteral) if sipCollection is None: rules = [] else: #For every rule head matching the query, we invoke the rule, #thus determining an adornment, and selecting a sip to follow rules = sipCollection.headToRule.get(queryPred, set()) if None in sipCollection.headToRule: #If there are second order rules, we add them #since they are a 'wildcard' rules.update(sipCollection.headToRule[None]) #maintained list of rules that haven't been processed before and #match the query validRules = [] #each subquery contains values for the bound arguments that are passed #through the sip arcs entering the node corresponding to that literal. For #each subquery generated, there is a set of answers. answers = [] # variableMapping = {} #Some TBox queries can be 'joined' together into SPARQL queries against #'base' predicates via an RDF dataset #These atomic concept inclusion axioms can be evaluated together #using a disjunctive operator at the body of a horn clause #where each item is a query of the form uniPredicate(?X): #Or( uniPredicate1(?X1), uniPredicate2(?X), uniPredicate3(?X), ..) #In this way massive, conjunctive joins can be 'mediated' #between the stated facts and the top-down solver @parameterizedPredicate([i for i in derivedPreds]) def IsAtomicInclusionAxiomRHS(rule, dPreds): """ This is an atomic inclusion axiom with a variable (or bound) RHS: uniPred(?ENTITY) """ bodyList = list(iterCondition(rule.formula.body)) body = first(bodyList) return GetOp(body) not in dPreds and \ len(bodyList) == 1 and \ body.op == RDF.type atomicInclusionAxioms = list(filter(IsAtomicInclusionAxiomRHS, rules)) if atomicInclusionAxioms and len(atomicInclusionAxioms) > 1: if debug: print("\tCombining atomic inclusion axioms: ") pprint(atomicInclusionAxioms, sys.stderr) if buildProof: factStep = InferenceStep(ns, source='some RDF graph') ns.steps.append(factStep) axioms = [rule.formula.body for rule in atomicInclusionAxioms] #attempt to exaustively apply any available substitutions #and determine if query if fully ground vars = [ v for v in GetArgs(queryLiteral, secondOrder=True) if isinstance(v, Variable) ] openVars, axioms, _bindings = \ normalizeBindingsAndQuery(vars, bindings, axioms) if openVars: # mappings = {} #See if we need to do any variable mappings from the query literals #to the literals in the applicable rules query, rt = EDBQuery(axioms, factGraph, openVars, _bindings).evaluate( debug, symmAtomicInclusion=True) if buildProof: factStep.groundQuery = subquery for ans in rt: if buildProof: factStep.bindings.update(ans) memoizeMemory.setdefault(queryLiteral, set()).add( (prepMemiozedAns(ans), ns)) yield ans, ns else: #All the relevant derivations have been explored and the result #is a ground query we can directly execute against the facts if buildProof: factStep.bindings.update(bindings) query, rt = EDBQuery(axioms, factGraph, _bindings).evaluate( debug, symmAtomicInclusion=True) if buildProof: factStep.groundQuery = subquery memoizeMemory.setdefault(queryLiteral, set()).add( (prepMemiozedAns(rt), ns)) yield rt, ns rules = filter(lambda i: not IsAtomicInclusionAxiomRHS(i), rules) for rule in rules: #An exception is the special predicate ph; it is treated as a base #predicate and the tuples in it are those supplied for qb by unification. headBindings = getBindingsFromLiteral(goalRDFStatement, rule.formula.head) # comboBindings = dict([(k, v) for k, v in itertools.chain( # bindings.items(), # headBindings.items())]) varMap = rule.formula.head.getVarMapping(queryLiteral) if headBindings and\ [term for term in rule.formula.head.getDistinguishedVariables(True) if varMap.get(term, term) not in headBindings]: continue # subQueryAnswers = [] # dontStop = True # projectedBindings = comboBindings.copy() if debug: print("%sProcessing rule" % \ ('\t' * proofLevel), rule.formula) if debug and sipCollection: print("Sideways Information Passing (sip) graph for %s: " % queryLiteral) print(sipCollection.serialize(format='n3')) for sip in SIPRepresentation(sipCollection): print(sip) try: # Invoke the rule if buildProof: step = InferenceStep(ns, rule.formula) else: step = None for rt, step in\ invokeRule([headBindings], iter(iterCondition(rule.formula.body)), rule.sip, (proofLevel + 1, memoizeMemory, sipCollection, factGraph, derivedPreds, processedRules.union([ AdornLiteral(query)])), step=step, debug=debug): if rt: if isinstance(rt, dict): #We received a mapping and must rewrite it via #correlation between the variables in the rule head #and the variables in the original query (after applying #bindings) varMap = rule.formula.head.getVarMapping( queryLiteral) if varMap: rt = MakeImmutableDict( refactorMapping(varMap, rt)) if buildProof: step.bindings = rt else: if buildProof: step.bindings = headBindings validRules.append(rule) if buildProof: ns.steps.append(step) if isGround: yield True, ns else: memoizeMemory.setdefault(queryLiteral, set()).add( (prepMemiozedAns(rt), ns)) yield rt, ns except RuleFailure: # Clean up failed antecedents if buildProof: if ns in step.antecedents: step.antecedents.remove(ns) if not validRules: #No rules matching, query factGraph for answers successful = False if buildProof: factStep = InferenceStep(ns, source='some RDF graph') ns.steps.append(factStep) if not isGround: subquery, rt = EDBQuery([queryLiteral], factGraph, [ v for v in GetArgs(queryLiteral, secondOrder=True) if isinstance(v, Variable) ], bindings).evaluate(debug) if buildProof: factStep.groundQuery = subquery for ans in rt: successful = True if buildProof: factStep.bindings.update(ans) memoizeMemory.setdefault(queryLiteral, set()).add( (prepMemiozedAns(ans), ns)) yield ans, ns if not successful and queryPred not in derivedPreds: #Open query didn't return any results and the predicate #is ostensibly marked as derived predicate, so we have failed memoizeMemory.setdefault(queryLiteral, set()).add( (False, ns)) yield False, ns else: #All the relevant derivations have been explored and the result #is a ground query we can directly execute against the facts if buildProof: factStep.bindings.update(bindings) subquery, rt = EDBQuery([queryLiteral], factGraph, bindings).evaluate(debug) if buildProof: factStep.groundQuery = subquery memoizeMemory.setdefault(queryLiteral, set()).add( (prepMemiozedAns(rt), ns)) yield rt, ns
def invokeRule(priorAnswers, bodyLiteralIterator, sip, otherargs, priorBooleanGoalSuccess=False, step=None, debug=False, buildProof=False): """ Continue invokation of rule using (given) prior answers and list of remaining body literals (& rule sip). If prior answers is a list, computation is split disjunctively [..] By combining the answers to all these subqueries, we generate answers for the original query involving the rule head Can also takes a PML step and updates it as it navigates the top-down proof tree (passing it on and updating it where necessary) """ assert not buildProof or step is not None proofLevel, memoizeMemory, sipCollection, \ factGraph, derivedPreds, processedRules = otherargs remainingBodyList = [i for i in bodyLiteralIterator] lazyGenerator = lazyGeneratorPeek(priorAnswers, 2) if lazyGenerator.successful: # There are multiple answers in this step, we need to call invokeRule # recursively for each answer, returning the first positive attempt success = False rt = None _step = None ansNo = 0 for priorAns in lazyGenerator: ansNo += 1 try: if buildProof: newStep = InferenceStep(step.parent, step.rule, source=step.source) newStep.antecedents = [ant for ant in step.antecedents] else: newStep = None for rt, _step in\ invokeRule([priorAns], iter([i for i in remainingBodyList]), sip, otherargs, priorBooleanGoalSuccess, newStep, debug=debug, buildProof=buildProof): if rt: yield rt, _step except RuleFailure: pass if not success: # None of prior answers were successful # indicate termination of rule processing raise RuleFailure( "Unable to solve either of %s against remainder of rule: %s" % (ansNo, remainingBodyList)) # yield False, _InferenceStep(step.parent, step.rule, source=step.source) else: lazyGenerator = lazyGeneratorPeek(lazyGenerator) projectedBindings = lazyGenerator.successful and first( lazyGenerator) or {} # First we check if we can combine a large group of subsequent body literals # into a single query # if we have a template map then we use it to further # distinguish which builtins can be solved via # cumulative SPARQl query - else we solve # builtins one at a time def sparqlResolvable(literal): if isinstance(literal, Uniterm): return not literal.naf and GetOp(literal) not in derivedPreds else: return isinstance(literal, N3Builtin) and \ literal.uri in factGraph.templateMap def sparqlResolvableNoTemplates(literal): if isinstance(literal, Uniterm): return not literal.naf and GetOp(literal) not in derivedPreds else: return False conjGroundLiterals = list( itertools.takewhile( hasattr(factGraph, 'templateMap') and sparqlResolvable or \ sparqlResolvableNoTemplates, remainingBodyList)) bodyLiteralIterator = iter(remainingBodyList) if len(conjGroundLiterals) > 1: # If there are literals to combine *and* a mapping from rule # builtins to SPARQL FILTER templates .. basePredicateVars = set( reduce(lambda x, y: x + y, [ list(GetVariables(arg, secondOrder=True)) for arg in conjGroundLiterals ])) if projectedBindings: openVars = basePredicateVars.intersection(projectedBindings) else: # We don't have any given bindings, so we need to treat # the body as an open query openVars = basePredicateVars queryConj = EDBQuery( [copy.deepcopy(lit) for lit in conjGroundLiterals], factGraph, openVars, projectedBindings) query, answers = queryConj.evaluate(debug) if isinstance(answers, bool): combinedAnswers = {} rtCheck = answers else: if projectedBindings: combinedAnswers = (mergeMappings1To2(ans, projectedBindings, makeImmutable=True) for ans in answers) else: combinedAnswers = (MakeImmutableDict(ans) for ans in answers) combinedAnsLazyGenerator = lazyGeneratorPeek(combinedAnswers) rtCheck = combinedAnsLazyGenerator.successful if not rtCheck: raise RuleFailure("No answers for combined SPARQL query: %s" % query) else: # We have solved the previous N body literals with a single # conjunctive query, now we need to make each of the literals # an antecedent to a 'query' step. if buildProof: queryStep = InferenceStep(None, source='some RDF graph') queryStep.groundQuery = subquery queryStep.bindings = {} # combinedAnswers[-1] queryHash = URIRef( "tag:[email protected]:Queries#" + \ makeMD5Digest(subquery)) queryStep.identifier = queryHash for subGoal in conjGroundLiterals: subNs = NodeSet(subGoal.toRDFTuple(), identifier=BNode()) subNs.steps.append(queryStep) step.antecedents.append(subNs) queryStep.parent = subNs for rt, _step in invokeRule( isinstance(answers, bool) and [projectedBindings] or combinedAnsLazyGenerator, iter(remainingBodyList[len(conjGroundLiterals):]), sip, otherargs, isinstance(answers, bool), step, debug=debug, buildProof=buildProof): yield rt, _step else: # Continue processing rule body condition # one literal at a time try: bodyLiteral = next( bodyLiteralIterator ) if py3compat.PY3 else bodyLiteralIterator.next() # if a N3 builtin, execute it using given bindings for boolean answer # builtins are moved to end of rule when evaluating rules via sip if isinstance(bodyLiteral, N3Builtin): lhs = bodyLiteral.argument rhs = bodyLiteral.result lhs = isinstance( lhs, Variable) and projectedBindings[lhs] or lhs rhs = isinstance( rhs, Variable) and projectedBindings[rhs] or rhs assert lhs is not None and rhs is not None if bodyLiteral.func(lhs, rhs): if debug: print("Invoked %s(%s, %s) -> True" % (bodyLiteral.uri, lhs, rhs)) # positive answer means we can continue processing the rule body if buildProof: ns = NodeSet(bodyLiteral.toRDFTuple(), identifier=BNode()) step.antecedents.append(ns) for rt, _step in invokeRule([projectedBindings], bodyLiteralIterator, sip, otherargs, step, priorBooleanGoalSuccess, debug=debug, buildProof=buildProof): yield rt, _step else: if debug: print("Successfully invoked %s(%s, %s) -> False" % (bodyLiteral.uri, lhs, rhs)) raise RuleFailure( "Failed builtin invokation %s(%s, %s)" % (bodyLiteral.uri, lhs, rhs)) else: # For every body literal, subqueries are generated according # to the sip sipArcPred = URIRef(GetOp(bodyLiteral) + \ '_' + '_'.join(GetArgs(bodyLiteral))) assert len(list(IncomingSIPArcs(sip, sipArcPred))) < 2 subquery = copy.deepcopy(bodyLiteral) subquery.ground(projectedBindings) for N, x in IncomingSIPArcs(sip, sipArcPred): #That is, each subquery contains values for the bound arguments #that are passed through the sip arcs entering the node #corresponding to that literal #Create query out of body literal and apply sip-provided bindings subquery = copy.deepcopy(bodyLiteral) subquery.ground(projectedBindings) if literalIsGround(subquery): #subquery is ground, so there will only be boolean answers #we return the conjunction of the answers for the current #subquery answer = False ns = None answers = first( itertools.dropwhile( lambda item: not item[0], SipStrategy( subquery.toRDFTuple(), sipCollection, factGraph, derivedPreds, MakeImmutableDict(projectedBindings), processedRules, network=step is not None and \ step.parent.network or None, debug=debug, buildProof=buildProof, memoizeMemory=memoizeMemory, proofLevel=proofLevel))) if answers: answer, ns = answers if not answer and not bodyLiteral.naf or \ (answer and bodyLiteral.naf): #negative answer means the invokation of the rule fails #either because we have a positive literal and there #is no answer for the subgoal or the literal is #negative and there is an answer for the subgoal raise RuleFailure( "No solutions solving ground query %s" % subquery) else: if buildProof: if not answer and bodyLiteral.naf: ns.naf = True step.antecedents.append(ns) #positive answer means we can continue processing the rule body #either because we have a positive literal and answers #for subgoal or a negative literal and no answers for the #the goal for rt, _step in invokeRule([projectedBindings], bodyLiteralIterator, sip, otherargs, True, step, debug=debug): yield rt, _step else: _answers = \ SipStrategy(subquery.toRDFTuple(), sipCollection, factGraph, derivedPreds, MakeImmutableDict(projectedBindings), processedRules, network=step is not None and \ step.parent.network or None, debug=debug, buildProof=buildProof, memoizeMemory=memoizeMemory, proofLevel=proofLevel) # solve (non-ground) subgoal def collectAnswers(_ans): for ans, ns in _ans: if isinstance(ans, dict): try: map = mergeMappings1To2( ans, projectedBindings, makeImmutable=True) yield map except: pass combinedAnswers = collectAnswers(_answers) answers = lazyGeneratorPeek(combinedAnswers) if not answers.successful \ and not bodyLiteral.naf \ or (bodyLiteral.naf and answers.successful): raise RuleFailure( "No solutions solving ground query %s" % subquery) else: # Either we have a positive subgoal and answers # or a negative subgoal and no answers if buildProof: if answers.successful: goals = set([g for a, g in answers]) assert len(goals) == 1 step.antecedents.append(goals.pop()) else: newNs = NodeSet( bodyLiteral.toRDFTuple(), network=step.parent.network, identifier=BNode(), naf=True) step.antecedents.append(newNs) for rt, _step in invokeRule( answers, bodyLiteralIterator, sip, otherargs, priorBooleanGoalSuccess, step, debug=debug, buildProof=buildProof): yield rt, _step except StopIteration: #Finished processing rule if priorBooleanGoalSuccess: yield projectedBindings and projectedBindings or True, step elif projectedBindings: #Return the most recent (cumulative) answers and the given step yield projectedBindings, step else: raise RuleFailure( "Finished processing rule unsuccessfully")
def hasBindings(self): for idx, term in enumerate(GetArgs(self)): if self.adornment[idx] == 'b': if not varsOnly or isinstance(term, Variable): return True return False
def AdornRule(derivedPreds, clause, newHead, ignoreUnboundDPreds=False, hybridPreds2Replace=None): """ Adorns a horn clause using the given new head and list of derived predicates """ assert len(list(iterCondition(clause.head))) == 1 hybridPreds2Replace = hybridPreds2Replace or [] adornedHead = AdornedUniTerm(clause.head, newHead.adornment) sip = BuildNaturalSIP(clause, derivedPreds, adornedHead, hybridPreds2Replace=hybridPreds2Replace, ignoreUnboundDPreds=ignoreUnboundDPreds) bodyPredReplace = {} def adornment(arg, headArc, x): if headArc: # Sip arc from head # don't mark bound if query has no bound/distinguished terms return (arg in x and arg in adornedHead.getDistinguishedVariables(True)) \ and 'b' or 'f' else: return arg in x and 'b' or 'f' for literal in iterCondition(sip.sipOrder): op = GetOp(literal) args = GetArgs(literal) if op in derivedPreds or (op in hybridPreds2Replace if hybridPreds2Replace else False): for N, x in IncomingSIPArcs(sip, getOccurrenceId(literal)): headArc = len(N) == 1 and N[0] == GetOp(newHead) if not set(x).difference(args): # A binding # for q is useful, however, only if it is a binding for an argument of q. bodyPredReplace[literal] = AdornedUniTerm( NormalizeUniterm(literal), [adornment(arg, headArc, x) for arg in args], literal.naf) # For a predicate occurrence with no incoming # arc, the adornment contains only f. For our purposes here, # we do not distinguish between a predicate with such an # adornment and an unadorned predicate (we do in order to support open queries) if literal not in bodyPredReplace and ignoreUnboundDPreds: bodyPredReplace[literal] = AdornedUniTerm( NormalizeUniterm(literal), ['f' for arg in GetArgs(literal)], literal.naf) if hybridPreds2Replace: atomPred = GetOp(adornedHead) if atomPred in hybridPreds2Replace: adornedHead.setOperator(URIRef(atomPred + u'_derived')) for bodAtom in [ bodyPredReplace.get(p, p) for p in iterCondition(sip.sipOrder) ]: bodyPred = GetOp(bodAtom) if bodyPred in hybridPreds2Replace: bodAtom.setOperator(URIRef(bodyPred + u'_derived')) rule = AdornedRule( Clause( And([ bodyPredReplace.get(p, p) for p in iterCondition(sip.sipOrder) ]), adornedHead)) rule.sip = sip return rule
def MagicSetTransformation(factGraph, rules, GOALS, derivedPreds=None, strictCheck=DDL_STRICTNESS_FALLBACK_DERIVED, noMagic=None, defaultPredicates=None): """ Takes a goal and a ruleset and returns an iterator over the rulest that corresponds to the magic set transformation: """ noMagic = noMagic and noMagic or [] magicPredicates = set() # replacement = {} adornedProgram = SetupDDLAndAdornProgram( factGraph, rules, GOALS, derivedPreds=derivedPreds, strictCheck=strictCheck, defaultPredicates=defaultPredicates) newRules = [] for rule in adornedProgram: if rule.isSecondOrder(): import warnings warnings.warn("Second order rule no supported by GMS: %s" % rule, RuntimeWarning) magicPositions = {} #Generate magic rules for idx, pred in enumerate(iterCondition(rule.formula.body)): # magicBody = [] if isinstance(pred, AdornedUniTerm): # and pred not in magicPredicates: # For each rule r in Pad, and for each occurrence of an adorned # predicate p a in its body, we generate a magic rule defining magic_p a prevPreds = [ item for _idx, item in enumerate(rule.formula.body) if _idx < idx ] if 'b' not in pred.adornment: import warnings warnings.warn( "adorned predicate w/out any bound arguments (%s in %s)" % (pred, rule.formula), RuntimeWarning) if GetOp(pred) not in noMagic: magicPred = pred.makeMagicPred() magicPositions[idx] = (magicPred, pred) inArcs = [(N, x) for ( N, x) in IncomingSIPArcs(rule.sip, getOccurrenceId(pred)) if not set(x).difference(GetArgs(pred))] if len(inArcs) > 1: # If there are several arcs entering qi, we define the # magic rule defining magic_qi in two steps. First, # for each arc Nj --> qi with label cj , we define a # rule with head label_qi_j(cj ). The body of the rule # is the same as the body of the magic rule in the # case where there is a single arc entering qi # (described above). Then the magic rule is defined as # follows. The head is magic_q(0). The body contains # label_qi_j(cj) for all j (that is, for all arcs # entering qi ). # # We combine all incoming arcs into a single list of # (body) conditions for the magic set PrettyPrintRule(rule) SIPRepresentation(rule.sip) print(pred, magicPred) _body = [] additionalRules = [] for idxSip, (N, x) in enumerate(inArcs): newPred = pred.clone() SetOp(newPred, URIRef('%s_label_%s' % (newPred.op, idxSip))) ruleBody = And( buildMagicBody(N, prevPreds, rule.formula.head, derivedPreds)) additionalRules.append( Rule(Clause(ruleBody, newPred))) _body.extend(newPred) # _body.extend(ruleBody) additionalRules.append( Rule(Clause(And(_body), magicPred))) newRules.extend(additionalRules) for i in additionalRules: print(i) raise NotImplementedError() else: for idxSip, (N, x) in enumerate(inArcs): ruleBody = And( buildMagicBody(N, prevPreds, rule.formula.head, derivedPreds, noMagic)) newRule = Rule(Clause(ruleBody, magicPred)) newRules.append(newRule) magicPredicates.add(magicPred) # Modify rules # we modify the original rule by inserting # occurrences of the magic predicates corresponding # to the derived predicates of the body and to the head # If there are no bound arguments in the head, we don't modify the rule idxIncrement = 0 newRule = copy.deepcopy(rule) for idx, (magicPred, origPred) in list(magicPositions.items()): newRule.formula.body.formulae.insert(idx + idxIncrement, magicPred) idxIncrement += 1 if 'b' in rule.formula.head.adornment and GetOp( rule.formula.head) not in noMagic: headMagicPred = rule.formula.head.makeMagicPred() if isinstance(newRule.formula.body, Uniterm): newRule.formula.body = And( [headMagicPred, newRule.formula.body]) else: newRule.formula.body.formulae.insert(0, headMagicPred) newRules.append(newRule) if not newRules: newRules.extend(AdditionalRules(factGraph)) for rule in newRules: if rule.formula.body: yield rule
def GetVars(atom): from FuXi.Rete.SidewaysInformationPassing import GetArgs return [term for term in GetArgs(atom) if isinstance(term, Variable)]
def StratifiedSPARQL(rule, nsMapping={EX_NS: 'ex'}): """ The SPARQL specification indicates that it is possible to test if a graph pattern does not match a dataset, via a combination of optional patterns and filter conditions (like negation as failure in logic programming)([9] Sec. 11.4.1). In this section we analyze in depth the scope and limitations of this approach. We will introduce a syntax for the “difference” of two graph patterns P1 and P2, denoted (P1 MINUS P2), with the intended informal meaning: “the set of mappings that match P1 and does not match P2”. Uses telescope to construct the SPARQL MINUS BGP expressions for body conditions with default negation formulae """ from FuXi.Rete.SidewaysInformationPassing import (GetArgs, findFullSip, iterCondition) # Find a sip order of the horn rule if isinstance(rule.formula.body, And): sipOrder = first( findFullSip(([rule.formula.head], None), rule.formula.body)) else: sipOrder = [rule.formula.head] + [rule.formula.body] from telescope import optional, op from telescope.sparql.queryforms import Select # from telescope.sparql.expressions import Expression from telescope.sparql.compiler import SelectCompiler from telescope.sparql.patterns import GroupGraphPattern toDo = [] negativeVars = set() positiveLiterals = False for atom in sipOrder[1:]: if atom.naf: toDo.append(atom) negativeVars.update(GetVars(atom)) else: positiveLiterals = True #The negative literas are moved to the back of the body conjunct #Intuitively, they should not be disconnected from the rest of rule #Due to the correlation between DL and guarded FOL [sipOrder.remove(toRemove) for toRemove in toDo] #posLiterals are all the positive literals leading up to the negated #literals (in left-to-right order) There may be none, see below posLiterals = sipOrder[1:] posVarIgnore = [] if not positiveLiterals: from FuXi.Horn.PositiveConditions import Uniterm #If there are no lead, positive literals (i.e. the LP is of the form: # H :- not B1, not B2, ... #Then a 'phantom' triple pattern is needed as the left operand to the OPTIONAL #in order to properly implement P0 MINUS P where P0 is an empty pattern keyVar = GetVars(rule.formula.head)[0] newVar1 = Variable(BNode()) newVar2 = Variable(BNode()) posVarIgnore.extend([newVar1, newVar2]) phantomLiteral = Uniterm(newVar1, [keyVar, newVar2]) posLiterals.insert(0, phantomLiteral) #The positive variables are collected positiveVars = set( reduce(lambda x, y: x + y, [GetVars(atom) for atom in posLiterals])) # vars = {} # varExprs = {} # copyPatterns = [] print("%s =: { %s MINUS %s} " % (rule.formula.head, posLiterals, toDo)) def collapseMINUS(left, right): negVars = set() for pred in iterCondition(right): negVars.update( [term for term in GetArgs(pred) if isinstance(term, Variable)]) innerCopyPatternNeeded = not negVars.difference(positiveVars) #A copy pattern is needed if the negative literals don't introduce new vars if innerCopyPatternNeeded: innerCopyPatterns, innerVars, innerVarExprs = createCopyPattern( [right]) #We use an arbitrary new variable as for the outer FILTER(!BOUND(..)) outerFilterVariable = list(innerVars.values())[0] optionalPatterns = [right] + innerCopyPatterns negatedBGP = optional( *[formula.toRDFTuple() for formula in optionalPatterns]) negatedBGP.filter( *[k == v for k, v in list(innerVarExprs.items())]) positiveVars.update( [Variable(k.value[0:]) for k in list(innerVarExprs.keys())]) positiveVars.update(list(innerVarExprs.values())) else: #We use an arbitrary, 'independent' variable for the outer FILTER(!BOUND(..)) outerFilterVariable = negVars.difference(positiveVars).pop() optionalPatterns = [right] negatedBGP = optional( *[formula.toRDFTuple() for formula in optionalPatterns]) positiveVars.update(negVars) left = left.where(*[negatedBGP]) left = left.filter(~op.bound(outerFilterVariable)) return left topLevelQuery = Select(GetArgs(rule.formula.head)).where( GroupGraphPattern.from_obj( [formula.toRDFTuple() for formula in posLiterals])) rt = reduce(collapseMINUS, [topLevelQuery] + toDo) return rt, SelectCompiler(nsMapping)