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
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]) ])
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)
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
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
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
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)
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)}.")
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)
def builder(*args): return RuleWrapper(None, [parser.Goal(pred_name, args)])