def _itergroundings_fast(self, formula, constituents, cidx, assignment, variables, falsevar=None, level=0): if cidx == len(constituents): # no remaining literals to ground. return the ground formula and statistics stat = [(varidx, validx, count) for (varidx, validx, count) in variables] yield formula.idx, stat return c = constituents[cidx] # go through all remaining groundings of the current constituent for varass in c.itervargroundings(self.mrf, partial=assignment): gnd = c.ground(self.mrf, dict_union(varass, assignment)) # check if it violates a hard constraint if formula.weight == HARD and gnd(self.mrf.evidence) < 1: raise SatisfiabilityException('MLN is unsatisfiable by evidence due to hard constraint violation %s (see above)' % global_bpll_grounding.mrf.formulas[formula.idx]) if isinstance(gnd, Logic.Equality): # if an equality grounding is false in a conjunction, we can stop since the # conjunction cannot be rendered true in any grounding that follows if gnd.truth(None) == 0: continue for gf in self._itergroundings_fast(formula, constituents, cidx+1, dict_union(assignment, varass), variables, falsevar, level+1): yield gf else: var = self.mrf.variable(gnd.gndatom) world_ = list(self.mrf.evidence) stat = [] skip = False falsevar_ = falsevar vars_ = list(variables) for validx, value in var.itervalues(): var.setval(value, world_) truth = gnd(world_) if truth == 0 and value == var.evidence_value(): # if the evidence value renders the current consituent false # and there was already a false literal in the grounding path, # we can prune the tree since no grounding will be true if falsevar is not None and falsevar != var.idx: skip = True break else: # if there was no literal false so far, we collect statistics only for the current literal # and only if all future literals will be true by evidence vars_ = [] falsevar_ = var.idx if truth > 0 and falsevar is None: stat.append((var.idx, validx, truth)) if falsevar is not None and falsevar == var.idx: # in case of non-mutual exclusive values take only the values that render all literals true # example: soft-functional constraint with !foo(?x) ^ foo(?y), x={X,Y,Z} where the evidence foo(Z) is true # here the grounding !foo(X) ^ foo(Y) is false: # !foo(X) is true for foo(Z) and foo(Y) and (!foo(Z) ^ !foox(X) ^ !foo(Y)) # foo(Y) is true for foo(Y) # both are only true for foo(Y) stat = set(variables).intersection(stat) skip = not bool(stat) # skip if no values remain if skip: continue for gf in self._itergroundings_fast(formula, constituents, cidx+1, dict_union(assignment, varass), vars_ + stat, falsevar=falsevar_, level=level+1): yield gf
def _itergroundings_fast(self, formula, constituents, cidx, pivotfct, truthpivot, assignment, level=0): if truthpivot == 0 and (isinstance(formula, Logic.Conjunction) or self.mrf.mln.logic.islit(formula)): if formula.weight == HARD: raise SatisfiabilityException('MLN is unsatisfiable given evidence due to hard constraint violation: {}'.format(str(formula))) return if truthpivot == 1 and (isinstance(formula, Logic.Disjunction) or self.mrf.mln.logic.islit(formula)): return if cidx == len(constituents): # we have reached the end of the formula constituents gf = formula.ground(self.mrf, assignment, simplify=True) if isinstance(gf, Logic.TrueFalse): return yield gf return c = constituents[cidx] for varass in c.itervargroundings(self.mrf, partial=assignment): newass = dict_union(assignment, varass) ga = c.ground(self.mrf, newass) truth = ga.truth(self.mrf.evidence) if truth is None: truthpivot_ = truthpivot elif truthpivot is None: truthpivot_ = truth else: truthpivot_ = pivotfct(truthpivot, truth) for gf in self._itergroundings_fast(formula, constituents, cidx + 1, pivotfct, truthpivot_, newass, level + 1): yield gf
def _iter_valid_variable_assignments(self, variables, assignments, eq_groundings): if not variables: yield assignments return eq_groundings = [ eq for eq in eq_groundings if not all([not self.mrf.mln.logic.isvar(a) for a in eq.args]) ] variable = variables[0] for value in self.mrf.domains[self.vardomains[variable]]: new_eq_groundings = [] goon = True for eq in eq_groundings: geq = eq.ground(None, {variable: value}, partial=True) t = geq(None) if t is not None and t != self.truth: goon = False break new_eq_groundings.append(geq) if not goon: continue for assignment in self._iter_valid_variable_assignments( variables[1:], dict_union(assignments, {variable: value}), new_eq_groundings): yield assignment
def _iter_valid_variable_assignments(self, variables, assignments, eq_groundings): if not variables: yield assignments return eq_groundings = [eq for eq in eq_groundings if not all([not self.mrf.mln.logic.isvar(a) for a in eq.args])] variable = variables[0] for value in self.mrf.domains[self.vardomains[variable]]: new_eq_groundings = [] goon = True for eq in eq_groundings: geq = eq.ground(None, {variable: value}, partial=True) t = geq(None) if t is not None and t != self.truth: goon = False break new_eq_groundings.append(geq) if not goon: continue for assignment in self._iter_valid_variable_assignments(variables[1:], dict_union(assignments, {variable: value}), new_eq_groundings): yield assignment
def _itergroundings_fast(self, formula, constituents, cidx, pivotfct, truthpivot, assignment, level=0): if truthpivot == 0 and (isinstance(formula, Logic.Conjunction) or self.mrf.mln.logic.islit(formula)): if formula.weight == HARD: raise SatisfiabilityException( 'MLN is unsatisfiable given evidence due to hard constraint violation: %s' % str(formula)) return if truthpivot == 1 and (isinstance(formula, Logic.Disjunction) or self.mrf.mln.logic.islit(formula)): return if cidx == len( constituents ): # we have reached the end of the formula constituents gf = formula.ground(self.mrf, assignment, simplify=True) if isinstance(gf, Logic.TrueFalse): return yield gf return c = constituents[cidx] for varass in c.itervargroundings(self.mrf, partial=assignment): newass = dict_union(assignment, varass) ga = c.ground(self.mrf, newass) truth = ga.truth(self.mrf.evidence) if truth is None: truthpivot_ = truthpivot elif truthpivot is None: truthpivot_ = truth else: truthpivot_ = pivotfct(truthpivot, truth) for gf in self._itergroundings_fast(formula, constituents, cidx + 1, pivotfct, truthpivot_, newass, level + 1): yield gf
def _itergroundings_fast(self, formula, constituents, cidx, assignment, variables, falsevar=None, level=0): if cidx == len(constituents): # no remaining literals to ground. return the ground formula # and statistics stat = [(varidx, validx, count) for (varidx, validx, count) in variables] yield formula.idx, stat return c = constituents[cidx] # go through all remaining groundings of the current constituent for varass in c.itervargroundings(self.mrf, partial=assignment): gnd = c.ground(self.mrf, dict_union(varass, assignment)) # check if it violates a hard constraint if formula.weight == HARD and gnd(self.mrf.evidence) < 1: raise SatisfiabilityException( 'MLN is unsatisfiable by evidence due to hard constraint violation {} (see above)' .format(global_bpll_grounding.mrf.formulas[formula.idx])) if isinstance(gnd, Logic.Equality): # if an equality grounding is false in a conjunction, we can # stop since the conjunction cannot be rendered true in any # grounding that follows if gnd.truth(None) == 0: continue for gf in self._itergroundings_fast( formula, constituents, cidx + 1, dict_union(assignment, varass), variables, falsevar, level + 1): yield gf else: var = self.mrf.variable(gnd.gndatom) world_ = list(self.mrf.evidence) stat = [] skip = False falsevar_ = falsevar vars_ = list(variables) for validx, value in var.itervalues(): var.setval(value, world_) truth = gnd(world_) if truth == 0 and value == var.evidence_value(): # if the evidence value renders the current # consituent false and there was already a false # literal in the grounding path, we can prune the # tree since no grounding will be true if falsevar is not None and falsevar != var.idx: skip = True break else: # if there was no literal false so far, we # collect statistics only for the current literal # and only if all future literals will be true # by evidence vars_ = [] falsevar_ = var.idx if truth > 0 and falsevar is None: stat.append((var.idx, validx, truth)) if falsevar is not None and falsevar == var.idx: # in case of non-mutual exclusive values take only the # values that render all literals true # example: soft-functional constraint with # !foo(?x) ^ foo(?y), x={X,Y,Z} where the evidence # foo(Z) is true # here the grounding !foo(X) ^ foo(Y) is false: # !foo(X) is true for foo(Z) and foo(Y) and # (!foo(Z) ^ !foox(X) ^ !foo(Y)) # foo(Y) is true for foo(Y) # both are only true for foo(Y) stat = set(variables).intersection(stat) skip = not bool(stat) # skip if no values remain if skip: continue for gf in self._itergroundings_fast(formula, constituents, cidx + 1, dict_union( assignment, varass), vars_ + stat, falsevar=falsevar_, level=level + 1): yield gf
def _gather_constraint_tuples(self, varindices, formula): ''' Collects and evaluates all tuples that belong to the constraint given by a formula. In case of disjunctions and conjunctions, this is fairly efficient since not all combinations need to be evaluated. Returns a dictionary mapping the constraint costs to the list of respective variable assignments. ''' logic = self.mrf.mln.logic # we can treat conjunctions and disjunctions fairly efficiently defaultProcedure = False conj = logic.islitconj(formula) disj = False if not conj: disj = logic.isclause(formula) if not varindices: return None if not conj and not disj: defaultProcedure = True if not defaultProcedure: assignment = [None] * len(varindices) children = list(formula.literals()) for gndlit in children: # constants are handled in the maxtruth/mintruth calls below if isinstance(gndlit, Logic.TrueFalse): continue # get the value of the gndlit that renders the formula true (conj) or false (disj): # for a conjunction, the literal must be true, # for a disjunction, it must be false. (gndatom, val) = (gndlit.gndatom, not gndlit.negated) if disj: val = not val val = 1 if val else 0 variable = self.variables[self.atom2var[gndatom.idx]] # in case there are multiple values of a variable that may render the formula true # we cannot apply this efficient implementation and have to fall back to the naive impl. tmp_evidence = variable.value2dict(variable.evidence_value()) evval = tmp_evidence.get(gndatom.idx) if evval is not None and evval != val: # the supposed value of the variable and the evidence value mismatch, # so the conjunction (disjunction) can never be rendered true (false) return tmp_evidence = dict_union(tmp_evidence, {gndatom.idx: val}) if variable.valuecount(tmp_evidence) > 1: defaultProcedure = True break # there is only one value remaining for _, value in variable.itervalues(tmp_evidence): varidx = self.atom2var[gndatom.idx] validx = self.val2idx[varidx][value] # if there are two different values needed to render the formula true... if assignment[varindices.index(varidx)] is not None and assignment[varindices.index(varidx)] != value: if formula.weight == HARD: if conj: # ...if it's a hard conjunction, the MLN is unsatisfiable -- e.g. foo(x) ^ !foo(x) raise SatisfiabilityException('Knowledge base is unsatisfiable due to hard constraint violation: %s' % formula) elif disj: # ...if it's a hard disjunction, it's a tautology -- e.g. foo(x) v !foo(x) continue else: # for soft constraints, unsatisfiable formulas and tautologies can be ignored return None assignment[varindices.index(varidx)] = validx if not defaultProcedure: maxtruth = formula.maxtruth(self.mrf.evidence) mintruth = formula.mintruth(self.mrf.evidence) if formula.weight == HARD and (maxtruth in Interval(']0,1[') or mintruth in Interval(']0,1[')): raise MRFValueException('No fuzzy truth values are allowed in hard constraints.') if conj: if formula.weight == HARD: cost = 0 defcost = self.wcsp.top else: cost = formula.weight * (1 - maxtruth) defcost = formula.weight else: if formula.weight == HARD: cost = self.wcsp.top defcost = 0 else: defcost = 0 cost = formula.weight * (1 - mintruth) if len(assignment) != len(varindices): raise MRFValueException('Illegal variable assignments. Variables: %s, Assignment: %s' % (varindices, assignment)) return {cost: [tuple(assignment)], defcost: 'else'} if defaultProcedure: # fallback: go through all combinations of truth assignments domains = [self.domains[v] for v in varindices] cost2assignments = defaultdict(list) # compute number of worlds to be examined and print a warning worlds = 1 for d in domains: worlds *= len(d) if worlds > 1000000: logger.warning('!!! WARNING: %d POSSIBLE WORLDS ARE GOING TO BE EVALUATED. KEEP IN SIGHT YOUR MEMORY CONSUMPTION !!!' % worlds) for c in combinations(domains): world = [0] * len(self.mrf.gndatoms) assignment = [] for varidx, value in zip(varindices, c): world = self.variables[varidx].setval(value, world) assignment.append(self.val2idx[varidx][value]) # the MRF feature imposed by this formula truth = formula(world) if truth is None: print 'POSSIBLE WORLD:' print '===============' self.mrf.print_world_vars(world) print 'GROUND FORMULA:' print '===============' formula.print_structure(world) raise Exception('Something went wrong: Truth of ground formula cannot be evaluated (see above)') if truth in Interval(']0,1[') and formula.weight == HARD: raise MRFValueException('No fuzzy truth values are allowed in hard constraints.') if formula.weight == HARD: if truth == 1: cost = 0 else: cost = self.wcsp.top else: cost = ((1 - truth) * formula.weight) cost2assignments[cost].append(tuple(assignment)) return cost2assignments assert False # unreachable
def _compute_stat_rec(self, literals, gndliterals, var_assign, formula, f_gndlit_parts=None, processed=None, isconj=False): ''' TODO: make sure that there are no equality constraints in the conjunction! ''' if len(literals) == 0: # at this point, we have a fully grounded conjunction in gndliterals # create a mapping from a partition to the ground literals in this formula # (criterion no. 1, applies to all kinds of formulas) part2gndlits = defaultdict(list) part_with_f_lit = None for gndlit in gndliterals: if isinstance(gndlit, Logic.Equality) or hasattr(self, 'qpreds') and gndlit.gndatom.predname not in self.qpreds: continue part = self.atomidx2partition[gndlit.gndatom.idx] part2gndlits[part].append(gndlit) if gndlit(self.mrf.evidence) == 0: part_with_f_lit = part # if there is a false ground literal we only need to take into account # the partition comprising this literal (criterion no. 2) # there is maximally one such partition with false literals in the conjunction # because of criterion no. 5 if isconj and part_with_f_lit is not None: gndlits = part2gndlits[part_with_f_lit] part2gndlits = {part_with_f_lit: gndlits} if not isconj: # if we don't have a conjunction, ground the formula with the given variable assignment gndformula = formula.ground(self.mrf, var_assign) for partition, gndlits in part2gndlits.iteritems(): # for each partition, select the ground atom truth assignments # in such a way that the conjunction is rendered true. There # is precisely one such assignment for each partition. (criterion 3/4) evidence = {} if isconj: for gndlit in gndlits: evidence[gndlit.gndatom.idx] = 0 if gndlit.negated else 1 for world in partition.itervalues(evidence): # update the sufficient statistics for the given formula, partition and world value worldidx = partition.valueidx(world) if isconj: truth = 1 else: # temporarily set the evidence in the MRF, compute the truth value of the # formula and remove the temp evidence with temporary_evidence(self.mrf): for atomidx, value in partition.value2dict(world).iteritems(): self.mrf[atomidx] = value truth = gndformula(self.mrf.evidence) if truth != 0: self.partition2formulas[partition.idx].add(formula.idx) self._addstat(formula.idx, partition.idx, worldidx, truth) return lit = literals[0] # ground the literal with the existing assignments gndlit = lit.ground(self.mrf, var_assign, partial=True) for assign in Logic.iter_eq_varassignments(gndlit, formula, self.mrf) if isinstance(gndlit, Logic.Equality) else gndlit.itervargroundings(self.mrf): # copy the arguments to avoid side effects # if f_gndlit_parts is None: f_gndlit_parts = set() # else: f_gndlit_parts = set(f_gndlit_parts) if processed is None: processed = [] else: processed = list(processed) # ground with the remaining free variables gndlit_ = gndlit.ground(self.mrf, assign) truth = gndlit_(self.mrf.evidence) # treatment of equality constraints if isinstance(gndlit_, Logic.Equality): if isconj: if truth == 1: self._compute_stat_rec(literals[1:], gndliterals, dict_union(var_assign, assign), formula, f_gndlit_parts, processed, isconj) else: continue else: self._compute_stat_rec(literals[1:], gndliterals + [gndlit_], dict_union(var_assign, assign), formula, f_gndlit_parts, processed, isconj) continue atom = gndlit_.gndatom if atom.idx in processed: continue # if we encounter a gnd literal that is false by the evidence # and there is already a false one in this grounding from a different # partition, we can stop the grounding process here. The gnd conjunction # will never ever be rendered true by any of this partitions values (criterion no. 5) isevidence = hasattr(self, 'qpreds') and gndlit_.gndatom.predname not in self.qpreds #assert isEvidence == False if isconj and truth == 0: if f_gndlit_parts is not None and atom not in f_gndlit_parts: continue elif isevidence: continue else: self._compute_stat_rec(literals[1:], gndliterals + [gndlit_], dict_union(var_assign, assign), formula, self.atomidx2partition[atom.idx], processed, isconj) continue elif isconj and isevidence: self._compute_stat_rec(literals[1:], gndliterals, dict_union(var_assign, assign), formula, f_gndlit_parts, processed, isconj) continue self._compute_stat_rec(literals[1:], gndliterals + [gndlit_], dict_union(var_assign, assign), formula, f_gndlit_parts, processed, isconj)
def _compute_stat_rec(self, literals, gndliterals, var_assign, formula, f_gndlit_parts=None, processed=None, isconj=False): ''' TODO: make sure that there are no equality constraints in the conjunction! ''' if len(literals) == 0: # at this point, we have a fully grounded conjunction in gndliterals # create a mapping from a partition to the ground literals in this formula # (criterion no. 1, applies to all kinds of formulas) part2gndlits = defaultdict(list) part_with_f_lit = None for gndlit in gndliterals: if isinstance(gndlit, Logic.Equality) or hasattr( self, 'qpreds' ) and gndlit.gndatom.predname not in self.qpreds: continue part = self.atomidx2partition[gndlit.gndatom.idx] part2gndlits[part].append(gndlit) if gndlit(self.mrf.evidence) == 0: part_with_f_lit = part # if there is a false ground literal we only need to take into account # the partition comprising this literal (criterion no. 2) # there is maximally one such partition with false literals in the conjunction # because of criterion no. 5 if isconj and part_with_f_lit is not None: gndlits = part2gndlits[part_with_f_lit] part2gndlits = {part_with_f_lit: gndlits} if not isconj: # if we don't have a conjunction, ground the formula with the given variable assignment # print 'formula', formula gndformula = formula.ground(self.mrf, var_assign) # print 'gndformula', gndformula # stop() for partition, gndlits in part2gndlits.iteritems(): # for each partition, select the ground atom truth assignments # in such a way that the conjunction is rendered true. There # is precisely one such assignment for each partition. (criterion 3/4) evidence = {} if isconj: for gndlit in gndlits: evidence[ gndlit.gndatom.idx] = 0 if gndlit.negated else 1 for world in partition.itervalues(evidence): # update the sufficient statistics for the given formula, partition and world value worldidx = partition.valueidx(world) if isconj: truth = 1 else: # temporarily set the evidence in the MRF, compute the truth value of the # formula and remove the temp evidence with temporary_evidence(self.mrf): for atomidx, value in partition.value2dict( world).iteritems(): self.mrf.set_evidence({atomidx: value}, erase=True) truth = gndformula(self.mrf.evidence) if truth is None: print gndformula print gndformula.print_structure( self.mrf.evidence) if truth != 0: self.partition2formulas[partition.idx].add(formula.idx) self._addstat(formula.idx, partition.idx, worldidx, truth) return lit = literals[0] # ground the literal with the existing assignments gndlit = lit.ground(self.mrf, var_assign, partial=True) for assign in Logic.iter_eq_varassignments( gndlit, formula, self.mrf) if isinstance( gndlit, Logic.Equality) else gndlit.itervargroundings( self.mrf): # copy the arguments to avoid side effects # if f_gndlit_parts is None: f_gndlit_parts = set() # else: f_gndlit_parts = set(f_gndlit_parts) if processed is None: processed = [] else: processed = list(processed) # ground with the remaining free variables gndlit_ = gndlit.ground(self.mrf, assign) truth = gndlit_(self.mrf.evidence) # treatment of equality constraints if isinstance(gndlit_, Logic.Equality): if isconj: if truth == 1: self._compute_stat_rec(literals[1:], gndliterals, dict_union(var_assign, assign), formula, f_gndlit_parts, processed, isconj) else: continue else: self._compute_stat_rec(literals[1:], gndliterals + [gndlit_], dict_union(var_assign, assign), formula, f_gndlit_parts, processed, isconj) continue atom = gndlit_.gndatom if atom.idx in processed: continue # if we encounter a gnd literal that is false by the evidence # and there is already a false one in this grounding from a different # partition, we can stop the grounding process here. The gnd conjunction # will never ever be rendered true by any of this partitions values (criterion no. 5) isevidence = hasattr( self, 'qpreds') and gndlit_.gndatom.predname not in self.qpreds #assert isEvidence == False if isconj and truth == 0: if f_gndlit_parts is not None and atom not in f_gndlit_parts: continue elif isevidence: continue else: self._compute_stat_rec(literals[1:], gndliterals + [gndlit_], dict_union(var_assign, assign), formula, self.atomidx2partition[atom.idx], processed, isconj) continue elif isconj and isevidence: self._compute_stat_rec(literals[1:], gndliterals, dict_union(var_assign, assign), formula, f_gndlit_parts, processed, isconj) continue self._compute_stat_rec(literals[1:], gndliterals + [gndlit_], dict_union(var_assign, assign), formula, f_gndlit_parts, processed, isconj)