def reduce_set(cells: int, blocks: [int], uvars: [int], nbase: int): """Produces a CNF-represented DNF which holds all the possible combinations for a row/col :param cells: The length of the row/col :param blocks: The hints for this row/col :param uvars: The literals to use for variables in this clause :param nbase: The base index for the auxiliary variables """ combos = [] if sum(blocks) + (len(blocks) - 1) > cells: raise Exception("The passed block values exceeded the number of cells") ogcombo = [] acc = 0 for block in blocks: ogcombo.append(acc) acc += block + 1 combos.append(ogcombo) ccombo = ogcombo.copy() lookat = len(blocks) - 1 while lookat >= 0: if blocks[-1] + ccombo[-1] < cells: ccombo[lookat] = ccombo[lookat] + 1 s = ccombo[lookat] + blocks[lookat] + 1 for i in range(lookat + 1, len(blocks)): ccombo[i] = s s += blocks[i] + 1 lookat = len(blocks) - 1 combos.append(ccombo.copy()) else: lookat -= 1 s = ccombo[lookat] + blocks[lookat] + 1 for i in range(lookat + 1, len(blocks)): ccombo[i] = s s += blocks[i] + 1 cnf = CNF() for combo in combos: clause = [ -v if in_combo(i, combo, blocks) else v for i, v in zip(range(cells), uvars) ] cnf.append(clause) return cnf.negate(nbase)
def negate(self, clauses): """ **Added in SugarRush**\n Uses :meth:`pysat.formula.CNF.negate`. Adds automatic bookkeeping of literals. """ cnf = CNF(from_clauses=clauses) neg = cnf.negate(topv=self._top_id()) neg_clauses = neg.clauses self._add_lits_from(neg_clauses) #neg_force = [[-auxvar] for auxvar in neg.auxvars] #print(neg_force) #self.add(neg_force) #print(neg.auxvars) #self.add([neg.auxvars]) return neg_clauses
def encoding_Learn_SSAT(self, classifier, attributes, sensitive_attributes, auxiliary_variables, probs, filename, dependency_constraints=[], ignore_sensitive_attribute=None, verbose=True, find_maximization=True, negate_dependency_CNF=False): """ Maximization-minimization algorithm """ self.instance = "Learn" # TODO here we call twice: 1) get the sensitive feature with maximum favor, 2) get the sensitive feature with minimum favor # classifier is assumed to be a boolean formula. It is a 2D list # attributes, sensitive_attributes and auxiliary_variables is a list of variables # probs is the list of i.i.d. probabilities of attributes that are not sensitive self.num_variables = np.array( attributes + [abs(_var) for _group in sensitive_attributes for _var in _group] + auxiliary_variables).max() self.formula = "" # the sensitive attribute is exist quantified for group in sensitive_attributes: if (ignore_sensitive_attribute == None or group != ignore_sensitive_attribute): for var in group: self.formula += self._apply_exist_quantification(var) # random quantification over non-sensitive attributes if (len(attributes) > 0): for attribute in attributes: self.formula += self._apply_random_quantification( attribute, probs[attribute]) else: # dummy random variables self.formula += self._apply_random_quantification( self.num_variables, 0.5) self.num_variables += 1 if (ignore_sensitive_attribute != None): for var in ignore_sensitive_attribute: self.formula += self._apply_exist_quantification(var) # Negate the classifier if (not find_maximization): _classifier = CNF(from_clauses=classifier) _negated_classifier = _classifier.negate(topv=self.num_variables) classifier = _negated_classifier.clauses # print(auxiliary_variables, self.num_variables, _negated_classifier.auxvars) auxiliary_variables += [ i for i in range(self.num_variables + 1, _negated_classifier.nv + 1) ] # print(auxiliary_variables) self.num_variables = max(_negated_classifier.nv, self.num_variables) """ The following should not work as negation of dependency constraints does not make sense """ # # When dependency CNF is provided, we also need to negate it to learn the least favored group # if(negate_dependency_CNF and len(dependency_constraints) > 0): # """ # (not a) And b is provided where a = classifier and b = dependency constraints. # Additionally, (not a) is in CNF. # Our goal is to construct (not (a And b)) <-> (not a) Or (not b) <-> (x Or y) And (x -> not a) And (y -> not b). # In this encoding, x and y are two introduced variables. # """ # _dependency_CNF = CNF(from_clauses=dependency_constraints) # _negated_dependency_CNF = _dependency_CNF.negate(topv=self.num_variables) # # redefine # auxiliary_variables += [i for i in range(self.num_variables + 1, _negated_dependency_CNF.nv + 3)] # self.num_variables = max(_negated_dependency_CNF.nv, self.num_variables) # dependency_constraints = [clause + [-1 * (self.num_variables + 1)] for clause in _negated_dependency_CNF.clauses] # dependency_constraints += [[self.num_variables + 1, self.num_variables + 2]] # classifier = [clause + [-1 * (self.num_variables + 2)] for clause in classifier] # self.num_variables += 2 # two additional variables are introduced # for each sensitive-attribute (one hot vector), at least one group must be True self._formula_for_equal_one_constraints = "" for _group in sensitive_attributes: if (len(_group) > 1): # when categorical attribute is not Boolean equal_one_constraint = PBEnc.equals( lits=_group, weights=[1 for _ in _group], bound=1, top_id=self.num_variables) for clause in equal_one_constraint.clauses: self._formula_for_equal_one_constraints += self._construct_clause( clause) auxiliary_variables += [ i for i in range(self.num_variables + 1, equal_one_constraint.nv + 1) ] self.num_variables = max(equal_one_constraint.nv, self.num_variables) # other variables (auxiliary) are exist quantified for var in auxiliary_variables: self.formula += self._apply_exist_quantification(var) # clauses for the classifier for clause in classifier: self.formula += self._construct_clause(clause) # clauses for the dependency constraints for clause in dependency_constraints: self.formula += self._construct_clause(clause) self.formula += self._formula_for_equal_one_constraints # store in a file self.formula = self._construct_header() + self.formula[:-1] file = open(filename, "w") file.write(self.formula) file.close()
class NaiveQBF(): """ This class work as a skeleton in order to define qbf solver based on SAT. """ def __init__(self): """ Constructor of the class. Always create an empty solver """ self.formula = CNF() self.quantifiers = [] self.propagate = [] def append_formula(self, formula): """ Append a formula (seen as a list of clauses) to the solver. """ for clause in formula: self.formula.append(clause) def propagate_literal(self, variable): """ Add a literal to be propagated. """ self.propagate.append(variable) if variable in self.quantifiers: self.quantifiers.remove(variable) def add_clause(self, clause): """ Add a clause to the formula """ self.formula.append(clause) def add_quantifiers(self, quantifier): """ Add a quantifier to the quantifiers list. Quantifiers are represented as integers. A positive quantifiers is interpreted as an exists, as well as a negative quantifiers is interpreted as a for all. """ if quantifier in self.quantifiers: raise ValueError('A variable can not be quantified twice') self.quantifiers.append(quantifier) def negate(self): """ negate the formula along with the quantifiers list. """ self.formula = self.formula.negate() self.quantifiers = [-quant for quant in self.quantifiers] def __solve(quantifiers, formula, propagate): """ Private method that implements the recursión that solve the problem. """ if quantifiers: new_quant = copy.deepcopy(quantifiers) quantifier = new_quant.pop() if quantifier < 0: return NaiveQBF.__solve( new_quant, formula, propagate + [quantifier]) and NaiveQBF.__solve( new_quant, formula, propagate + [-quantifier]) if NaiveQBF.__solve(new_quant, formula, propagate + [quantifier]): return True return NaiveQBF.__solve(new_quant, formula, propagate + [quantifier]) else: solver = Solver(name='cd') solver.append_formula(formula) print(formula.clauses) print(propagate) return solver.solve(assumptions=propagate) def solve(self): """ Solved the associated formula. """ return NaiveQBF.__solve(self.quantifiers, self.formula, self.propagate)