def optimize(self, formula, model): """ Try to optimize the solution with a MaxSAT solver. """ MaxSAT = RC2Stratified if self.options.weighted else RC2 formula_new = WCNF() formula_new.extend(formula.hard) # hardening the soft clauses based on the model for j in range(1, self.nof_terms + 1): formula_new.append([model[self.unused(j)]]) for lb in self.labels: for q in self.samps[lb]: formula_new.append([model[self.miss(q + 1)]]) for j in range(1, self.nof_terms + 1): for r in range(1, self.nof_feats + 1): formula_new.append([-self.dvar1(j, r)], weight=1) formula_new.append([-self.dvar0(j, r)], weight=1) with MaxSAT( formula_new, solver=self.options.solver, adapt=self.options.am1, exhaust=self.options.exhaust, minz=self.options.minz, trim=self.options.trim, ) as rc2: model = rc2.compute() return model
def get_MCS(KBa_s, KBa_h, q, seed, clauses_dict): # Compute minimal hitting set wcnf = WCNF() for c in KBa_s: if c not in seed: # don't add to the soft clauses those in the seed wcnf.append(c, weight=1) for c in KBa_h: wcnf.append(c) for c in seed: # add clauses in the seed as hard if any(isinstance(el, list) for el in c): for cs in c: wcnf.append(cs) else: wcnf.append(c) wcnf.extend(q.negate().clauses) lbx = LBX(wcnf, use_cld=True, solver_name='g3') # Compute mcs and return the clauses indexes mcs = lbx.compute() # Current mcs is computed w.r.t. the soft clauses excluding the seed. Below we find the corresponding indexes of these clauses in KBa_s temp_cl_lookup = create_clauses_lookup(wcnf.soft) clauses = get_clauses_from_index(mcs, temp_cl_lookup) mcs = get_index_from_clauses(clauses, clauses_dict) return mcs
def MUSExtraction(self, C): wcnf = WCNF() wcnf.extend(self.cnf.clauses) wcnf.extend([[l] for l in C], [1] * len(C)) with MUSX(wcnf, verbosity=0) as musx: mus = musx.compute() # gives back positions of the clauses !! return set(C[i - 1] for i in mus)
def __createWncf(self, initialisationFormulas, distanceFormula, artefactForMinimization): ''' This method creates the wncf formulas with the weighted variables depending on the distance and artefact. :param initialisationFormulas: @see __artefactsInitialisation :param distanceFormula: @see __compute_distance :param artefactForMinimization: MULTI_ALIGNMENT or ANTI_ALIGNMENT or EXACT_ALIGNMENT :return: ''' formulas = initialisationFormulas + distanceFormula + self.__sup_to_minimize(artefactForMinimization) full_formula = And([], [], formulas) cnf = full_formula.operatorToCnf(self.__vars.iterator) wcnf = WCNF() wcnf.extend(cnf) wcnf = self.__createWeights(wcnf,artefactForMinimization) self.__formula_time = time.time() return wcnf
def get_MUS(KB, e, q): # Compute minimal unsatisfiable set wcnf2 = WCNF() for k in e: if any(isinstance(el, list) for el in k): for ks in k: wcnf2.append(ks, weight=1) else: wcnf2.append(k, weight=1) if KB: for c in KB.clauses: wcnf2.append(c, weight=1) wcnf2.extend((q.negate().clauses)) mmusx = MUSX(wcnf2, verbosity=0) mus = mmusx.compute() return [list(wcnf2.soft[m - 1]) for m in mus]
def grow_maxsat(self, f, A, HS): remaining, weights = None, None wcnf = WCNF() # HARD clauses wcnf.extend(self.cnf.clauses) wcnf.extend([[l] for l in HS]) # SOFT clauses to grow if self.params.interpretation is Interpretation.INITIAL: remaining = list(self.I0 - HS) elif self.params.interpretation is Interpretation.ACTUAL: remaining = list(self.I - HS) elif self.params.interpretation is Interpretation.FULL: remaining = list(self.Iend - HS) elif self.params.interpretation is Interpretation.FINAL: remaining = list(A - HS) remaining_clauses = [[l] for l in remaining] if self.params.maxsat_weighing is Weighing.POSITIVE: weights = [f(l) for l in remaining] elif self.params.maxsat_weighing is Weighing.INVERSE: max_weight = max(f(l) for l in remaining) + 1 weights = [max_weight - f(l) for l in remaining] elif self.params.maxsat_weighing is Weighing.UNIFORM: weights = [1] * len(remaining) # cost is associated for assigning a truth value to literal not in # contrary to A. wcnf.extend(clauses=remaining_clauses, weights=weights) # solve the MAXSAT problem with RC2(wcnf) as s: if self.params.maxsat_polarity and hasattr(s, 'oracle'): s.oracle.set_phases(literals=list(self.Iend)) t_model = s.compute() return set(t_model)
def encode(self, label, nof_terms=1): """ Encode the problem of computing a DS of size nof_terms. """ self.nof_terms = nof_terms enc = WCNF() # all the hard clauses # # constraint 6 (relaxed with the unused variable) for j in range(1, self.nof_terms + 1): for r in range(1, self.nof_feats + 1): enc.append([-self.unused(j), self.svar(j, r)]) enc.append( [self.unused(j)] + [-self.svar(j, r) for r in range(1, self.nof_feats + 1)]) # sort unused rules for j in range(1, self.nof_terms): enc.append([-self.unused(j), self.unused(j + 1)]) # constraint 7 for j in range(1, self.nof_terms + 1): for r in range(1, self.nof_feats + 1): d0 = self.dvar0(j, r) p0 = [-self.svar(j, r), self.lvar(j, r)] enc.append([d0, -p0[0], -p0[1]]) enc.append([-d0, p0[0]]) enc.append([-d0, p0[1]]) d1 = self.dvar1(j, r) p1 = [-self.svar(j, r), -self.lvar(j, r)] enc.append([d1, -p1[0], -p1[1]]) enc.append([-d1, p1[0]]) enc.append([-d1, p1[1]]) # constraint 8 if len(self.labels) == 1: # distinguish one class from all the others other_labels = set(self.samps.keys()) else: # distinguish the classes under question only other_labels = set(self.labels) other_labels.remove(label) other_labels = sorted(other_labels) for j in range(1, self.nof_terms + 1): for lb in other_labels: for q in self.samps[lb]: cl = [self.unused(j), self.miss(q + 1)] # the clause is relaxed shift = 0 for r in range(1, self.nof_feats + 1): if r - 1 in self.data.vmiss[q]: # this feature is missing in q'th sample cl.append(-self.svar(j, r)) shift += 1 elif self.data.samps[q][r - 1 - shift] > 0: cl.append(self.dvar1(j, r)) else: cl.append(self.dvar0(j, r)) enc.append(cl) # constraint 9 for j in range(1, self.nof_terms + 1): for q in self.samps[label]: cr = self.crvar(j, q + 1) cl = [self.unused(j)] shift = 0 for r in range(1, self.nof_feats + 1): if r - 1 in self.data.vmiss[q]: # this feature is missing in q'th sample cl.append(-self.svar(j, r)) shift += 1 elif self.data.samps[q][r - 1 - shift] > 0: cl.append(self.dvar1(j, r)) else: cl.append(self.dvar0(j, r)) enc.append([cr] + cl) for l in cl: enc.append([-cr, -l]) # constraint 10 for q in self.samps[label]: enc.append( [self.miss(q + 1)] + [self.crvar(j, q + 1) for j in range(1, self.nof_terms + 1)]) # at most one value can be chosen for a feature for feats in six.itervalues(self.ffmap.dir): if len(feats) > 2: for j in range(1, self.nof_terms + 1): lits = [self.dvar0(j, r + 1) for r in feats] # atmost1 can be true onev = CardEnc.atmost(lits, top_id=enc.nv, encoding=self.options.enc) enc.extend(onev.clauses) # soft clauses # minimizing the number of literals used for j in range(1, self.nof_terms + 1): enc.append([self.unused(j)], weight=self.lambda_) # minimizing the number of missclassifications for lb in self.labels: for q in self.samps[lb]: enc.append([-self.miss(q + 1)], weight=self.data.wghts[q]) # there should be at least one rule for this class enc.append([-self.unused(1)]) # saving comments for j in range(1, self.nof_terms + 1): for r in range(1, self.nof_feats + 1): enc.comments.append('c s({0}, {1}) => v{2}'.format( j, r, self.svar(j, r))) enc.comments.append('c l({0}, {1}) => v{2}'.format( j, r, self.lvar(j, r))) enc.comments.append('c d0({0}, {1}) => v{2}'.format( j, r, self.dvar0(j, r))) enc.comments.append('c d1({0}, {1}) => v{2}'.format( j, r, self.dvar1(j, r))) for q in range(len(self.data.samps)): enc.comments.append('c cr({0}, {1}) => v{2}'.format( j, q + 1, self.crvar(j, q + 1))) for j in range(1, self.nof_terms + 1): enc.comments.append('c u({0}) => v{1}'.format(j, self.unused(j))) for lb in self.labels: for q in self.samps[lb]: enc.comments.append('c m({0}) => v{1}'.format( q + 1, self.miss(q + 1))) for n, f in zip(self.data.names[:-1], self.data.feats[:-1]): for v in f: if self.data.fvmap.dir[(n, v)] > 0: enc.comments.append('c {0} = {1} => positive'.format(n, v)) else: enc.comments.append('c {0} = {1} => negative'.format(n, v)) return enc
def encode(self, label, nof_lits=1): """ Encode the problem of computing a DS of size nof_lits. """ self.nof_lits = nof_lits self.nof_samps = len(self.data.samps) self.nof_labls = len(self.labels) if len(self.labels) == 1: # distinguish one class from all the others other_labels = set(self.samps.keys()) else: # distinguish the classes under question only other_labels = set(self.labels) other_labels.remove(label) other_labels = sorted(other_labels) for j in range(1, self.nof_lits + 1): for r in range(1, self.nof_feats + 2): self.feat(j, r) for j in range(1, self.nof_lits + 1): self.sign(j) for j in range(1, self.nof_lits + 1): self.leaf(j) enc = WCNF() # all the hard clauses # # exactly one feature per node (including class/label) for j in range(1, self.nof_lits + 1): feats = [self.feat(j, r) for r in range(1, self.nof_feats + 2)] + [self.unused(j)] one = CardEnc.equals(lits=feats, vpool=self.idpool, encoding=self.options.enc) enc.extend(one) # at most one class/label per node for j in range(1, self.nof_lits + 1): labels = [self.label(j, z) for z in self.labels] am1 = CardEnc.atmost(lits=labels, vpool=self.idpool, encoding=self.options.enc) enc.extend(am1) # the model is split, # i.e. we currently target only rules for this concrete class enc.append([self.label(j, label)]) # propagation of unused literals for j in range(1, self.nof_lits): enc.append([-self.unused(j), self.unused(j + 1)]) # leaf constraints # this disallows empty (default) rules and thus is disabled # enc.append([-self.leaf(1)]) # first node can't be a leaf # last leaf for j in range(1, self.nof_lits): enc.append([-self.unused(j + 1), self.unused(j), self.leaf(j)]) enc.append([self.leaf(self.nof_lits), self.unused(self.nof_lits)]) # everything is reachable at node 1 for lb in self.labels: for i in self.samps[lb]: enc.append([self.reached(1, i + 1)]) # reachability propagation for j in range(1, self.nof_lits): for lb in self.labels: for i in self.samps[lb]: aij = self.agree(j, i + 1) cl, shift = [], 0 for r in range(1, self.nof_feats + 1): if r - 1 in self.data.vmiss[i]: # this feature is missing in i'th sample shift += 1 else: # a = self.agree(j, i + 1, r) # node j agrees with sample i on feature r f = self.feat(j, r) # feature r decided in node j s = self.sign(j) # polarity of node j if self.data.samps[i][r - 1 - shift] > 0: a = self.sets1(j, r) if a > enc.nv: enc.extend([[-a, f], [-a, s], [a, -f, -s]]) else: a = self.sets0(j, r) if a > enc.nv: enc.extend([[-a, f], [-a, -s], [a, -f, s]]) cl.append(a) enc.append([-aij] + cl) for l in cl: enc.append([aij, -l]) cur = self.reached(j, i + 1) # node j is reachable for sample i new = self.reached(j + 1, i + 1) # node j + 1 reachable for sample i enc.append([-new, self.leaf(j), cur]) enc.append([-new, self.leaf(j), aij]) enc.append([ new, -self.leaf(j)]) enc.append([ new, -cur, -aij]) # correctness of leafs for j in range(1, self.nof_lits + 1): for lb in self.labels: for i in self.samps[lb]: enc.append([-self.leaf(j), -self.reached(j, i + 1), self.label(j, lb), self.miss(i + 1)]) # coverage constraints for i in self.samps[label]: cl = [self.miss(i + 1)] # here the clause is relaxed for j in range(1, self.nof_lits + 1): cvar = self.covered(j, i + 1) enc.append([-cvar, self.leaf(j)]) enc.append([-cvar, self.reached(j, i + 1)]) enc.append([cvar, -self.leaf(j), -self.reached(j, i + 1)]) cl.append(cvar) enc.append(cl) # soft clauses # minimizing the number of literals used for j in range(1, self.nof_lits + 1): # enc.append([self.unused(j)], weight=self.lambdas[label]) enc.append([self.unused(j)], weight=self.lambda_) # minimizing the number of missclassifications for lb in self.labels: for i in self.samps[lb]: enc.append([-self.miss(i + 1)], weight=self.data.wghts[i]) # there should be at least two literals in the decision set # since u_j variables are sorted, we know that they are u1 and u2 # enc.extend([[-self.unused(1)], [-self.unused(2)]]) # the previous constraints disallowed empty (default) rules # and so this one looks better enc.extend([[-self.unused(1)]]) # symmetry breaking if self.options.bsymm: self.add_bsymm(enc) for v, o in self.idpool.id2obj.items(): enc.comments.append('c {0} <=> {1}'.format(o, v)) return enc