Esempio n. 1
0
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)
Esempio n. 2
0
 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
Esempio n. 3
0
    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()
Esempio n. 4
0
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)