예제 #1
0
 def matrixAsPredicateFacts(self, functor, arity, m):
     result = {}
     m1 = scipy.sparse.coo_matrix(m)
     typeName1 = self.schema.getArgType(functor, arity, 0)
     if arity == 2:
         typeName2 = self.schema.getArgType(functor, arity, 1)
         for i in range(len(m1.data)):
             a = self.schema.getSymbol(typeName1, m1.row[i])
             b = self.schema.getSymbol(typeName2, m1.col[i])
             w = m1.data[i]
             result[parser.Goal(functor, [a, b])] = w
     else:
         assert arity == 1, "Arity (%d) must be 1 or 2" % arity
         for i in range(len(m1.data)):
             assert m1.row[i] == 0, "Expected 0 at m1.row[%d]" % i
             b = self.schema.getSymbol(typeName1, m1.col[i])
             w = m1.data[i]
             if b == None:
                 if i == 0 and w < 1e-10:
                     logging.warn(
                         'ignoring low weight %g placed on index 0 for type %s in predicate %s'
                         % (w, typeName1, functor))
                 elif i == 0:
                     logging.warn(
                         'ignoring large weight %g placed on index 0 for type %s in predicate %s'
                         % (w, typeName1, functor))
                 else:
                     assert False, 'cannot find symbol on fact with weight %g for index %d for type %s in predicate %s' % (
                         w, i, typeName1, functor)
             if b is not None:
                 result[parser.Goal(functor, [b])] = w
     return result
예제 #2
0
 def goal_builder(rule_id):
     var_name = rule_id[0].upper() + rule_id[1:]
     return RuleWrapper(None, [],
                        features=[parser.Goal('weight', [var_name])],
                        findall=[
                            parser.Goal(bpcompiler.ASSIGN,
                                        [var_name, rule_id, type_name])
                        ])
예제 #3
0
 def setFeatureWeights(self,epsilon=1.0):
     def possibleModes(rule):
         # cycle through all possible modes
         f = rule.lhs.functor
         a = rule.lhs.arity
         for k in range(a):
             io = ['i']*a
             io[k] = 'o'
             yield declare.asMode("%s/%s" % (f,"".join(io)))
     if self.db.isTypeless():
         self._setFeatureWeightsForTypelessDB(epsilon=epsilon)
     else:
         inferredParamType = {}
         # don't assume types for weights have been declared
         for rule in self.rules:
           for m in possibleModes(rule):
             varTypes = bpcompiler.BPCompiler(m,self,0,rule).inferredTypes()
             for goal in rule.rhs:
               if goal.arity==1 and (goal.functor,goal.arity) in self.db.paramSet:
                 newType = varTypes.get(goal.args[0])
                 decl = declare.TypeDeclaration(parser.Goal(goal.functor,[newType]))
                 self.db.schema.declarePredicateTypes(decl.functor,decl.args())
         for (functor,arity) in self.db.paramList:
             if arity==1:
                 typename = self.db.schema.getArgType(functor,arity,0)
                 self.db.setParameter(functor,arity,self.db.ones(typename)*epsilon)
             else:
                 logging.warn('cannot set weights of matrix parameter %s/%d automatically',functor,arity)
예제 #4
0
 def _listRules(self, functor, arity):
     mode = declare.ModeDeclaration(parser.Goal(functor, ['x'] * arity),
                                    strict=False)
     rules = self.prog.rules.rulesFor(mode)
     if rules is not None:
         for r in rules:
             print r
         return True
     return False
예제 #5
0
def asMode(spec):
    """Convert strings like "foo(i,o)" or "foo/io" to ModeDeclarations.
    Or, if given a ModeDeclaration object, return that object.
    """
    if type(spec)==type("") and spec.find("/")>=0:
        functor,rest = spec.split("/")
        return ModeDeclaration(parser.Goal(functor,list(rest)))
    elif type(spec)==type(""):
        return ModeDeclaration(spec)
    else:
        return spec
예제 #6
0
 def _moveFeaturesToRHS(self,rule0):
     rule = parser.Rule(rule0.lhs, rule0.rhs)
     if not rule0.findall:
         #parsed format is {f1,f2,...} but we only support {f1}
         if rule0.features is None:
           logging.warn('this rule has no features: %s' % str(rule))
         else:
           assert len(rule0.features)==1,'multiple constant features not supported'
           assert rule0.features[0].arity==0, '{foo(A,...)} not allowed, use {foo(A,...):true}'
           constFeature = rule0.features[0].functor
           constAsVar = constFeature.upper()
           rule.rhs.append( parser.Goal(bpcompiler.ASSIGN, [constAsVar,constFeature]) )
           rule.rhs.append( parser.Goal('weighted',[constAsVar]) )
           # record the rule name, ie the constant feature
           self.ruleIds.append(constFeature)
     else:
         #format is {foo(F):-...}
         assert len(rule0.features)==1,'feature generators of the form {a,b: ... } not supported'
         featureLHS = rule0.features[0]
         assert featureLHS.arity==1, 'non-constant features must be of the form {foo(X):-...}'
         outputVar = featureLHS.args[0]
         paramName = featureLHS.functor
         for goal in rule0.findall:
             if goal.arity!=0 and goal.functor!='true':
               rule.rhs.append(goal)
         rule.rhs.append( parser.Goal(paramName,[outputVar]) )
         # record the feature predicate 'foo' as a parameter
         if self.db: self.db.markAsParameter(paramName,1)
         if self.db.isTypeless():
             # record the domain of the predicate that will be used as a feature in parameters
             for goal in rule0.findall:
                 if outputVar in goal.args:
                   k = goal.args.index(outputVar)
                   if goal.arity==2:
                       paramMode = declare.asMode("%s/io" % goal.functor) if k==0 else declare.asMode("%s/oi" % goal.functor)
                       self.paramDomains[paramName].append(paramMode)
     return rule
예제 #7
0
    def toMode(self, j):
        """Helper - Return a mode declaration for the j-th goal of the rule,
    based on the information flow"""
        goal = self.goals[j]
        gin = self.goalDict[j]

        def argIOMode(x):
            if x in gin.inputs: return 'i'
            elif x in gin.outputs: return 'o'
            else:
                # TODO figure out what's happening here?
                assert x != 'i' and x != 'o' and x != 'i1' and x != 'i2', 'Illegal to use constants i,o,i1,o1 in a program'
                return x

        return declare.ModeDeclaration(parser.Goal(
            goal.functor, [argIOMode(x) for x in goal.args]),
                                       strict=False)
예제 #8
0
 def testBuilder1(self):
   b = simple.Builder()
   X,Y,Z = b.variables("X Y Z")
   aunt,parent,sister,wife = b.predicates("aunt parent sister wife")
   uncle = b.predicate("uncle")
   b += aunt(X,Y) <= uncle(X,Z) & wife(Z,Y)
   b += aunt(X,Y) <= parent(X,Z) & sister(Z,Y)
   r1 = b.rule_id("ruleid_t","r1")
   r2 = b.rule_id("ruleid_t","r2")
   b += aunt(X,Y) <= uncle(X,Z) & wife(Z,Y) // r1
   b += aunt(X,Y) <= parent(X,Z) & sister(Z,Y) // r2
   feature,description = b.predicates("feature description")
   weight = b.predicate("weight")
   F = b.variable("F")
   D = b.variable("D")
   b += aunt(X,Y) <= uncle(X,Z) & wife(Z,Y) // (weight(F) | description(X,D) & feature(X,F))
   b.rules.listing()
   rs = b.rules.rulesFor(parser.Goal('aunt',[X,Y]))
   self.assertEqual(str(rs[0]), "aunt(X,Y) :- uncle(X,Z), wife(Z,Y).")
   self.assertEqual(str(rs[1]), "aunt(X,Y) :- parent(X,Z), sister(Z,Y).")
   self.assertEqual(str(rs[2]), "aunt(X,Y) :- uncle(X,Z), wife(Z,Y) {weight(R1) : assign(R1,r1,ruleid_t)}.")
   self.assertEqual(str(rs[3]), "aunt(X,Y) :- parent(X,Z), sister(Z,Y) {weight(R2) : assign(R2,r2,ruleid_t)}.")
   self.assertEqual(str(rs[4]), "aunt(X,Y) :- uncle(X,Z), wife(Z,Y) {weight(F) : description(X,D),feature(X,F)}.")
예제 #9
0
    def generateOps(self):
        """Emulate BP and emit the sequence of operations needed.  Instead of
    actually constructing a message from src->dst in the course of
    BP, what we do instead is emit operations that would construct
    the message and assign it a 'variable' named 'foo', and then
    return not the message but the string that names the 'variable'.
    """

        messages = {}

        def addOp(op, traceDepth, msgFrom, msgTo):
            """Add an operation to self.ops, echo if required"""
            if conf.trace: print '%s+%s' % (('| ' * traceDepth), op)

            def jToGoal(msg):
                return str(self.goals[msg]) if type(msg) == type(0) else msg

            op.setMessage(jToGoal(msgFrom), jToGoal(msgTo))
            self.ops.append(op)

        def makeMessageName(stem, v, j=None, j2=None):
            """ create a string that meaningfully names this message
      """
            msgName = '%s_%s' % (stem, v)
            if j is not None: msgName += '%d' % j
            if j2 is not None: msgName += '%d' % j2
            self.msgType[msgName] = self.varDict[v].varType
            return msgName

        def msgGoal2Var(j, v, traceDepth):
            """Send a message from a goal to a variable.  Note goals can have at
      most one input and at most one output.  This is complex
      because there are several cases, depending on if the goal
      is LHS on RHS, and if the variable is an input or
      output."""
            gin = self.goalDict[j]
            if conf.trace: print '%smsg: %d->%s' % (('| ' * traceDepth), j, v)
            if j == 0 and v in self.goalDict[j].inputs:
                #input port -> input variable - The lhs goal, j==0, is the input factor
                assert parser.isVariableAtom(v), 'input must be a variable'
                return v
            elif j == 0:
                #output port -> output variable
                assert False, 'illegal message goal %d to var %s' % (j, v)
            elif j > 0 and v in self.goalDict[j].outputs:
                #message from rhs goal to an output variable of that goal
                msgName = makeMessageName('f', v, j)
                mode = self.toMode(j)
                if not gin.inputs:
                    # special case - binding a variable to a constant with assign(Var,const) or assign(Var,type,const)
                    # TODO: should unary predicates in general be an input?
                    errorMsg = 'output variables without inputs are only allowed for assign/2 or assign/3: %s' % str(
                        self.rule.rhs[gin.index - 1])
                    assert (mode.functor == ASSIGN and mode.arity >= 2
                            and mode.isOutput(0)), errorMsg
                    addOp(ops.AssignOnehotToVar(msgName, mode), traceDepth, j,
                          v)
                    return msgName
                else:
                    # figure out how to forward message from inputs to outputs
                    if (self.tensorlogProg.plugins.isDefined(mode)):
                        fxs = []
                        for vIn in gin.inputs:
                            vMsg = msgVar2Goal(vIn, j, traceDepth + 1)
                            fxs.append(vMsg)
                        outType = self.tensorlogProg.plugins.outputType(
                            mode, self.collectInputTypes(j))
                        addOp(
                            ops.CallPlugin(msgName, fxs, mode,
                                           dstType=outType), traceDepth, j, v)
                    else:
                        fx = msgVar2Goal(
                            _only(gin.inputs), j, traceDepth + 1
                        )  #ask for the message forward from the input to goal j
                        if not gin.definedPred:
                            addOp(ops.VecMatMulOp(msgName, fx, mode),
                                  traceDepth, j, v)
                        else:
                            addOp(
                                ops.DefinedPredOp(self.tensorlogProg, msgName,
                                                  fx, mode, self.depth + 1),
                                traceDepth, j, v)
                    return msgName
            elif j > 0 and v in self.goalDict[j].inputs:
                #message from rhs goal to an input variable of that goal
                gin = self.goalDict[j]
                msgName = makeMessageName('b', v, j)
                mode = self.toMode(j)

                def hasOutputVarUsedElsewhere(gin):
                    outVar = _only(gin.outputs)
                    return self.varDict[outVar].inputTo

                if gin.outputs and hasOutputVarUsedElsewhere(gin):
                    bx = msgVar2Goal(
                        _only(gin.outputs), j, traceDepth + 1
                    )  #ask for the message backward from the input to goal
                    addOp(ops.VecMatMulOp(msgName, bx, mode, transpose=True),
                          traceDepth, j, v)
                    return msgName
                else:
                    if gin.outputs:
                        #optimize away the message from the output
                        # var of gin, since it would be a dense
                        # all-ones vector
                        # TODO: is this needed, since multiplication by an all-ones vector is kindof how this is usually implemented?
                        assert len(
                            gin.outputs
                        ) == 1, 'need single output from %s' % self.goals[j]
                        #this variable now is connected to the main chain
                        self.varDict[_only(gin.outputs)].connected = True
                        assert not gin.definedPred, 'subpredicates must generate an output which is used downstream'
                        addOp(
                            ops.AssignPreimageToVar(msgName, mode,
                                                    self.msgType[msgName]),
                            traceDepth, j, v)
                    else:
                        addOp(
                            ops.AssignVectorToVar(msgName, mode,
                                                  self.msgType[msgName]),
                            traceDepth, j, v)

                    return msgName
            else:
                assert False, 'unexpected message goal %d -> %s ins %r outs %r' % (
                    j, v, gin.inputs, gin.outputs)

        def msgVar2Goal(v, j, traceDepth):
            """Message from a variable to a goal.
      """
            vin = self.varDict[v]
            vin.connected = True
            gin = self.goalDict[j]
            #variables have one outputOf, but possily many inputTo
            #connections. Information  propagates back from things the
            #variables are inputTo, unless those goals are CallPlugin's.
            vNeighbors = [
                j2 for j2 in [vin.outputOf] + list(vin.inputTo) if j2 != j
            ]
            if conf.trace:
                print '%smsg from %s to %d, vNeighbors=%r' % (
                    '| ' * traceDepth, v, j, vNeighbors)
            assert len(
                vNeighbors
            ), 'variables should have >=1 neighbor but %s has none: %d' % (v,
                                                                           j)
            #form product of the incoming messages, cleverly
            #generating only the variables we really need
            currentProduct = msgGoal2Var(vNeighbors[0], v, traceDepth + 1)
            for j2 in vNeighbors[1:]:
                nextProd = makeMessageName(
                    'p', v, j,
                    j2) if j2 != vNeighbors[-1] else makeMessageName('fb', v)
                multiplicand = msgGoal2Var(j2, v, traceDepth + 1)
                addOp(
                    ops.ComponentwiseVecMulOp(nextProd, currentProduct,
                                              multiplicand), traceDepth, v, j)
                currentProduct = nextProd
            return currentProduct

        #
        # main BP code starts here
        #

        #generate a message from the output variable to the lhs
        outputVar = _only(self.goalDict[0].outputs)
        outputMsg = msgVar2Goal(outputVar, 0, 1)

        #now look for other unconnected variables, and connect them to
        #a pseudo-node so they have something to send to.  The
        #outputMsg above will be weighted by the product of all of
        #these messages.
        weighters = []
        psj = len(self.goals)
        self.goals.append(parser.Goal('PSEUDO', []))
        self.goalDict[psj] = GoalInfo(psj)
        #heuristic - start with the rightmost unconnected variable,
        #hoping that it's the end of a chain rooted at the input,
        #which should be quicker to evaluate
        for j in reversed(range(1, len(self.goals))):
            goalj = self.goals[j]
            for i in range(goalj.arity):
                v = goalj.args[i]
                if parser.isVariableAtom(v) and not self.varDict[v].connected:
                    #save the message from this unconnected node
                    weighters.append(msgVar2Goal(v, psj, 1))
        #multiply the weighting factors from the unconnected node to
        #the outputMsg, again cleverly reusing variable names to keep
        #the expression simple.
        currentProduct = outputMsg
        for msg in weighters:
            nextProd = makeMessageName(
                'w', outputVar) if msg == weighters[-1] else makeMessageName(
                    'p_%s' % msg, outputVar)
            multiplicand = msg
            addOp(ops.WeightedVec(nextProd, multiplicand, currentProduct), 0,
                  msg, 'PSEUDO')
            currentProduct = nextProd

        # save the output and inputs
        self.output = currentProduct
        self.outputType = self.msgType[currentProduct]
        self.inputs = list(self.goalDict[0].inputs)
        self.inputTypes = map(lambda v: self.varDict[v].varType, self.inputs)
예제 #10
0
 def builder(*args):
     return RuleWrapper(None, [parser.Goal(pred_name, args)])