def Th(owlGraph, _class, variable=Variable('X'), position=LHS): """ DLP head (antecedent) knowledge assertional forms (ABox assertions, conjunction of ABox assertions, and universal role restriction assertions) """ props = list(set(owlGraph.predicates(subject=_class))) if OWL_NS.allValuesFrom in props: #http://www.w3.org/TR/owl-semantics/#owl_allValuesFrom for s, p, o in owlGraph.triples((_class, OWL_NS.allValuesFrom, None)): prop = list(owlGraph.objects(subject=_class, predicate=OWL_NS.onProperty))[0] newVar = Variable(BNode()) body = Uniterm(prop, [variable, newVar], newNss=owlGraph.namespaces()) for head in Th(owlGraph, o, variable=newVar): yield Clause(body, head) elif OWL_NS.hasValue in props: prop = list(owlGraph.objects(subject=_class, predicate=OWL_NS.onProperty))[0] o = first(owlGraph.objects(subject=_class, predicate=OWL_NS.hasValue)) yield Uniterm(prop, [variable, o], newNss=owlGraph.namespaces()) elif OWL_NS.someValuesFrom in props: #http://www.w3.org/TR/owl-semantics/#someValuesFrom for s, p, o in owlGraph.triples((_class, OWL_NS.someValuesFrom, None)): prop = list(owlGraph.objects(subject=_class, predicate=OWL_NS.onProperty))[0] newVar = BNode() yield And([Uniterm(prop, [variable, newVar], newNss=owlGraph.namespaces()), generatorFlattener(Th(owlGraph, o, variable=newVar))]) elif OWL_NS.intersectionOf in props: from FuXi.Syntax.InfixOWL import BooleanClass yield And([first(Th(owlGraph, h, variable)) for h in BooleanClass(_class)]) else: #Simple class yield Uniterm(RDF.type, [variable, isinstance(_class, BNode) and SkolemizeExistentialClasses(_class) or _class], newNss=owlGraph.namespaces())
def Tc(owlGraph, negatedFormula): """ Handles the conversion of negated DL concepts into a general logic programming condition for the body of a rule that fires when the body conjunct is in the minimal model """ if (negatedFormula, OWL_NS.hasValue, None) in owlGraph: #not ( R value i ) bodyUniTerm = Uniterm(RDF.type, [Variable("X"), NormalizeBooleanClassOperand(negatedFormula, owlGraph)], newNss=owlGraph.namespaces()) condition = NormalizeClause(Clause(Tb(owlGraph, negatedFormula), bodyUniTerm)).body assert isinstance(condition, Uniterm) condition.naf = True return condition elif (negatedFormula, OWL_NS.someValuesFrom, None) in owlGraph: #not ( R some C ) binaryRel, unaryRel = Tb(owlGraph, negatedFormula) negatedBinaryRel = copy.deepcopy(binaryRel) negatedBinaryRel.naf = True negatedUnaryRel = copy.deepcopy(unaryRel) negatedUnaryRel.naf = True return Or([negatedBinaryRel, And([binaryRel, negatedUnaryRel])]) elif isinstance(negatedFormula, URIRef): return Uniterm(RDF.type, [Variable("X"), NormalizeBooleanClassOperand(negatedFormula, owlGraph)], newNss=owlGraph.namespaces(), naf=True) else: raise UnsupportedNegation("Unsupported negated concept: %s" % negatedFormula)
def testSerializingEvalPred(self): nsBindings = {u'bfp': BFP_NS, u'rule': BFP_RULE} evaluateTerm = Uniterm(BFP_NS.evaluate, [BFP_RULE[str(1)], Literal(1)], newNss=nsBindings) self.failUnless(repr(evaluateTerm), "bfp:evaluate(rule:1 1)") xVar = Variable('X') yVar = Variable('Y') bodyTerm = Uniterm(RDF.rest, [xVar, yVar], newNss=nsBindings) rule = Rule(Clause(bodyTerm, evaluateTerm), declare=[xVar, yVar]) self.assertEqual( repr(rule), "Forall ?X ?Y ( bfp:evaluate(rule:1 1) :- rdf:rest(?X ?Y) )")
def Tb(owlGraph, _class, variable=Variable('X')): """ DLP body (consequent knowledge assertional forms (ABox assertions, conjunction / disjunction of ABox assertions, and exisential role restriction assertions) These are all common EL++ templates for KR """ props = list(set(owlGraph.predicates(subject=_class))) if OWL_NS.intersectionOf in props and not isinstance(_class, URIRef): for s, p, o in owlGraph.triples((_class, OWL_NS.intersectionOf, None)): conj = [] handleConjunct(conj, owlGraph, o, variable) return And(conj) elif OWL_NS.unionOf in props and not isinstance(_class, URIRef): # http://www.w3.org/TR/owl-semantics/#owl_unionOf for s, p, o in owlGraph.triples((_class, OWL_NS.unionOf, None)): return Or([ Tb(owlGraph, c, variable=variable) for c in Collection(owlGraph, o) ]) elif OWL_NS.someValuesFrom in props: # http://www.w3.org/TR/owl-semantics/#owl_someValuesFrom prop = list( owlGraph.objects(subject=_class, predicate=OWL_NS.onProperty))[0] o = list( owlGraph.objects(subject=_class, predicate=OWL_NS.someValuesFrom))[0] newVar = Variable(BNode()) # body = Uniterm(prop, [variable, newVar], newNss=owlGraph.namespaces()) # head = Th(owlGraph, o, variable=newVar) return And([ Uniterm(prop, [variable, newVar], newNss=owlGraph.namespaces()), Tb(owlGraph, o, variable=newVar) ]) elif OWL_NS.hasValue in props: # http://www.w3.org/TR/owl-semantics/#owl_hasValue # Domain-specific rules for hasValue # Can be achieved via pD semantics prop = list( owlGraph.objects(subject=_class, predicate=OWL_NS.onProperty))[0] o = first(owlGraph.objects(subject=_class, predicate=OWL_NS.hasValue)) return Uniterm(prop, [variable, o], newNss=owlGraph.namespaces()) elif OWL_NS.complementOf in props: return Tc(owlGraph, first(owlGraph.objects(_class, OWL_NS.complementOf))) else: # simple class # "Named" Uniterm _classTerm = SkolemizeExistentialClasses(_class) return Uniterm(RDF.type, [variable, _classTerm], newNss=owlGraph.namespaces())
def handleConjunct(conjunction, owlGraph, o, conjunctVar=Variable('X')): for bodyTerm in Collection(owlGraph, o): negatedFormula = False addToConjunct = None for negatedFormula in owlGraph.objects(subject=bodyTerm, predicate=OWL_NS.complementOf): addToConjunct = Tc(owlGraph, negatedFormula) if negatedFormula: # addToConjunct will be the term we need to add to the conjunct conjunction.append(addToConjunct) else: normalizedBodyTerm = NormalizeBooleanClassOperand( bodyTerm, owlGraph) bodyUniTerm = Uniterm(RDF.type, [conjunctVar, normalizedBodyTerm], newNss=owlGraph.namespaces()) processedBodyTerm = Tb(owlGraph, bodyTerm, conjunctVar) classifyingClause = NormalizeClause( Clause(processedBodyTerm, bodyUniTerm)) # redundantClassifierClause = processedBodyTerm == bodyUniTerm if isinstance(normalizedBodyTerm, URIRef) and normalizedBodyTerm.find( SKOLEMIZED_CLASS_NS) == -1: conjunction.append(bodyUniTerm) elif (bodyTerm, OWL_NS.someValuesFrom, None) in owlGraph or\ (bodyTerm, OWL_NS.hasValue, None) in owlGraph: conjunction.extend(classifyingClause.body) elif (bodyTerm, OWL_NS.allValuesFrom, None) in owlGraph: raise MalformedDLPFormulaError( "Universal restrictions can only be used as the second argument to rdfs:subClassOf (GCIs)" ) elif (bodyTerm, OWL_NS.unionOf, None) in owlGraph: conjunction.append(classifyingClause.body) elif (bodyTerm, OWL_NS.intersectionOf, None) in owlGraph: conjunction.append(bodyUniTerm)
def extractAtom(self, atom): args, op = self.atoms[atom] op = self.extractTerm(op) args = list(map(self.extractTerm, Collection(self.graph, args))) if len(args) > 2: raise NotImplementedError( "FuXi RIF Core parsing only supports subset involving binary/unary Atoms" ) return Uniterm(op, args)
def extractFrame(self, frame): obj, slots = self.frames[frame] rt = [] for slot in Collection(self.graph, slots): k = self.extractTerm(first(self.graph.objects(slot, RIF_NS.slotkey))) v = self.extractTerm(first(self.graph.objects(slot, RIF_NS.slotvalue))) rt.append( Uniterm(k, [self.extractTerm(obj), v]) ) return rt
def extractPredication(self, predication, predType): if predType == RIF_NS.Frame: return self.extractFrame(predication) elif predType == RIF_NS.Atom: return [self.extractAtom(predication)] else: assert predType == RIF_NS.External # from FuXi.Rete.RuleStore import N3Builtin # N3Builtin(self, uri, func, argument, result) args, op = self.externals[predication] args = list(map(self.extractTerm, Collection(self.graph, args))) op = self.extractTerm(op) return [ExternalFunction(Uniterm(op, args))]
def T(owlGraph, complementExpansions=[], derivedPreds=[]): """ #Subsumption (purely for TBOX classification) {?C rdfs:subClassOf ?SC. ?A rdfs:subClassOf ?C} => {?A rdfs:subClassOf ?SC}. {?C owl:equivalentClass ?A} => {?C rdfs:subClassOf ?A. ?A rdfs:subClassOf ?C}. {?C rdfs:subClassOf ?SC. ?SC rdfs:subClassOf ?C} => {?C owl:equivalentClass ?SC}. T(rdfs:subClassOf(C, D)) -> Th(D(y)) :- Tb(C(y)) T(owl:equivalentClass(C, D)) -> { T(rdfs:subClassOf(C, D) T(rdfs:subClassOf(D, C) } A generator over the Logic Programming rules which correspond to the DL ( unary predicate logic ) subsumption axiom described via rdfs:subClassOf """ for s, p, o in owlGraph.triples((None, OWL_NS.complementOf, None)): if isinstance(o, URIRef) and isinstance(s, URIRef): headLiteral = Uniterm( RDF.type, [Variable("X"), SkolemizeExistentialClasses(s)], newNss=owlGraph.namespaces()) yield NormalizeClause(Clause(Tc(owlGraph, o), headLiteral)) for c, p, d in owlGraph.triples((None, RDFS.subClassOf, None)): try: yield NormalizeClause(Clause(Tb(owlGraph, c), Th(owlGraph, d))) except UnsupportedNegation: warnings.warn( "Unable to handle negation in DL axiom (%s), skipping" % c, # e.msg, SyntaxWarning, 3) # assert isinstance(c, URIRef), "%s is a kind of %s"%(c, d) for c, p, d in owlGraph.triples((None, OWL_NS.equivalentClass, None)): if c not in derivedPreds: yield NormalizeClause(Clause(Tb(owlGraph, c), Th(owlGraph, d))) yield NormalizeClause(Clause(Tb(owlGraph, d), Th(owlGraph, c))) for s, p, o in owlGraph.triples((None, OWL_NS.intersectionOf, None)): try: if s not in complementExpansions: if s in derivedPreds: warnings.warn( "Derived predicate (%s) is defined via a conjunction (consider using a complex GCI) " % owlGraph.qname(s), SyntaxWarning, 3) elif isinstance( s, BNode): # and (None, None, s) not in owlGraph:# and \ # (s, RDFS.subClassOf, None) in owlGraph: # complex GCI, pass over (handled) by Tb continue conjunction = [] handleConjunct(conjunction, owlGraph, o) body = And(conjunction) head = Uniterm(RDF.type, [Variable("X"), SkolemizeExistentialClasses(s)], newNss=owlGraph.namespaces()) # O1 ^ O2 ^ ... ^ On => S(?X) yield Clause(body, head) if isinstance(s, URIRef): # S(?X) => O1 ^ O2 ^ ... ^ On # special case, owl:intersectionOf is a neccessary and sufficient # criteria and should thus work in *both* directions # This rule is not added for anonymous classes or derived # predicates if s not in derivedPreds: yield Clause(head, body) except UnsupportedNegation: warnings.warn( "Unable to handle negation in DL axiom (%s), skipping" % s, # e.msg, SyntaxWarning, 3) for s, p, o in owlGraph.triples((None, OWL_NS.unionOf, None)): if isinstance(s, URIRef): # special case, owl:unionOf is a neccessary and sufficient # criteria and should thus work in *both* directions body = Or([ Uniterm( RDF.type, [Variable("X"), NormalizeBooleanClassOperand(i, owlGraph)], newNss=owlGraph.namespaces()) for i in Collection(owlGraph, o) ]) head = Uniterm(RDF.type, [Variable("X"), s], newNss=owlGraph.namespaces()) yield Clause(body, head) for s, p, o in owlGraph.triples((None, OWL_NS.inverseOf, None)): # T(owl:inverseOf(P, Q)) -> { Q(x, y) :- P(y, x) # P(y, x) :- Q(x, y) } newVar = Variable(BNode()) s = SkolemizeExistentialClasses(s) if isinstance(s, BNode) else s o = SkolemizeExistentialClasses(o) if isinstance(o, BNode) else o body1 = Uniterm(s, [newVar, Variable("X")], newNss=owlGraph.namespaces()) head1 = Uniterm(o, [Variable("X"), newVar], newNss=owlGraph.namespaces()) yield Clause(body1, head1) newVar = Variable(BNode()) body2 = Uniterm(o, [Variable("X"), newVar], newNss=owlGraph.namespaces()) head2 = Uniterm(s, [newVar, Variable("X")], newNss=owlGraph.namespaces()) yield Clause(body2, head2) for s, p, o in owlGraph.triples( (None, RDF.type, OWL_NS.TransitiveProperty)): # T(owl:TransitiveProperty(P)) -> P(x, z) :- P(x, y) ^ P(y, z) y = Variable(BNode()) z = Variable(BNode()) x = Variable("X") s = SkolemizeExistentialClasses(s) if isinstance(s, BNode) else s body = And([ Uniterm(s, [x, y], newNss=owlGraph.namespaces()), Uniterm(s, [y, z], newNss=owlGraph.namespaces()) ]) head = Uniterm(s, [x, z], newNss=owlGraph.namespaces()) yield Clause(body, head) for s, p, o in owlGraph.triples((None, RDFS.subPropertyOf, None)): # T(rdfs:subPropertyOf(P, Q)) -> Q(x, y) :- P(x, y) x = Variable("X") y = Variable("Y") s = SkolemizeExistentialClasses(s) if isinstance(s, BNode) else s o = SkolemizeExistentialClasses(o) if isinstance(o, BNode) else o body = Uniterm(s, [x, y], newNss=owlGraph.namespaces()) head = Uniterm(o, [x, y], newNss=owlGraph.namespaces()) yield Clause(body, head) for s, p, o in owlGraph.triples((None, OWL_NS.equivalentProperty, None)): # T(owl:equivalentProperty(P, Q)) -> { Q(x, y) :- P(x, y) # P(x, y) :- Q(x, y) } x = Variable("X") y = Variable("Y") s = SkolemizeExistentialClasses(s) if isinstance(s, BNode) else s o = SkolemizeExistentialClasses(o) if isinstance(o, BNode) else o body = Uniterm(s, [x, y], newNss=owlGraph.namespaces()) head = Uniterm(o, [x, y], newNss=owlGraph.namespaces()) yield Clause(body, head) yield Clause(head, body) # Contribution (Symmetric DL roles) for s, p, o in owlGraph.triples( (None, RDF.type, OWL_NS.SymmetricProperty)): # T(owl:SymmetricProperty(P)) -> P(y, x) :- P(x, y) y = Variable("Y") x = Variable("X") s = SkolemizeExistentialClasses(s) if isinstance(s, BNode) else s body = Uniterm(s, [x, y], newNss=owlGraph.namespaces()) head = Uniterm(s, [y, x], newNss=owlGraph.namespaces()) yield Clause(body, head) for s, p, o in owlGraph.triples_choices((None, [RDFS.range, RDFS.domain], None)): s = SkolemizeExistentialClasses(s) if isinstance(s, BNode) else s if p == RDFS.range: # T(rdfs:range(P, D)) -> D(y) := P(x, y) x = Variable("X") y = Variable(BNode()) body = Uniterm(s, [x, y], newNss=owlGraph.namespaces()) head = Uniterm(RDF.type, [y, o], newNss=owlGraph.namespaces()) yield Clause(body, head) else: # T(rdfs:domain(P, D)) -> D(x) := P(x, y) x = Variable("X") y = Variable(BNode()) body = Uniterm(s, [x, y], newNss=owlGraph.namespaces()) head = Uniterm(RDF.type, [x, o], newNss=owlGraph.namespaces()) yield Clause(body, head)
def __init__(self, formulae=None, n3Rules=None, nsMapping=None): from FuXi.Rete.RuleStore import N3Builtin self.nsMapping = nsMapping and nsMapping or {} self.formulae = formulae and formulae or [] if n3Rules: from FuXi.DLP import breadth_first # Convert a N3 abstract model (parsed from N3) into a RIF BLD for lhs, rhs in n3Rules: allVars = set() for ruleCondition in [lhs, rhs]: for stmt in ruleCondition: if isinstance(stmt, N3Builtin): ExternalFunction(stmt, newNss=self.nsMapping) # print(stmt) # raise allVars.update([ term for term in stmt if isinstance(term, (BNode, Variable)) ]) body = [ isinstance(term, N3Builtin) and term or Uniterm(list(term)[1], [list(term)[0], list(term)[-1]], newNss=nsMapping) for term in lhs ] body = len(body) == 1 and body[0] or And(body) head = [ Uniterm(p, [s, o], newNss=nsMapping) for s, p, o in rhs ] head = len(head) == 1 and head[0] or And(head) # first we identify body variables bodyVars = set( reduce(lambda x, y: x + y, [ list(extractVariables(i, existential=False)) for i in iterCondition(body) ])) # then we identify head variables headVars = set( reduce(lambda x, y: x + y, [ list(extractVariables(i, existential=False)) for i in iterCondition(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]) for uniTerm in iterCondition(head): def updateUniterm(uterm): newArg = [updateDict.get(i, i) for i in uniTerm.arg] uniTerm.arg = newArg if isinstance(uniTerm, Uniterm): updateUniterm(uniTerm) else: for u in uniTerm: updateUniterm(u) exist = [ list(extractVariables(i)) for i in breadth_first(head) ] e = Exists(formula=head, declare=set(reduce(lambda x, y: x + y, exist, []))) if reduce(lambda x, y: x + y, exist): head = e assert e.declare, exist self.formulae.append(Rule(Clause(body, head), declare=allVars))
def NormalizeUniterm(term): if isinstance(term, Uniterm): return term elif isinstance(term, N3Builtin): return Uniterm(term.uri, term.argument, term.naf)
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)