Beispiel #1
0
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())
Beispiel #2
0
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) )")
Beispiel #4
0
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())
Beispiel #5
0
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)
Beispiel #6
0
 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)
Beispiel #7
0
 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
Beispiel #8
0
 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))]
Beispiel #9
0
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)
Beispiel #10
0
    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))
Beispiel #11
0
def NormalizeUniterm(term):
    if isinstance(term, Uniterm):
        return term
    elif isinstance(term, N3Builtin):
        return Uniterm(term.uri, term.argument, term.naf)
Beispiel #12
0
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)