Ejemplo n.º 1
0
 def _one_node_maps_to_alo_state_classic(self,
                                         solver: Solver,
                                         size: int,
                                         new_node_from: int = 0) -> None:
     for i in range(new_node_from, self._apta.size):
         solver.add_clause(
             tuple(self._vars.var('x', i, j) for j in range(size)))
Ejemplo n.º 2
0
    def dominating_subset(self, k=1):
        """
        Check if there exists a vertex cover of, at most, k-vertices.
        Accepts as params:
        - n_color: number of color to check
        - verbose: whether or not print the process
        """
        if not self.edges():
            return []

        logging.info('\nCodifying SAT Solver...')
        solver = Solver(name='cd')
        vpool = IDPool()
        vertices_ids = [vpool.id(vertex) for vertex in self.vertices()]

        logging.info(' -> Codifying: Every vertex must be accessible')
        for vertex in self.vertices():
            solver.add_clause([vpool.id(vertex)] + [
                vpool.id(adjacent_vertex) for adjacent_vertex in self[vertex]
            ])

        logging.info(' -> Codifying: At most', k,
                     'vertices should be selected')

        cnf = CardEnc.atmost(lits=vertices_ids, bound=k, vpool=vpool)
        solver.append_formula(cnf)

        logging.info('Running SAT Solver...')
        return solver.solve()
Ejemplo n.º 3
0
    def Solver(self, clauses=None):
        transform_clauses = []
        if clauses == None:
            clauses = np.array(self.clauses)
        for i in range(len(clauses)):
            line = []
            for item in clauses[i]:
                if type(item) != int: line.append(item.item())
                else: line.append(item)
            transform_clauses.append(line)
        s = Solver(name='cadical')
        for item in transform_clauses:
            s.add_clause(item)
        self.result_tag = s.solve()

        result_list = s.get_model()
        if self.result_tag == True:
            for index in result_list:
                if index > 0:
                    sindex = int(np.abs(index).item() - 1)
                    self.result.append(self.small_search_space[sindex])
                else:
                    continue
        else:
            print('gg')
            return

        if len(self.result) != self.tree_count:
            self.result_tag = False
            print('gg')
            return
        else:
            return
Ejemplo n.º 4
0
 def _state_has_at_least_one_parent(self,
                                    solver: Solver,
                                    size: int,
                                    old_size: int = 0) -> None:
     for child in range(max(1, old_size), size):
         solver.add_clause(
             tuple(
                 self._vars.var('p', child, parent)
                 for parent in range(child)))
Ejemplo n.º 5
0
def quantidade_verdade(clauses, s):
    cont = 0
    for clause in clauses:
        d = Solver()
        d.add_clause(clause)
        if (d.solve(s)):
            cont = cont + 1
        d.delete()
    return cont
Ejemplo n.º 6
0
 def count_sat_clauses(r_clauses, r_sol_f):
     sat_count = 0
     for clause in r_clauses:
         s = Solver(name='cd')
         s.add_clause(clause)
         r = s.solve(assumptions=r_sol_f)
         sat_count = sat_count + 1 if r else sat_count
         s.delete()
     return sat_count / len(r_clauses) * 1.0
Ejemplo n.º 7
0
 def _state_has_at_most_one_parent(self,
                                   solver: Solver,
                                   size: int,
                                   old_size: int = 0) -> None:
     for child in range(old_size, size):
         for parent in range(child):
             for other_parent in range(parent):
                 solver.add_clause(
                     (-self._vars.var('p', child, parent),
                      -self._vars.var('p', child, other_parent)))
Ejemplo n.º 8
0
 def _one_node_maps_to_at_most_one_state(self,
                                         solver: Solver,
                                         size: int,
                                         new_node_from: int = 0,
                                         old_size: int = 0) -> None:
     for v in range(new_node_from, self._apta.size):
         for i in range(old_size, size):
             for j in range(0, i):
                 solver.add_clause((-self._vars.var('x', v, i),
                                    -self._vars.var('x', v, j)))
Ejemplo n.º 9
0
 def _inconsistency_graph_constraints(self,
                                      solver: Solver,
                                      size: int,
                                      new_node_from: int = 0,
                                      old_size: int = 0) -> None:
     for node1 in range(self._ig.size):
         for node2 in self._ig.edges[node1]:
             if node1 >= new_node_from or node2 >= new_node_from:
                 for s in range(old_size, size):
                     solver.add_clause((-self._vars.var('x', node1, s),
                                        -self._vars.var('x', node2, s)))
Ejemplo n.º 10
0
 def _one_node_maps_to_alo_state_switch(self,
                                        solver: Solver,
                                        size: int,
                                        new_node_from: int = 0,
                                        old_size: int = 0) -> None:
     for i in range(new_node_from, self._apta.size):
         solver.add_clause(
             tuple(self._vars.var('x', i, j) for j in range(size)) +
             (self._vars.var('sw_x', size, i), ))
     if old_size > 0:
         for v in range(self._apta.size):
             solver.add_clause((self._vars.var('sw_x', old_size, v), ))
Ejemplo n.º 11
0
def sat(KB_clauses, seed):
    # Check if a formula is satisfiable
    s = Solver(name='g4')
    for k in KB_clauses:
        s.add_clause(k)

    for k in seed:
        s.add_clause(k)

    if s.solve():
        return True
    else:
        return False
Ejemplo n.º 12
0
def skeptical_entailment(KB, seed, q):
    # Check if KB entails a query

    s = Solver(name='g4')
    for k in KB:
        s.add_clause(k)
    for k in seed:
        s.add_clause(k)
    # add negation of query
    s.append_formula(q.negate().clauses)
    if s.solve() == False:
        return True
    else:
        return False
Ejemplo n.º 13
0
 def _dfa_is_deterministic(self,
                           solver: Solver,
                           size: int,
                           old_size: int = 0) -> None:
     for l_id in range(self._alphabet_size):
         for i in range(old_size):
             for j in range(old_size, size):
                 for k in range(j):
                     solver.add_clause((-self._vars.var('y', i, l_id, j),
                                        -self._vars.var('y', i, l_id, k)))
         for i in range(old_size, size):
             for j in range(size):
                 for k in range(j):
                     solver.add_clause((-self._vars.var('y', i, l_id, j),
                                        -self._vars.var('y', i, l_id, k)))
Ejemplo n.º 14
0
    def _dfa_is_complete_switch(self,
                                solver: Solver,
                                size: int,
                                old_size: int = 0) -> None:
        for i in range(size):
            for l_id in range(self._alphabet_size):
                solver.add_clause(
                    tuple(
                        self._vars.var('y', i, l_id, j) for j in range(size)) +
                    (self._vars.var('sw_y', size, i, l_id), ))

        if old_size > 0:
            for from_ in range(old_size):
                for l_id in range(self._alphabet_size):
                    solver.add_clause((self._vars.var('sw_y', old_size, from_,
                                                      l_id), ))
Ejemplo n.º 15
0
def solve_problem(input):

    """initialize variables"""
    num_police = input["police"]
    num_medics = input["medics"]
    days = len(input["observations"])
    colums_size = len(input["observations"][0][0])
    row_size = len(input["observations"][0])
    queries = input["queries"]
    board_size = row_size * colums_size

    """create the database"""
    db = []
    create_database(days, row_size, colums_size, db)

    """create the data_knowlede"""
    data_knowlede = []

    initial_states(board_size, days, row_size, colums_size, input, db, data_knowlede)
    curr_h_next_s_then_neighbor_s(board_size, days, row_size, colums_size, input, db, data_knowlede)
    curr_h_neibor_s_then_next_s(board_size, days, row_size, colums_size, input, db, data_knowlede)
    fix_mistakes(board_size, days, row_size, colums_size, input, db, data_knowlede)
    illness_expires(board_size, days, row_size, colums_size, db, data_knowlede)
    quarentine_expires(board_size, days, row_size, colums_size, db, data_knowlede)
    U_and_I_stay_this_way(board_size, days, row_size, colums_size, db, data_knowlede)
    previues_to_U_and_I(board_size, days, row_size, colums_size, db, data_knowlede)
    next_h(board_size, days, row_size, colums_size, db, data_knowlede)
    previues_h(board_size, days, row_size, colums_size, db, data_knowlede)
    previues_s(board_size, days, row_size, colums_size, db, data_knowlede)
    previues_q(board_size, days, row_size, colums_size, db, data_knowlede)
    next_q(board_size, days, row_size, colums_size, db, data_knowlede)
    next_s(board_size, days, row_size, colums_size, db, data_knowlede)

    """"solve the problem"""
    s = Solver()
    dict={}

    for i in data_knowlede:
        s.add_clause(i)

    sulotion_exist = s.solve()
    sulotion = s.get_model()

    set_dictionary_according_to_queries(days, row_size, colums_size, queries, db, sulotion, sulotion_exist, s, dict)

    return(dict)
Ejemplo n.º 16
0
    def coloring(self, n_color):
        """
        Returns whether or not there exists a vertex coloring
        of, at most, n_color colors.

        Accepts one param:
        - n_color: number of color to check

        Might raise ValueError exception.
        """
        if n_color < 0:
            raise ValueError('Number of colors must be positive integer')

        if n_color == 0:
            return not bool(self.vertices())

        logging.info('\nCodifying SAT Solver...')
        solver = Solver(name='cd')
        vpool = IDPool()

        logging.info(
            ' -> Codifying: Every vertex must have a color, and only one')
        for vertex in self.vertices():
            cnf = CardEnc.equals(lits=[
                vpool.id('{}color{}'.format(vertex, color))
                for color in range(n_color)
            ],
                                 vpool=vpool,
                                 encoding=0)

            solver.append_formula(cnf)

        logging.info(
            ' -> Codifying: No two neighbours can have the same color')
        for vertex in self.vertices():
            for neighbour in self[vertex]:
                for color in range(n_color):
                    solver.add_clause([
                        -vpool.id('{}color{}'.format(vertex, color)),
                        -vpool.id('{}color{}'.format(neighbour, color))
                    ])

        logging.info('Running SAT Solver...')
        return solver.solve()
Ejemplo n.º 17
0
def check_sat(observations, num_medics, num_police):
    s = Solver(name='g4', with_proof=True)
    assumptions = create_assumptions(observations)
    h_squares, s_squares, unknown_squares = {}, {}, {}
    for i in range(len(observations[0])):
        for j in range(len(observations[0][0])):
            for turn in range(len(observations)):
                status = observations[turn][i][j]
                if turn == 0 and status in ["I", "Q"]:
                    return False
                square_num = calc_square_num(i, j, observations)
                clauses = solve_square(square_num, status, turn, observations,
                                       assumptions)
                if clauses:
                    for c in clauses:
                        s.add_clause(c)
                if status == 'H':
                    if turn not in h_squares.keys():
                        h_squares[turn] = [(i, j)]
                    else:
                        h_squares[turn].append((i, j))
                elif status == 'S':
                    if turn not in s_squares.keys():
                        s_squares[turn] = [(i, j)]
                    else:
                        s_squares[turn].append((i, j))
                elif status == "?":
                    if turn not in unknown_squares.keys():
                        unknown_squares[turn] = [(i, j)]
                    else:
                        unknown_squares[turn].append((i, j))

    assign_workers(h_squares, unknown_squares, "I", num_medics, assumptions, s,
                   observations)
    assign_workers(s_squares, unknown_squares, "Q", num_police, assumptions, s,
                   observations)
    final_assumptions = []
    for li in assumptions:
        final_assumptions += li
    final_assumptions = list(dict.fromkeys(final_assumptions))
    ans = s.solve(assumptions=final_assumptions)
    return ans
Ejemplo n.º 18
0
 def _one_node_maps_to_alo_state_chain(self,
                                       solver: Solver,
                                       size: int,
                                       new_node_from: int = 0,
                                       old_size: int = 0) -> None:
     if old_size == 0:
         for i in range(new_node_from, self._apta.size):
             solver.add_clause(
                 tuple(
                     self._vars.var('x', i, j)
                     for j in range(old_size, size)) +
                 (-self._vars.var('alo_x', size, i), ))
     else:
         for i in range(new_node_from, self._apta.size):
             solver.add_clause(
                 tuple(
                     self._vars.var('x', i, j)
                     for j in range(old_size, size)) +
                 (-self._vars.var('alo_x', size, i),
                  self._vars.var('alo_x', old_size, i)))
Ejemplo n.º 19
0
 def _dfa_is_complete_chain(self,
                            solver: Solver,
                            size: int,
                            old_size: int = 0) -> None:
     if old_size == 0:
         for l_id in range(self._alphabet_size):
             for i in range(old_size):
                 solver.add_clause(
                     tuple(
                         self._vars.var('y', i, l_id, j)
                         for j in range(old_size, size)) +
                     (-self._vars.var('alo_y', size, i, l_id), ))
     else:
         for l_id in range(self._alphabet_size):
             for i in range(old_size):
                 solver.add_clause(
                     tuple(
                         self._vars.var('y', i, l_id, j)
                         for j in range(old_size, size)) +
                     (-self._vars.var('alo_y', size, i, l_id),
                      self._vars.var('alo_y', old_size, i, l_id)))
     for l_id in range(self._alphabet_size):
         for i in range(old_size, size):
             solver.add_clause(
                 tuple(
                     self._vars.var('y', i, l_id, j) for j in range(size)) +
                 (-self._vars.var('alo_y', size, i, l_id), ))
Ejemplo n.º 20
0
def test_pysat():
    s = Solver()
    s.add_clause([-1, 2, -3])  # 3 is a selector for this clause
    s.add_clause([-1, -2, -4])  # 4 is a selector for this clause

    r = s.solve()
    print("r:", r)
    print(s.get_model())

    s.add_clause([1, -5])  # 5 is a selector for this clause
    r = s.solve(assumptions=[
        3, 4, 5
    ])  # you need to call the solver assuming these literals are true
    print("r:", r)

    print(s.get_core())  # this should report [3, 4, 5] as a core
Ejemplo n.º 21
0
class MCSls(object):
    """
        Algorithm BLS for computing MCSes, augmented with "clause :math:`D`"
        calls. Given an unsatisfiable partial CNF formula, i.e.  formula in the
        :class:`.WCNF` format, this class can be used to compute a given number
        of MCSes of the formula. The implementation follows the description of
        the basic linear search (BLS) algorithm description in [1]_. It can use
        any SAT solver available in PySAT. Additionally, the "clause :math:`D`"
        heuristic can be used when enumerating MCSes.

        The default SAT solver to use is ``m22`` (see :class:`.SolverNames`).
        The "clause :math:`D`" heuristic is disabled by default, i.e.
        ``use_cld`` is set to ``False``. Internal SAT solver's timer is also
        disabled by default, i.e. ``use_timer`` is ``False``.

        :param formula: unsatisfiable partial CNF formula
        :param use_cld: whether or not to use "clause :math:`D`"
        :param solver_name: SAT oracle name
        :param use_timer: whether or not to use SAT solver's timer

        :type formula: :class:`.WCNF`
        :type use_cld: bool
        :type solver_name: str
        :type use_timer: bool
    """
    def __init__(self,
                 formula,
                 use_cld=False,
                 solver_name='m22',
                 use_timer=False):
        """
            Constructor.
        """

        # bootstrapping the solver with hard clauses
        self.oracle = Solver(name=solver_name,
                             bootstrap_with=formula.hard,
                             use_timer=use_timer)
        self.solver = solver_name

        # adding native cardinality constraints (if any) as hard clauses
        # this can be done only if the Minicard solver is in use
        if isinstance(formula, WCNFPlus) and formula.atms:
            assert solver_name in SolverNames.minicard, \
                    'Only Minicard supports native cardinality constraints. Make sure you use the right type of formula.'

            for atm in formula.atms:
                self.oracle.add_atmost(*atm)

        self.topv = formula.nv  # top variable id
        self.sels = []
        self.ucld = use_cld
        self.smap = {}

        # mappings between internal and external variables
        VariableMap = collections.namedtuple('VariableMap', ['e2i', 'i2e'])
        self.vmap = VariableMap(e2i={}, i2e={})

        # at this point internal and external variables are the same
        for v in range(1, formula.nv + 1):
            self.vmap.e2i[v] = v
            self.vmap.i2e[v] = v

        for cl in formula.soft:
            new_cl = cl[:]
            if len(cl) > 1 or cl[0] < 0:
                self.topv += 1
                sel = self.topv

                new_cl.append(-sel)  # creating a new selector
                self.oracle.add_clause(new_cl)
            else:
                sel = cl[0]

            self.sels.append(sel)
            self.smap[sel] = len(self.sels)

    def __del__(self):
        """
            Destructor.
        """

        self.delete()

    def __enter__(self):
        """
            'with' constructor.
        """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
            'with' destructor.
        """

        self.delete()

    def delete(self):
        """
            Explicit destructor of the internal SAT oracle.
        """

        if self.oracle:
            self.oracle.delete()
            self.oracle = None

    def add_clause(self, clause, soft=False):
        """
            The method for adding a new hard of soft clause to the problem
            formula. Although the input formula is to be specified as an
            argument of the constructor of :class:`MCSls`, adding clauses may
            be helpful when *enumerating* MCSes of the formula. This way, the
            clauses are added incrementally, i.e. *on the fly*.

            The clause to add can be any iterable over integer literals. The
            additional Boolean parameter ``soft`` can be set to ``True``
            meaning the the clause being added is soft (note that parameter
            ``soft`` is set to ``False`` by default).

            :param clause: a clause to add
            :param soft: whether or not the clause is soft

            :type clause: iterable(int)
            :type soft: bool
        """

        # first, map external literals to internal literals
        # introduce new variables if necessary
        cl = list(
            map(
                lambda l: self._map_extlit(l), clause if not len(clause) == 2
                or not type(clause[0]) == list else clause[0]))

        if not soft:
            if not len(clause) == 2 or not type(clause[0]) == list:
                # the clause is hard, and so we simply add it to the SAT oracle
                self.oracle.add_clause(cl)
            else:
                # this should be a native cardinality constraint,
                # which can be used only together with Minicard
                assert self.solver in SolverNames.minicard, \
                        'Only Minicard supports native cardinality constraints.'

                self.oracle.add_atmost(cl, clause[1])
        else:
            # soft clauses should be augmented with a selector
            sel = cl[0]
            if len(cl) > 1 or cl[0] < 0:
                self.topv += 1
                sel = self.topv

                self.oracle.add_clause(cl + [-sel])

            self.sels.append(sel)
            self.smap[sel] = len(self.sels)

    def compute(self):
        """
            Compute and return one solution. This method checks whether the
            hard part of the formula is satisfiable, i.e. an MCS can be
            extracted. If the formula is satisfiable, the model computed by the
            SAT call is used as an *over-approximation* of the MCS in the
            method :func:`_compute` invoked here, which implements the BLS
            algorithm augmented with CLD oracle calls.

            An MCS is reported as a list of integers, each representing a soft
            clause index (the smallest index is ``1``).

            :rtype: list(int)
        """

        self.setd = []
        self.solution = None
        self.bb_assumps = []  # backbone assumptions
        self.ss_assumps = []  # satisfied soft clause assumptions

        if self.oracle.solve():
            # hard part is satisfiable => there is a solution
            self._overapprox()
            self._compute()

            self.solution = [self.smap[-l] for l in self.bb_assumps]

        return self.solution

    def enumerate(self):
        """
            This method iterates through MCSes enumerating them until the
            formula has no more MCSes. The method iteratively invokes
            :func:`compute`. Note that the method does not block the MCSes
            computed - this should be explicitly done by a user.
        """

        done = False
        while not done:
            mcs = self.compute()

            if mcs != None:
                yield mcs
            else:
                done = True

    def block(self, mcs):
        """
            Block a (previously computed) MCS. The MCS should be given as an
            iterable of integers. Note that this method is not automatically
            invoked from :func:`enumerate` because a user may want to block
            some of the MCSes conditionally depending on the needs. For
            example, one may want to compute disjoint MCSes only in which case
            this standard blocking is not appropriate.

            :param mcs: an MCS to block
            :type mcs: iterable(int)
        """

        self.oracle.add_clause([self.sels[cl_id - 1] for cl_id in mcs])

    def _overapprox(self):
        """
            The method extracts a model corresponding to an over-approximation
            of an MCS, i.e. it is the model of the hard part of the formula
            (the corresponding oracle call is made in :func:`compute`).

            Here, the set of selectors is divided into two parts:
            ``self.ss_assumps``, which is an under-approximation of an MSS
            (maximal satisfiable subset) and ``self.setd``, which is an
            over-approximation of the target MCS. Both will be further refined
            in :func:`_compute`.
        """

        model = self.oracle.get_model()

        for sel in self.sels:
            if len(model) < sel or model[sel - 1] > 0:
                # soft clauses contain positive literals
                # so if var is true then the clause is satisfied
                self.ss_assumps.append(sel)
            else:
                self.setd.append(sel)

    def _compute(self):
        """
            The main method of the class, which computes an MCS given its
            over-approximation. The over-approximation is defined by a model
            for the hard part of the formula obtained in :func:`_overapprox`
            (the corresponding oracle is made in :func:`compute`).

            The method is essentially a simple loop going over all literals
            unsatisfied by the previous model, i.e. the literals of
            ``self.setd`` and checking which literals can be satisfied. This
            process can be seen a refinement of the over-approximation of the
            MCS. The algorithm follows the pseudo-code of the BLS algorithm
            presented in [1]_.

            Additionally, if :class:`MCSls` was constructed with the
            requirement to make "clause :math:`D`" calls, the method calls
            :func:`do_cld_check` at every iteration of the loop using the
            literals of ``self.setd`` not yet checked, as the contents of
            "clause :math:`D`".
        """

        # unless clause D checks are used, test one literal at a time
        # and add it either to satisfied of backbone assumptions
        i = 0
        while i < len(self.setd):
            if self.ucld:
                self.do_cld_check(self.setd[i:])
                i = 0

            if self.setd:
                # if may be empty after the clause D check

                self.ss_assumps.append(self.setd[i])
                if not self.oracle.solve(assumptions=self.ss_assumps +
                                         self.bb_assumps):
                    self.ss_assumps.pop()
                    self.bb_assumps.append(-self.setd[i])

            i += 1

    def do_cld_check(self, cld):
        """
            Do the "clause :math:`D`" check. This method receives a list of
            literals, which serves a "clause :math:`D`" [1]_, and checks
            whether the formula conjoined with :math:`D` is satisfiable.

            If clause :math:`D` cannot be satisfied together with the formula,
            then negations of all of its literals are backbones of the formula
            and the MCSls algorithm can stop. Otherwise, the literals satisfied
            by the new model refine the MCS further.

            Every time the method is called, a new fresh selector variable
            :math:`s` is introduced, which augments the current clause
            :math:`D`. The SAT oracle then checks if clause :math:`(D \\vee
            \\neg{s})` can be satisfied together with the internal formula.
            The :math:`D` clause is then disabled by adding a hard clause
            :math:`(\\neg{s})`.

            :param cld: clause :math:`D` to check
            :type cld: list(int)
        """

        # adding a selector literal to clause D
        # selector literals for clauses D currently
        # cannot be reused, but this may change later
        self.topv += 1
        sel = self.topv
        cld.append(-sel)

        # adding clause D
        self.oracle.add_clause(cld)
        self.ss_assumps.append(sel)

        self.setd = []
        self.oracle.solve(assumptions=self.ss_assumps + self.bb_assumps)

        self.ss_assumps.pop()  # removing clause D assumption
        if self.oracle.get_status() == True:
            model = self.oracle.get_model()

            for l in cld[:-1]:
                # filtering all satisfied literals
                if model[abs(l) - 1] > 0:
                    self.ss_assumps.append(l)
                else:
                    self.setd.append(l)
        else:
            # clause D is unsatisfiable => all literals are backbones
            self.bb_assumps.extend([-l for l in cld[:-1]])

        # deactivating clause D
        self.oracle.add_clause([-sel])

    def _map_extlit(self, l):
        """
            Map an external variable to an internal one if necessary.

            This method is used when new clauses are added to the formula
            incrementally, which may result in introducing new variables
            clashing with the previously used *clause selectors*. The method
            makes sure no clash occurs, i.e. it maps the original variables
            used in the new problem clauses to the newly introduced auxiliary
            variables (see :func:`add_clause`).

            Given an integer literal, a fresh literal is returned. The returned
            integer has the same sign as the input literal.

            :param l: literal to map
            :type l: int

            :rtype: int
        """

        v = abs(l)

        if v in self.vmap.e2i:
            return int(copysign(self.vmap.e2i[v], l))
        else:
            self.topv += 1

            self.vmap.e2i[v] = self.topv
            self.vmap.i2e[self.topv] = v

            return int(copysign(self.topv, l))

    def oracle_time(self):
        """
            Report the total SAT solving time.
        """

        return self.oracle.time_accum()
Ejemplo n.º 22
0
class MUSX(object):
    """
        MUS eXtractor using the deletion-based algorithm. The algorithm is
        described in [1]_ (also see the module description above). Essentially,
        the algorithm can be seen as an iterative process, which tries to
        remove one soft clause at a time and check whether the remaining set of
        soft clauses is still unsatisfiable together with the hard clauses.

        The constructor of :class:`MUSX` objects receives a target
        :class:`.WCNF` formula, a SAT solver name, and a verbosity level. Note
        that the default SAT solver is MiniSat22 (referred to as ``'m22'``, see
        :class:`.SolverNames` for details). The default verbosity level is
        ``1``.

        :param formula: input WCNF formula
        :param solver: name of SAT solver
        :param verbosity: verbosity level

        :type formula: :class:`.WCNF`
        :type solver: str
        :type verbosity: int
    """
    def __init__(self, formula, solver='m22', verbosity=1):
        """
            Constructor.
        """

        topv, self.verbose = formula.nv, verbosity

        # clause selectors and a mapping from selectors to clause ids
        self.sels, self.vmap = [], {}

        # constructing the oracle
        self.oracle = Solver(name=solver,
                             bootstrap_with=formula.hard,
                             use_timer=True)

        if isinstance(formula, WCNFPlus) and formula.atms:
            assert solver in SolverNames.minicard, \
                    'Only Minicard supports native cardinality constraints. Make sure you use the right type of formula.'

            for atm in formula.atms:
                self.oracle.add_atmost(*atm)

        # relaxing soft clauses and adding them to the oracle
        for i, cl in enumerate(formula.soft):
            topv += 1

            self.sels.append(topv)
            self.vmap[topv] = i

            self.oracle.add_clause(cl + [-topv])

    def __enter__(self):
        """
            'with' constructor.
        """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
            'with' destructor.
        """

        self.oracle.delete()
        self.oracle = None

    def delete(self):
        """
            Explicit destructor of the internal SAT oracle.
        """

        if self.oracle:
            self.oracle.delete()
            self.oracle = None

    def compute(self):
        """
            This is the main method of the :class:`MUSX` class. It computes a
            set of soft clauses belonging to an MUS of the input formula.
            First, the method checks whether the formula is satisfiable. If it
            is, nothing else is done. Otherwise, an *unsatisfiable core* of the
            formula is extracted, which is later used as an over-approximation
            of an MUS refined in :func:`_compute`.
        """

        # cheking whether or not the formula is unsatisfiable
        if not self.oracle.solve(assumptions=self.sels):
            # get an overapproximation of an MUS
            approx = sorted(self.oracle.get_core())

            if self.verbose:
                print('c MUS approx:',
                      ' '.join([str(self.vmap[sel] + 1) for sel in approx]),
                      '0')

            # iterate over clauses in the approximation and try to delete them
            mus = self._compute(approx)

            # return an MUS
            return list(map(lambda x: self.vmap[x] + 1, mus))

    def _compute(self, approx):
        """
            Deletion-based MUS extraction. Given an over-approximation of an
            MUS, i.e. an unsatisfiable core previously returned by a SAT
            oracle, the method represents a loop, which at each iteration
            removes a clause from the core and checks whether the remaining
            clauses of the approximation are unsatisfiable together with the
            hard clauses.

            Soft clauses are (de)activated using the standard MiniSat-like
            assumptions interface [2]_. Each soft clause :math:`c` is augmented
            with a selector literal :math:`s`, e.g. :math:`(c) \gets (c \\vee
            \\neg{s})`. As a result, clause :math:`c` can be activated by
            assuming literal :math:`s`. The over-approximation provided as an
            input is specified as a list of selector literals for clauses in
            the unsatisfiable core.

            .. [2] Niklas Eén, Niklas Sörensson. *Temporal induction by
                incremental SAT solving*. Electr. Notes Theor. Comput. Sci.
                89(4). 2003. pp. 543-560

            :param approx: an over-approximation of an MUS
            :type approx: list(int)

            Note that the method does not return. Instead, after its execution,
            the input over-approximation is refined and contains an MUS.
        """

        i = 0

        while i < len(approx):
            to_test = approx[:i] + approx[(i + 1):]
            sel, clid = approx[i], self.vmap[approx[i]]

            if self.verbose > 1:
                print('c testing clid: {0}'.format(clid), end='')

            if self.oracle.solve(assumptions=to_test):
                if self.verbose > 1:
                    print(' -> sat (keeping {0})'.format(clid))

                i += 1
            else:
                if self.verbose > 1:
                    print(' -> unsat (removing {0})'.format(clid))

                approx = to_test

        return approx

    def oracle_time(self):
        """
            Method for calculating and reporting the total SAT solving time.
        """

        return self.oracle.time_accum()
Ejemplo n.º 23
0
def solve_problem(input):
    g = Solver()
    obs0 = input["observations"][0]
    n = len(obs0)
    m = len(obs0[0])
    obs = input["observations"]

    x = m * n * len(obs) * 8 + 1
    g.add_clause([-x])
    police = input["police"]
    medics = input["medics"]
    for k in range(len(input["observations"])):
        sList = []
        iList = []
        for i in range(n):
            for j in range(m):

                place = k * m * n * 8 + (i * m + j) * 8

                if input["police"] == 0:
                    g.add_clause([-(place + 6)])
                    g.add_clause([-(place + 7)])
                if input["medics"] == 0:
                    g.add_clause([-(place + 8)])

                if (obs[k][i][j] == "S" or obs[k][i][j]
                        == "?") and k < len(input["observations"]) - 1:
                    sList.append(place + m * n * 8 + 6)
                if (obs[k][i][j] == "H" or obs[k][i][j]
                        == "?") and k < len(input["observations"]) - 1:
                    iList.append(place + m * n * 8 + 8)
                g.add_clause([
                    place + 1, place + 2, place + 3, place + 4, place + 5,
                    place + 6, place + 7, place + 8
                ])
                for l in range(1, 9):
                    for o in range(1, 9):
                        if (o != l):
                            g.add_clause([-(place + l), -(place + o)])

                if k < len(input["observations"]) - 1:
                    g.add_clause([-(place + 5), (place + m * n * 8 + 5)])
                    g.add_clause([-(place + 8), (place + m * n * 8 + 8)])

                    g.add_clause([-(place + 6), place + m * n * 8 + 7])
                    g.add_clause([-(place + 7), place + m * n * 8 + 1])

                    g.add_clause([
                        -(place + 2), place + m * n * 8 + 3,
                        place + m * n * 8 + 6
                    ])
                    g.add_clause([
                        -(place + 3), place + m * n * 8 + 4,
                        place + m * n * 8 + 6
                    ])
                    g.add_clause([
                        -(place + 4), place + m * n * 8 + 1,
                        place + m * n * 8 + 6
                    ])

                    if i > 0:
                        g.add_clause([
                            -(place + 2), -(place - m * 8 + 1),
                            (place + m * n * 8 - m * 8 + 2),
                            (place + m * n * 8 - m * 8 + 8),
                            (place + m * n * 8 + 6)
                        ])
                        g.add_clause([
                            -(place + 3), -(place - m * 8 + 1),
                            (place + m * n * 8 - m * 8 + 2),
                            (place + m * n * 8 - m * 8 + 8),
                            (place + m * n * 8 + 6)
                        ])
                    if j > 0:
                        g.add_clause([
                            -(place + 2), -(place - 8 + 1),
                            (place + m * n * 8 - 8 + 2),
                            (place + m * n * 8 - 8 + 8),
                            (place + m * n * 8 + 6)
                        ])
                        g.add_clause([
                            -(place + 3), -(place - 8 + 1),
                            (place + m * n * 8 - 8 + 2),
                            (place + m * n * 8 - 8 + 8),
                            (place + m * n * 8 + 6)
                        ])
                    if i < n - 1:
                        g.add_clause([
                            -(place + 2), -(place + m * 8 + 1),
                            (place + m * n * 8 + m * 8 + 2),
                            (place + m * n * 8 + m * 8 + 8),
                            (place + m * n * 8 + 6)
                        ])
                        g.add_clause([
                            -(place + 3), -(place + m * 8 + 1),
                            (place + m * n * 8 + m * 8 + 2),
                            (place + m * n * 8 + m * 8 + 8),
                            (place + m * n * 8 + 6)
                        ])
                    if j < m - 1:
                        g.add_clause([
                            -(place + 2), -(place + 8 + 1),
                            (place + m * n * 8 + 8 + 2),
                            (place + m * n * 8 + 8 + 8),
                            (place + m * n * 8 + 6)
                        ])
                        g.add_clause([
                            -(place + 3), -(place + 8 + 1),
                            (place + m * n * 8 + 8 + 2),
                            (place + m * n * 8 + 8 + 8),
                            (place + m * n * 8 + 6)
                        ])

                # zkanu s1 fkan fi 7da 7wale s
                if k >= 1:

                    g.add_clause([-(place + 7), place - m * n * 8 + 6])
                    g.add_clause([
                        -(place + 6), place - m * n * 8 + 2,
                        place - m * n * 8 + 3, place - m * n * 8 + 4
                    ])
                    g.add_clause([-(place + 3), place - m * n * 8 + 2])
                    g.add_clause([-(place + 4), place - m * n * 8 + 3])
                    g.add_clause([-(place + 2), place - m * n * 8 + 1])

                    g.add_clause([-(place + 5), (place - m * n * 8 + 5)])
                    g.add_clause([
                        -(place + 8), (place - m * n * 8 + 8),
                        (place - m * n * 8 + 1)
                    ])

                    g.add_clause([
                        -(place + 2),
                        (i > 0) * (place - m * n * 8 - m * 8 + 2) + x *
                        (i <= 0),
                        (i > 0) * (place - m * n * 8 - m * 8 + 3) + x *
                        (i <=
                         0),  #(i>0)*(place - m * n * 8 - m * 8 + 4)+x*(i<=0),
                        (j > 0) * (place - m * n * 8 - 8 + 2) + x * (j <= 0),
                        (j > 0) * (place - m * n * 8 - 8 + 3) + x * (j <= 0),
                        #(j>0)*(place - m * n * 8 - 8 + 4)+x*(j<=0),
                        (i < n - 1) * (place - m * n * 8 + m * 8 + 2) + x *
                        (i >= n - 1),
                        (i < n - 1) * (place - m * n * 8 + m * 8 + 3) + x *
                        (i >= n - 1),
                        #(i<n-1)*(place - m * n * 8 + m * 8 + 4)+x*(i>=n-1),
                        (j < m - 1) * (place - m * n * 8 + 8 + 2) + x *
                        (j >= m - 1),
                        (j < m - 1) * (place - m * n * 8 + 8 + 3) + x *
                        (j >= m - 1),
                        #(j<m-1)*(place - m * n * 8 + 8 + 4)+x*(j>=m-1)
                    ])

                if obs[k][i][j] == "H":
                    g.add_clause([place + 1])

                if obs[k][i][j] == "U":
                    g.add_clause([place + 5])
                if obs[k][i][j] == "I":
                    g.add_clause([place + 8])
                if obs[k][i][j] == "S":
                    g.add_clause([place + 2, place + 3, place + 4])

                if obs[k][i][j] == "Q":
                    g.add_clause([place + 6, place + 7])

                #
                if k == 0:
                    if obs[k][i][j] == "S":
                        g.add_clause([place + 2])

                    if obs[k][i][j] == "?":
                        g.add_clause([place + 1, place + 2, place + 5])

        if (len(sList) != 0 and police > 0):
            if len(sList) > police:
                qcombi = list(combinations(sList, police + 1))
                for i in range(len(qcombi)):
                    notcombi = []
                    for j in range(police + 1):
                        notcombi.append(-qcombi[i][j])
                    g.add_clause(notcombi)
                allqcombi = list(combinations(sList, len(sList) - police + 1))
                for i in range(len(allqcombi)):
                    g.add_clause(allqcombi[i])
            else:
                for i in range(len(sList)):
                    g.add_clause([sList[i]])

        if (len(iList) != 0 and medics > 0):

            if len(iList) > medics:
                icombi = list(combinations(iList, medics + 1))
                for i in range(len(icombi)):
                    notcombi = []
                    for j in range(medics + 1):
                        notcombi.append(-icombi[i][j])
                    g.add_clause(notcombi)
                allqcombi = list(combinations(iList, len(iList) - medics + 1))

                for i in range(len(allqcombi)):
                    g.add_clause(allqcombi[i])
            else:
                for i in range(len(iList)):
                    g.add_clause([iList[i]])

    obs0 = input["observations"][0]
    n = len(obs0)
    m = len(obs0[0])
    my_list = []
    my_list2 = []
    for i in range(len(input["queries"])):
        a = input["queries"][i][1] * m * n * 8 + (
            input["queries"][i][0][0] * m + input["queries"][i][0][1]) * 8
        if input["queries"][i][2] == 'H':
            my_list.append((input["queries"][i]))
            if g.solve(assumptions=[a + 1]) == True and not (
                    g.solve(assumptions=[a + 2]) == True
                    or g.solve(assumptions=[a + 3]) == True
                    or g.solve(assumptions=[a + 4]) == True
                    or g.solve(assumptions=[a + 5]) == True
                    or g.solve(assumptions=[a + 6]) == True
                    or g.solve(assumptions=[a + 7]) == True
                    or g.solve(assumptions=[a + 8]) == True):
                c = "T"
            elif g.solve(assumptions=[a + 1]) == True:
                c = "?"
            else:
                c = "F"
            my_list2.append(c)
        if input["queries"][i][2] == 'S':
            my_list.append(input["queries"][i])
            if (g.solve(assumptions=[a + 2]) == True
                    or g.solve(assumptions=[a + 3]) == True
                    or g.solve(assumptions=[a + 4])
                    == True) and not (g.solve(assumptions=[a + 1]) == True
                                      or g.solve(assumptions=[a + 5]) == True
                                      or g.solve(assumptions=[a + 6]) == True
                                      or g.solve(assumptions=[a + 7]) == True
                                      or g.solve(assumptions=[a + 8]) == True):
                c = "T"
            elif g.solve(assumptions=[a + 2]) == True or g.solve(
                    assumptions=[a + 3]) == True or g.solve(
                        assumptions=[a + 4]) == True:
                c = "?"
            else:
                c = "F"
            my_list2.append(c)
        if input["queries"][i][2] == 'Q':
            my_list.append(input["queries"][i])
            if g.solve(assumptions=[a + 6]) == True or g.solve(
                    assumptions=[a + 7]) == True and not (
                        g.solve(assumptions=[a + 1]) == True
                        or g.solve(assumptions=[a + 2]) == True
                        or g.solve(assumptions=[a + 3]) == True
                        or g.solve(assumptions=[a + 4]) == True
                        or g.solve(assumptions=[a + 5]) == True
                        or g.solve(assumptions=[a + 8]) == True):
                c = "T"
            elif g.solve(assumptions=[a + 6]) == True or g.solve(
                    assumptions=[a + 7]) == True:
                c = "?"
            else:
                c = "F"
            my_list2.append(c)
        if input["queries"][i][2] == 'U':
            my_list.append(input["queries"][i])
            if g.solve(assumptions=[a + 5]) == True and not (
                    g.solve(assumptions=[a + 2]) == True
                    or g.solve(assumptions=[a + 3]) == True
                    or g.solve(assumptions=[a + 4]) == True
                    or g.solve(assumptions=[a + 1]) == True
                    or g.solve(assumptions=[a + 6]) == True
                    or g.solve(assumptions=[a + 7]) == True
                    or g.solve(assumptions=[a + 8]) == True):

                c = "T"
            elif g.solve(assumptions=[a + 5]) == True:
                c = "?"

            else:
                c = "F"
            my_list2.append(c)
        if input["queries"][i][2] == 'I':
            my_list.append(input["queries"][i])
            if g.solve(assumptions=[a + 8]) == True and not (
                    g.solve(assumptions=[a + 2]) == True
                    or g.solve(assumptions=[a + 3]) == True
                    or g.solve(assumptions=[a + 4]) == True
                    or g.solve(assumptions=[a + 5]) == True
                    or g.solve(assumptions=[a + 6]) == True
                    or g.solve(assumptions=[a + 7]) == True
                    or g.solve(assumptions=[a + 1]) == True):
                c = "T"
            elif g.solve(assumptions=[a + 8]) == True:
                c = "?"
            else:
                c = "F"
            my_list2.append(c)
    dic = {}
    for i in range(len(my_list)):
        dic[my_list[i]] = my_list2[i]
    return dic
Ejemplo n.º 24
0
Archivo: fm.py Proyecto: sschnug/pysat
class FM(object):
    """
        Algorithm FM - FU & Malik - MSU1.
    """
    def __init__(self, formula, enc=EncType.pairwise, solver='m22', verbose=1):
        """
            Constructor.
        """

        # saving verbosity level
        self.verbose = verbose
        self.solver = solver
        self.time = 0.0

        # MaxSAT related stuff
        self.topv = self.orig_nv = formula.nv
        self.hard = formula.hard
        self.soft = formula.soft
        self.atm1 = []
        self.wght = formula.wght
        self.cenc = enc
        self.cost = 0

        # initialize SAT oracle with hard clauses only
        self.init(with_soft=False)

    def __enter__(self):
        """
            'with' constructor.
        """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
            'with' destructor.
        """

        self.delete()

    def init(self, with_soft=True):
        """
            Initialize the SAT solver.
        """

        self.oracle = Solver(name=self.solver,
                             bootstrap_with=self.hard,
                             use_timer=True)

        # self.atm1 is not empty only in case of minicard
        for am in self.atm1:
            self.oracle.add_atmost(*am)

        if with_soft:
            for cl, cpy in zip(self.soft, self.scpy):
                if cpy:
                    self.oracle.add_clause(cl)

    def delete(self):
        """
            Explicit destructor.
        """

        if self.oracle:
            self.time += self.oracle.time_accum()  # keep SAT solving time

            self.oracle.delete()
            self.oracle = None

    def reinit(self):
        """
            Delete and create a new SAT solver.
        """

        self.delete()
        self.init()

    def compute(self):
        """
            Compute and return a solution.
        """

        if self.oracle.solve():
            # hard part is satisfiable
            # create selectors and a mapping from selectors to clause ids
            self.sels, self.vmap = [], {}
            self.scpy = [True for cl in self.soft]

            # adding soft clauses to oracle
            for i in range(len(self.soft)):
                self.topv += 1

                self.soft[i].append(-self.topv)
                self.sels.append(self.topv)
                self.oracle.add_clause(self.soft[i])

                self.vmap[self.topv] = i

            self._compute()
        else:
            print('s UNSATISFIABLE')

    def _compute(self):
        """
            Compute and return a solution.
        """

        while True:
            if self.oracle.solve(assumptions=self.sels):
                print('s OPTIMUM FOUND')
                print('o {0}'.format(self.cost))

                if self.verbose > 1:
                    model = self.oracle.get_model()
                    model = filter(lambda l: abs(l) <= self.orig_nv, model)
                    print('v', ' '.join([str(l) for l in model]), '0')

                return
            else:
                self.treat_core()

                if self.verbose:
                    print('c cost: {0}; core sz: {1}'.format(
                        self.cost, len(self.core)))

                self.reinit()

    def treat_core(self):
        """
            Found core in main loop, deal with it.
        """

        # extracting the core
        self.core = [self.vmap[sel] for sel in self.oracle.get_core()]
        minw = min(map(lambda i: self.wght[i], self.core))

        # updating the cost
        self.cost += minw

        # splitting clauses in the core if necessary
        self.split_core(minw)

        # relaxing clauses in the core and adding a new atmost1 constraint
        self.relax_core()

    def split_core(self, minw):
        """
            Split clauses in the core whenever necessary.
        """

        for clid in self.core:
            sel = self.sels[clid]

            if self.wght[clid] > minw:
                self.topv += 1

                cl_new = []
                for l in self.soft[clid]:
                    if l != -sel:
                        cl_new.append(l)
                    else:
                        cl_new.append(-self.topv)

                self.sels.append(self.topv)
                self.vmap[self.topv] = len(self.soft)

                self.soft.append(cl_new)
                self.wght.append(self.wght[clid] - minw)
                self.wght[clid] = minw

                self.scpy.append(True)

    def relax_core(self):
        """
            Relax and bound the core.
        """

        if len(self.core) > 1:
            # relaxing
            rels = []

            for clid in self.core:
                self.topv += 1
                rels.append(self.topv)
                self.soft[clid].append(self.topv)

            # creating a new cardinality constraint
            am1 = CardEnc.atmost(lits=rels,
                                 top_id=self.topv,
                                 encoding=self.cenc)

            for cl in am1.clauses:
                self.hard.append(cl)

            # only if minicard
            # (for other solvers am1.atmosts should be empty)
            for am in am1.atmosts:
                self.atm1.append(am)

            self.topv = am1.nv

        elif len(self.core) == 1:  # unit core => simply negate the clause
            self.remove_unit_core()

    def remove_unit_core(self):
        """
            Remove a clause responsible for a unit core.
        """

        self.scpy[self.core[0]] = False

        for l in self.soft[self.core[0]]:
            self.hard.append([-l])

    def oracle_time(self):
        """
            Report the total SAT solving time.
        """

        self.time += self.oracle.time_accum(
        )  # include time of the last SAT call
        return self.time
Ejemplo n.º 25
0
class LBX(object):
    """
        LBX-like algorithm for computing MCSes. Given an unsatisfiable partial
        CNF formula, i.e. formula in the :class:`.WCNF` format, this class can
        be used to compute a given number of MCSes of the formula. The
        implementation follows the LBX algorithm description in [1]_. It can
        use any SAT solver available in PySAT. Additionally, the "clause
        :math:`D`" heuristic can be used when enumerating MCSes.

        The default SAT solver to use is ``m22`` (see :class:`.SolverNames`).
        The "clause :math:`D`" heuristic is disabled by default, i.e.
        ``use_cld`` is set to ``False``. Internal SAT solver's timer is also
        disabled by default, i.e. ``use_timer`` is ``False``.

        :param formula: unsatisfiable partial CNF formula
        :param use_cld: whether or not to use "clause :math:`D`"
        :param solver_name: SAT oracle name
        :param use_timer: whether or not to use SAT solver's timer

        :type formula: :class:`.WCNF`
        :type use_cld: bool
        :type solver_name: str
        :type use_timer: bool
    """
    def __init__(self,
                 formula,
                 use_cld=False,
                 solver_name='m22',
                 use_timer=False):
        """
            Constructor.
        """

        # bootstrapping the solver with hard clauses
        self.oracle = Solver(name=solver_name,
                             bootstrap_with=formula.hard,
                             use_timer=use_timer)
        self.solver = solver_name

        # adding native cardinality constraints (if any) as hard clauses
        # this can be done only if the Minicard solver is in use
        if isinstance(formula, WCNFPlus) and formula.atms:
            assert self.oracle.supports_atmost(), \
                    '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(solver_name)

            for atm in formula.atms:
                self.oracle.add_atmost(*atm)

        self.topv = formula.nv  # top variable id
        self.soft = formula.soft
        self.sels = []
        self.ucld = use_cld

        # mappings between internal and external variables
        VariableMap = collections.namedtuple('VariableMap', ['e2i', 'i2e'])
        self.vmap = VariableMap(e2i={}, i2e={})

        # at this point internal and external variables are the same
        for v in range(1, formula.nv + 1):
            self.vmap.e2i[v] = v
            self.vmap.i2e[v] = v

        for cl in self.soft:
            sel = cl[0]
            if len(cl) > 1 or cl[0] < 0:
                self.topv += 1
                sel = self.topv

                self.oracle.add_clause(cl + [-sel])

            self.sels.append(sel)

    def __del__(self):
        """
            Destructor.
        """

        self.delete()

    def __enter__(self):
        """
            'with' constructor.
        """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
            'with' destructor.
        """

        self.delete()

    def delete(self):
        """
            Explicit destructor of the internal SAT oracle.
        """

        if self.oracle:
            self.oracle.delete()
            self.oracle = None

    def add_clause(self, clause, soft=False):
        """
            The method for adding a new hard of soft clause to the problem
            formula. Although the input formula is to be specified as an
            argument of the constructor of :class:`LBX`, adding clauses may be
            helpful when *enumerating* MCSes of the formula. This way, the
            clauses are added incrementally, i.e. *on the fly*.

            The clause to add can be any iterable over integer literals. The
            additional Boolean parameter ``soft`` can be set to ``True``
            meaning the the clause being added is soft (note that parameter
            ``soft`` is set to ``False`` by default).

            Also note that besides pure clauses, the method can also expect
            native cardinality constraints represented as a pair ``(lits,
            bound)``. Only hard cardinality constraints can be added.

            :param clause: a clause to add
            :param soft: whether or not the clause is soft

            :type clause: iterable(int)
            :type soft: bool
        """

        # first, map external literals to internal literals
        # introduce new variables if necessary
        cl = list(
            map(
                lambda l: self._map_extlit(l), clause if not len(clause) == 2
                or not type(clause[0]) in (list, tuple, set) else clause[0]))

        if not soft:
            if not len(clause) == 2 or not type(
                    clause[0]) in (list, tuple, set):
                # the clause is hard, and so we simply add it to the SAT oracle
                self.oracle.add_clause(cl)
            else:
                # this should be a native cardinality constraint,
                # which can be used only together with Minicard
                assert self.oracle.supports_atmost(), \
                        '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(self.solver)

                self.oracle.add_atmost(cl, clause[1])
        else:
            self.soft.append(cl)

            # soft clauses should be augmented with a selector
            sel = cl[0]
            if len(cl) > 1 or cl[0] < 0:
                self.topv += 1
                sel = self.topv

                self.oracle.add_clause(cl + [-sel])

            self.sels.append(sel)

    def compute(self, enable=[]):
        """
            Compute and return one solution. This method checks whether the
            hard part of the formula is satisfiable, i.e. an MCS can be
            extracted. If the formula is satisfiable, the model computed by the
            SAT call is used as an *over-approximation* of the MCS in the
            method :func:`_compute` invoked here, which implements the LBX
            algorithm.

            An MCS is reported as a list of integers, each representing a soft
            clause index (the smallest index is ``1``).

            An optional input parameter is ``enable``, which represents a
            sequence (normally a list) of soft clause indices that a user
            would prefer to enable/satisfy. Note that this may result in an
            unsatisfiable oracle call, in which case ``None`` will be reported
            as solution. Also, the smallest clause index is assumed to be
            ``1``.

            :param enable: a sequence of clause ids to enable
            :type enable: iterable(int)

            :rtype: list(int)
        """

        self.setd = []
        self.satc = [False for cl in self.soft]  # satisfied clauses
        self.solution = None
        self.bb_assumps = []  # backbone assumptions
        self.ss_assumps = []  # satisfied soft clause assumptions

        if self.oracle.solve(
                assumptions=[self.sels[cl_id - 1] for cl_id in enable]):
            # hard part is satisfiable => there is a solution
            self._filter_satisfied(update_setd=True)
            self._compute()

            self.solution = list(
                map(lambda i: i + 1,
                    filter(lambda i: not self.satc[i], range(len(self.soft)))))

        return self.solution

    def enumerate(self):
        """
            This method iterates through MCSes enumerating them until the
            formula has no more MCSes. The method iteratively invokes
            :func:`compute`. Note that the method does not block the MCSes
            computed - this should be explicitly done by a user.
        """

        done = False
        while not done:
            mcs = self.compute()

            if mcs != None:
                yield mcs
            else:
                done = True

    def block(self, mcs):
        """
            Block a (previously computed) MCS. The MCS should be given as an
            iterable of integers. Note that this method is not automatically
            invoked from :func:`enumerate` because a user may want to block
            some of the MCSes conditionally depending on the needs. For
            example, one may want to compute disjoint MCSes only in which case
            this standard blocking is not appropriate.

            :param mcs: an MCS to block
            :type mcs: iterable(int)
        """

        self.oracle.add_clause([self.sels[cl_id - 1] for cl_id in mcs])

    def _satisfied(self, cl, model):
        """
            Given a clause (as an iterable of integers) and an assignment (as a
            list of integers), this method checks whether or not the assignment
            satisfies the clause. This is done by a simple clause traversal.
            The method is invoked from :func:`_filter_satisfied`.

            :param cl: a clause to check
            :param model: an assignment

            :type cl: iterable(int)
            :type model: list(int)

            :rtype: bool
        """

        for l in cl:
            if len(model) < abs(l) or model[abs(l) - 1] == l:
                # either literal is unassigned or satisfied by the model
                return True

        return False

    def _filter_satisfied(self, update_setd=False):
        """
            This method extracts a model provided by the previous call to a SAT
            oracle and iterates over all soft clauses checking if each of is
            satisfied by the model. Satisfied clauses are marked accordingly
            while the literals of the unsatisfied clauses are kept in a list
            called ``setd``, which is then used to refine the correction set
            (see :func:`_compute`, and :func:`do_cld_check`).

            Optional Boolean parameter ``update_setd`` enforces the method to
            update variable ``self.setd``. If this parameter is set to
            ``False``, the method only updates the list of satisfied clauses,
            which is an under-approximation of a *maximal satisfiable subset*
            (MSS).

            :param update_setd: whether or not to update setd
            :type update_setd: bool
        """

        model = self.oracle.get_model()
        setd = set()

        for i, cl in enumerate(self.soft):
            if not self.satc[i]:
                if self._satisfied(cl, model):
                    self.satc[i] = True
                    self.ss_assumps.append(self.sels[i])
                else:
                    setd = setd.union(set(cl))

        if update_setd:
            self.setd = sorted(setd)

    def _compute(self):
        """
            The main method of the class, which computes an MCS given its
            over-approximation. The over-approximation is defined by a model
            for the hard part of the formula obtained in :func:`compute`.

            The method is essentially a simple loop going over all literals
            unsatisfied by the previous model, i.e. the literals of
            ``self.setd`` and checking which literals can be satisfied. This
            process can be seen a refinement of the over-approximation of the
            MCS. The algorithm follows the pseudo-code of the LBX algorithm
            presented in [1]_.

            Additionally, if :class:`LBX` was constructed with the requirement
            to make "clause :math:`D`" calls, the method calls
            :func:`do_cld_check` at every iteration of the loop using the
            literals of ``self.setd`` not yet checked, as the contents of
            "clause :math:`D`".
        """

        # unless clause D checks are used, test one literal at a time
        # and add it either to satisfied of backbone assumptions
        i = 0
        while i < len(self.setd):
            if self.ucld:
                self.do_cld_check(self.setd[i:])
                i = 0

            if self.setd:  # if may be empty after the clause D check
                if self.oracle.solve(assumptions=self.ss_assumps +
                                     self.bb_assumps + [self.setd[i]]):
                    # filtering satisfied clauses
                    self._filter_satisfied()
                else:
                    # current literal is backbone
                    self.bb_assumps.append(-self.setd[i])

            i += 1

    def do_cld_check(self, cld):
        """
            Do the "clause :math:`D`" check. This method receives a list of
            literals, which serves a "clause :math:`D`" [2]_, and checks
            whether the formula conjoined with :math:`D` is satisfiable.

            .. [2] Joao Marques-Silva, Federico Heras, Mikolas Janota,
                Alessandro Previti, Anton Belov. *On Computing Minimal
                Correction Subsets*. IJCAI 2013. pp. 615-622

            If clause :math:`D` cannot be satisfied together with the formula,
            then negations of all of its literals are backbones of the formula
            and the LBX algorithm can stop. Otherwise, the literals satisfied
            by the new model refine the MCS further.

            Every time the method is called, a new fresh selector variable
            :math:`s` is introduced, which augments the current clause
            :math:`D`. The SAT oracle then checks if clause :math:`(D \\vee
            \\neg{s})` can be satisfied together with the internal formula.
            The :math:`D` clause is then disabled by adding a hard clause
            :math:`(\\neg{s})`.

            :param cld: clause :math:`D` to check
            :type cld: list(int)
        """

        # adding a selector literal to clause D
        # selector literals for clauses D currently
        # cannot be reused, but this may change later
        self.topv += 1
        sel = self.topv
        cld.append(-sel)

        # adding clause D
        self.oracle.add_clause(cld)

        if self.oracle.solve(assumptions=self.ss_assumps + self.bb_assumps +
                             [sel]):
            # filtering satisfied
            self._filter_satisfied(update_setd=True)
        else:
            # clause D is unsatisfiable => all literals are backbones
            self.bb_assumps.extend([-l for l in cld[:-1]])
            self.setd = []

        # deactivating clause D
        self.oracle.add_clause([-sel])

    def _map_extlit(self, l):
        """
            Map an external variable to an internal one if necessary.

            This method is used when new clauses are added to the formula
            incrementally, which may result in introducing new variables
            clashing with the previously used *clause selectors*. The method
            makes sure no clash occurs, i.e. it maps the original variables
            used in the new problem clauses to the newly introduced auxiliary
            variables (see :func:`add_clause`).

            Given an integer literal, a fresh literal is returned. The returned
            integer has the same sign as the input literal.

            :param l: literal to map
            :type l: int

            :rtype: int
        """

        v = abs(l)

        if v in self.vmap.e2i:
            return int(copysign(self.vmap.e2i[v], l))
        else:
            self.topv += 1

            self.vmap.e2i[v] = self.topv
            self.vmap.i2e[self.topv] = v

            return int(copysign(self.topv, l))

    def oracle_time(self):
        """
            Report the total SAT solving time.
        """

        return self.oracle.time_accum()
Ejemplo n.º 26
0
def closest_string(bitarray_list, distance=4):
    """
    Return if a bitarray exists of distance at most 'distance'.
    Use example:

    s1=bitarray('0010')
    s2=bitarray('0011')
    closest_string([s1,s2], distance=0)
    > False
    closest_string([s1,s2], distance=2)
    > True
    """
    if distance < 0:
        raise ValueError('Distance must be positive integer')

    logging.info('\nCodifying SAT Solver...')

    length = max(len(bit_arr) for bit_arr in bitarray_list)
    solver = Solver(name='mcm')
    vpool = IDPool()
    local_list = bitarray_list.copy()

    logging.info(' -> Codifying: normalizing strings')
    for index, bitarr in enumerate(bitarray_list):
        aux = (length - len(bitarr)) * bitarray('0')
        local_list[index] = bitarr + aux

    logging.info(' -> Codifying: imposing distance condition')
    for index, word in enumerate(local_list):
        for pos in range(length):
            vpool.id(ut.xvar(index, pos))

    for pos in range(length):
        vpool.id(ut.yvar(pos))

    for index, word in enumerate(local_list):
        for pos in range(length):
            vpool.id(ut.zvar(index, pos))

    for index, word in enumerate(local_list):
        for pos in range(length):
            for clause in ut.triple_equal(ut.xvar(index, pos),
                                          ut.yvar(pos),
                                          ut.zvar(index, pos),
                                          vpool=vpool):
                solver.add_clause(clause)
        cnf = CardEnc.atleast(
            lits=[vpool.id(ut.zvar(index, pos)) for pos in range(length)],
            bound=length - distance,
            vpool=vpool)
        solver.append_formula(cnf)

    logging.info(' -> Codifying: Words Value')
    assumptions = []
    for index, word in enumerate(local_list):
        for pos in range(length):
            assumptions += [
                vpool.id(ut.xvar(index, pos)) * (-1)**(not word[pos])
            ]

    logging.info('Running SAT Solver...')
    return solver.solve(assumptions=assumptions)
Ejemplo n.º 27
0
Archivo: lbx.py Proyecto: sschnug/pysat
class LBX(object):
    """
        LBX-like algorithm for computing MCSes.
    """

    def __init__(self, formula, use_cld=False, solver_name='m22', use_timer=False):
        """
            Constructor.
        """

        # bootstrapping the solver with hard clauses
        self.oracle = Solver(name=solver_name, bootstrap_with=formula.hard, use_timer=use_timer)

        self.topv = formula.nv  # top variable id
        self.soft = formula.soft
        self.sels = []
        self.ucld = use_cld
        self.vmap_dir = {}
        self.vmap_opp = {}

        for cl in self.soft:
            sel = cl[0]
            if len(cl) > 1 or cl[0] < 0:
                self.topv += 1
                sel = self.topv

                self.oracle.add_clause(cl + [-sel])

            self.sels.append(sel)
            self.vmap_dir[sel] = len(self.sels)
            self.vmap_opp[len(self.sels)] = sel

    def __enter__(self):
        """
            'with' constructor.
        """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
            'with' destructor.
        """

        self.oracle.delete()
        self.oracle = None

    def delete(self):
        """
            Explicit destructor.
        """

        if self.oracle:
            self.oracle.delete()
            self.oracle = None

    def compute(self):
        """
            Compute and return one solution.
        """

        self.setd = []
        self.satc = [False for cl in self.soft]  # satisfied clauses
        self.solution = None
        self.bb_assumps = []  # backbone assumptions
        self.ss_assumps = []  # satisfied soft clause assumptions

        if self.oracle.solve():
            # hard part is satisfiable => there is a solution
            self._filter_satisfied(update_setd=True)
            self._compute()

            self.solution = list(map(lambda i: i + 1, filter(lambda i: not self.satc[i], range(len(self.soft)))))

        return self.solution

    def enumerate(self):
        """
            Enumerate all MCSes and report them one by one.
        """

        done = False
        while not done:
            mcs = self.compute()

            if mcs != None:
                yield mcs
            else:
                done = True

    def block(self, mcs):
        """
            Block a (previously computed) MCS.
        """

        self.oracle.add_clause([self.vmap_opp[cl_id] for cl_id in mcs])

    def _satisfied(self, cl, model):
        """
            Checks whether or not a clause is satisfied by a model.
        """

        for l in cl:
            if len(model) < abs(l) or model[abs(l) - 1] == l:
                # either literal is unassigned or satisfied by the model
                return True

        return False

    def _filter_satisfied(self, update_setd=False):
        """
            Separates satisfied clauses and literals of unsatisfied clauses.
        """

        model = self.oracle.get_model()
        setd = set()

        for i, cl in enumerate(self.soft):
            if not self.satc[i]:
                if self._satisfied(cl, model):
                    self.satc[i] = True
                    self.ss_assumps.append(self.sels[i])
                else:
                    setd = setd.union(set(cl))

        if update_setd:
            self.setd = list(setd)

    def _compute(self):
        """
            Compute an MCS.
        """

        # unless clause D checks are used, test one literal at a time
        # and add it either to satisfied of backbone assumptions
        i = 0
        while i < len(self.setd):
            if self.ucld:
                self.do_cld_check(self.setd[i:])
                i = 0

            if self.setd:  # if may be empty after the clause D check
                if self.oracle.solve(assumptions=self.ss_assumps + self.bb_assumps + [self.setd[i]]):
                    # filtering satisfied clauses
                    self._filter_satisfied()
                else:
                    # current literal is backbone
                    self.bb_assumps.append(-self.setd[i])

            i += 1

    def do_cld_check(self, cld):
        """
            Do clause D check.
        """

        # adding a selector literal to clause D
        # selector literals for clauses D currently
        # cannot be reused, but this may change later
        self.topv += 1
        sel = self.topv
        cld.append(-sel)

        # adding clause D
        self.oracle.add_clause(cld)

        if self.oracle.solve(assumptions=self.ss_assumps + self.bb_assumps + [sel]):
            # filtering satisfied
            self._filter_satisfied(update_setd=True)
        else:
            # clause D is unsatisfiable => all literals are backbones
            self.bb_assumps.extend([-l for l in cld[:-1]])
            self.setd = []

        # deactivating clause D
        self.oracle.add_clause([-sel])

    def oracle_time(self):
        """
            Report the total SAT solving time.
        """

        return self.oracle.time_accum()
Ejemplo n.º 28
0
class FM(object):
    """
        A non-incremental implementation of the FM (Fu&Malik, or WMSU1)
        algorithm. The algorithm (see details in [5]_) is *core-guided*, i.e.
        it solves maximum satisfiability with a series of unsatisfiability
        oracle calls, each producing an unsatisfiable core. The clauses
        involved in an unsatisfiable core are *relaxed* and a new
        :math:`\\textsf{AtMost1}` constraint on the corresponding *relaxation
        variables* is added to the formula. The process gets a bit more
        sophisticated in the case of weighted formulas because of the *clause
        weight splitting* technique.

        The constructor of :class:`FM` objects receives a target :class:`.WCNF`
        MaxSAT formula, an identifier of the cardinality encoding to use, a SAT
        solver name, and a verbosity level. Note that the algorithm uses the
        ``pairwise`` (see :class:`.card.EncType`) cardinality encoding by
        default, while the default SAT solver is MiniSat22 (referred to as
        ``'m22'``, see :class:`.SolverNames` for details). The default
        verbosity level is ``1``.

        :param formula: input MaxSAT formula
        :param enc: cardinality encoding to use
        :param solver: name of SAT solver
        :param verbose: verbosity level

        :type formula: :class:`.WCNF`
        :type enc: int
        :type solver: str
        :type verbose: int
    """

    def __init__(self, formula, enc=EncType.pairwise, solver='m22', verbose=1):
        """
            Constructor.
        """

        # saving verbosity level
        self.verbose = verbose
        self.solver = solver
        self.time = 0.0

        # MaxSAT related stuff
        self.topv = self.orig_nv = formula.nv
        self.hard = copy.deepcopy(formula.hard)
        self.soft = copy.deepcopy(formula.soft)
        self.wght = formula.wght[:]
        self.cenc = enc
        self.cost = 0

        if isinstance(formula, WCNFPlus) and formula.atms:
            self.atm1 = copy.deepcopy(formula.atms)
        else:
            self.atm1 = None

        # initialize SAT oracle with hard clauses only
        self.init(with_soft=False)

    def __enter__(self):
        """
            'with' constructor.
        """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
            'with' destructor.
        """

        self.delete()

    def init(self, with_soft=True):
        """
            The method for the SAT oracle initialization. Since the oracle is
            is used non-incrementally, it is reinitialized at every iteration
            of the MaxSAT algorithm (see :func:`reinit`). An input parameter
            ``with_soft`` (``False`` by default) regulates whether or not the
            formula's soft clauses are copied to the oracle.

            :param with_soft: copy formula's soft clauses to the oracle or not
            :type with_soft: bool
        """

        self.oracle = Solver(name=self.solver, bootstrap_with=self.hard, use_timer=True)

        if self.atm1:  # this check is needed at the beggining (before iteration 1)
            assert self.oracle.supports_atmost(), \
                    '{0} does not support native cardinality constraints. Make sure you use the right type of formula.'.format(solver_name)

            # self.atm1 is not empty only in case of minicard
            for am in self.atm1:
                self.oracle.add_atmost(*am)

        if with_soft:
            for cl, cpy in zip(self.soft, self.scpy):
                if cpy:
                    self.oracle.add_clause(cl)

    def delete(self):
        """
            Explicit destructor of the internal SAT oracle.
        """

        if self.oracle:
            self.time += self.oracle.time_accum()  # keep SAT solving time

            self.oracle.delete()
            self.oracle = None

    def reinit(self):
        """
            This method calls :func:`delete` and :func:`init` to reinitialize
            the internal SAT oracle. This is done at every iteration of the
            MaxSAT algorithm.
        """

        self.delete()
        self.init();

    def compute(self):
        """
            Compute a MaxSAT solution. First, the method checks whether or
            not the set of hard clauses is satisfiable. If not, the method
            returns ``False``. Otherwise, add soft clauses to the oracle and
            call the MaxSAT algorithm (see :func:`_compute`).

            Note that the soft clauses are added to the oracles after being
            augmented with additional *selector* literals. The selectors
            literals are then used as *assumptions* when calling the SAT oracle
            and are needed for extracting unsatisfiable cores.
        """

        if self.oracle.solve():
            # hard part is satisfiable
            # create selectors and a mapping from selectors to clause ids
            self.sels, self.vmap = [], {}
            self.scpy = [True for cl in self.soft]

            # adding soft clauses to oracle
            for i in range(len(self.soft)):
                self.topv += 1

                self.soft[i].append(-self.topv)
                self.sels.append(self.topv)
                self.oracle.add_clause(self.soft[i])

                self.vmap[self.topv] = i

            self._compute()
            return True
        else:
            return False

    def _compute(self):
        """
            This method implements WMSU1 algorithm. The method is essentially a
            loop, which at each iteration calls the SAT oracle to decide
            whether the working formula is satisfiable. If it is, the method
            derives a model (stored in variable ``self.model``) and returns.
            Otherwise, a new unsatisfiable core of the formula is extracted
            and processed (see :func:`treat_core`), and the algorithm proceeds.
        """

        while True:
            if self.oracle.solve(assumptions=self.sels):
                self.model = self.oracle.get_model()
                self.model = list(filter(lambda l: abs(l) <= self.orig_nv, self.model))
                return
            else:
                self.treat_core()

                if self.verbose > 1:
                    print('c cost: {0}; core sz: {1}'.format(self.cost, len(self.core)))

                self.reinit()

    def treat_core(self):
        """
            Now that the previous SAT call returned UNSAT, a new unsatisfiable
            core should be extracted and relaxed. Core extraction is done
            through a call to the :func:`pysat.solvers.Solver.get_core` method,
            which returns a subset of the selector literals deemed responsible
            for unsatisfiability.

            After the core is extracted, its *minimum weight* ``minw`` is
            computed, i.e. it is the minimum weight among the weights of all
            soft clauses involved in the core (see [5]_). Note that the cost of
            the MaxSAT solution is incremented by ``minw``.

            Clauses that have weight larger than ``minw`` are split (see
            :func:`split_core`). Afterwards, all clauses of the unsatisfiable
            core are relaxed (see :func:`relax_core`).
        """

        # extracting the core
        self.core = [self.vmap[sel] for sel in self.oracle.get_core()]
        minw = min(map(lambda i: self.wght[i], self.core))

        # updating the cost
        self.cost += minw

        # splitting clauses in the core if necessary
        self.split_core(minw)

        # relaxing clauses in the core and adding a new atmost1 constraint
        self.relax_core()

    def split_core(self, minw):
        """
            Split clauses in the core whenever necessary.

            Given a list of soft clauses in an unsatisfiable core, the method
            is used for splitting clauses whose weights are greater than the
            minimum weight of the core, i.e. the ``minw`` value computed in
            :func:`treat_core`. Each clause :math:`(c\\vee\\neg{s},w)`, s.t.
            :math:`w>minw` and :math:`s` is its selector literal, is split into
            clauses (1) clause :math:`(c\\vee\\neg{s}, minw)` and (2) a
            residual clause :math:`(c\\vee\\neg{s}',w-minw)`. Note that the
            residual clause has a fresh selector literal :math:`s'` different
            from :math:`s`.

            :param minw: minimum weight of the core
            :type minw: int
        """

        for clid in self.core:
            sel = self.sels[clid]

            if self.wght[clid] > minw:
                self.topv += 1

                cl_new = []
                for l in self.soft[clid]:
                    if l != -sel:
                        cl_new.append(l)
                    else:
                        cl_new.append(-self.topv)

                self.sels.append(self.topv)
                self.vmap[self.topv] = len(self.soft)

                self.soft.append(cl_new)
                self.wght.append(self.wght[clid] - minw)
                self.wght[clid] = minw

                self.scpy.append(True)

    def relax_core(self):
        """
            Relax and bound the core.

            After unsatisfiable core splitting, this method is called. If the
            core contains only one clause, i.e. this clause cannot be satisfied
            together with the hard clauses of the formula, the formula gets
            augmented with the negation of the clause (see
            :func:`remove_unit_core`).

            Otherwise (if the core contains more than one clause), every clause
            :math:`c` of the core is *relaxed*. This means a new *relaxation
            literal* is added to the clause, i.e. :math:`c\gets c\\vee r`,
            where :math:`r` is a fresh (unused) relaxation variable. After the
            clauses get relaxed, a new cardinality encoding is added to the
            formula enforcing the sum of the new relaxation variables to be not
            greater than 1, :math:`\sum_{c\in\phi}{r\leq 1}`, where
            :math:`\phi` denotes the unsatisfiable core.
        """

        if len(self.core) > 1:
            # relaxing
            rels = []

            for clid in self.core:
                self.topv += 1
                rels.append(self.topv)
                self.soft[clid].append(self.topv)

            # creating a new cardinality constraint
            am1 = CardEnc.atmost(lits=rels, top_id=self.topv, encoding=self.cenc)

            for cl in am1.clauses:
                self.hard.append(cl)

            # only if minicard
            # (for other solvers am1.atmosts should be empty)
            for am in am1.atmosts:
                self.atm1.append(am)

            self.topv = am1.nv

        elif len(self.core) == 1:  # unit core => simply negate the clause
            self.remove_unit_core()

    def remove_unit_core(self):
        """
            If an unsatisfiable core contains only one clause :math:`c`, this
            method is invoked to add a bunch of new unit size hard clauses. As
            a result, the SAT oracle gets unit clauses :math:`(\\neg{l})` for
            all literals :math:`l` in clause :math:`c`.
        """

        self.scpy[self.core[0]] = False

        for l in self.soft[self.core[0]]:
            self.hard.append([-l])

    def oracle_time(self):
        """
            Method for calculating and reporting the total SAT solving time.
        """

        self.time += self.oracle.time_accum()  # include time of the last SAT call
        return self.time
Ejemplo n.º 29
0
class LSU:
    """
        Linear SAT-UNSAT algorithm for MaxSAT [1]_. The algorithm can be seen
        as a series of satisfiability oracle calls refining an upper bound on
        the MaxSAT cost, followed by one unsatisfiability call, which stops the
        algorithm. The implementation encodes the sum of all selector literals
        using the *iterative totalizer encoding* [2]_. At every iteration, the
        upper bound on the cost is reduced and enforced by adding the
        corresponding unit size clause to the working formula. No clauses are
        removed during the execution of the algorithm. As a result, the SAT
        oracle is used incrementally.

        .. warning:: At this point, :class:`LSU` supports only
            **unweighted** problems.

        The constructor receives an input :class:`.WCNF` formula, a name of the
        SAT solver to use (see :class:`.SolverNames` for details), and an
        integer verbosity level.

        :param formula: input MaxSAT formula
        :param solver: name of SAT solver
        :param pb_enc_type: PB encoding type to use for solving weighted problems
        :param expect_interrupt: whether or not an :meth:`interrupt` call is expected
        :param verbose: verbosity level

        :type formula: :class:`.WCNF`
        :type solver: str
        :type expect_interrupt: bool
        :type verbose: int
    """
    def __init__(self,
                 formula,
                 solver='g4',
                 pb_enc_type=EncType.best,
                 expect_interrupt=False,
                 verbose=0):
        """
            Constructor.
        """

        self.verbose = verbose
        self.solver = solver
        self.pb_enc_type = pb_enc_type
        self.expect_interrupt = expect_interrupt
        self.formula = formula
        self.vpool = IDPool(occupied=[
            (1, formula.nv)
        ])  # variable pool used for managing card/PB encodings
        self.sels = []  # soft clause selector variables
        self.is_weighted = False  # auxiliary flag indicating if it's a weighted problem
        self.tot = None  # totalizer encoder for the cardinality constraint
        self._init(formula)  # initialize SAT oracle

    def _init(self, formula):
        """
            SAT oracle initialization. The method creates a new SAT oracle and
            feeds it with the formula's hard clauses. Afterwards, all soft
            clauses of the formula are augmented with selector literals and
            also added to the solver. The list of all introduced selectors is
            stored in variable ``self.sels``.

            :param formula: input MaxSAT formula
            :type formula: :class:`WCNF`
        """

        self.oracle = Solver(name=self.solver,
                             bootstrap_with=formula.hard,
                             incr=True,
                             use_timer=True)

        for i, cl in enumerate(formula.soft):
            # TODO: if clause is unit, use its literal as selector
            # (ITotalizer must be extended to support PB constraints first)
            selv = self.vpool._next()
            cl.append(selv)
            self.oracle.add_clause(cl)
            self.sels.append(selv)
        self.is_weighted = any(w > 1 for w in formula.wght)

        if self.verbose > 1:
            print('c formula: {0} vars, {1} hard, {2} soft'.format(
                formula.nv, len(formula.hard), len(formula.soft)))

    def __del__(self):
        """
            Destructor.
        """

        self.delete()

    def __enter__(self):
        """
            'with' constructor.
        """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
            'with' destructor.
        """

        self.delete()

    def delete(self):
        """
            Explicit destructor of the internal SAT oracle and the
            :class:`.ITotalizer` object.
        """

        if self.oracle:
            self.oracle.delete()
            self.oracle = None

        if self.tot:
            self.tot.delete()
            self.tot = None

    def solve(self):
        """
            Computes a solution to the MaxSAT problem. The method implements
            the LSU/LSUS algorithm, i.e. it represents a loop, each iteration
            of which calls a SAT oracle on the working MaxSAT formula and
            refines the upper bound on the MaxSAT cost until the formula
            becomes unsatisfiable.

            Returns ``True`` if the hard part of the MaxSAT formula is
            satisfiable, i.e. if there is a MaxSAT solution, and ``False``
            otherwise.

            :rtype: bool
        """

        is_sat = False

        while self.oracle.solve_limited(
                expect_interrupt=self.expect_interrupt):
            is_sat = True
            self.model = self.oracle.get_model()
            self.cost = self._get_model_cost(self.formula, self.model)
            if self.verbose:
                print('o {0}'.format(self.cost))
                sys.stdout.flush()
            if self.cost == 0:  # if cost is 0, then model is an optimum solution
                break
            self._assert_lt(self.cost)

        if is_sat:
            self.model = filter(lambda l: abs(l) <= self.formula.nv,
                                self.model)
            if self.verbose:
                if self.found_optimum():
                    print('s OPTIMUM FOUND')
                else:
                    print('s SATISFIABLE')
        elif self.verbose:
            print('s UNSATISFIABLE')

        return is_sat

    def get_model(self):
        """
            This method returns a model obtained during a prior satisfiability
            oracle call made in :func:`solve`.

            :rtype: list(int)
        """

        return self.model

    def found_optimum(self):
        """
            Checks if the optimum solution was found in a prior call to
            :func:`solve`.

            :rtype: bool
        """

        return self.oracle.get_status() is not None

    def _get_model_cost(self, formula, model):
        """
            Given a WCNF formula and a model, the method computes the MaxSAT
            cost of the model, i.e. the sum of weights of soft clauses that are
            unsatisfied by the model.

            :param formula: an input MaxSAT formula
            :param model: a satisfying assignment

            :type formula: :class:`.WCNF`
            :type model: list(int)

            :rtype: int
        """

        model_set = set(model)
        cost = 0

        for cl, w in zip(formula.soft, formula.wght):
            cost += w if all(l not in model_set for l in filter(
                lambda l: abs(l) <= self.formula.nv, cl)) else 0

        return cost

    def _assert_lt(self, cost):
        """
            The method enforces an upper bound on the cost of the MaxSAT
            solution. For unweighted problems, this is done by encoding the sum
            of all soft clause selectors with the use the iterative totalizer
            encoding, i.e. :class:`.ITotalizer`. Note that the sum is created
            once, at the beginning. Each of the following calls to this method
            only enforces the upper bound on the created sum by adding the
            corresponding unit size clause. For weighted problems, the PB
            encoding given through the :meth:`__init__` method is used.
            Each such clause is added on the fly with no restart of the
            underlying SAT oracle.

            :param cost: the cost of the next MaxSAT solution is enforced to be
                *lower* than this current cost

            :type cost: int
        """

        if self.is_weighted:
            # TODO: use incremental PB encoding
            self.oracle.append_formula(
                PBEnc.leq(self.sels,
                          weights=self.formula.wght,
                          bound=cost - 1,
                          vpool=self.vpool))
        else:

            if self.tot is None:
                self.tot = ITotalizer(lits=self.sels,
                                      ubound=cost - 1,
                                      top_id=self.vpool.top)
                self.vpool.top = self.tot.top_id

                for cl in self.tot.cnf.clauses:
                    self.oracle.add_clause(cl)

            self.oracle.add_clause([-self.tot.rhs[cost - 1]])

    def interrupt(self):
        """
            Interrupt the current execution of LSU's :meth:`solve` method.
            Can be used to enforce time limits using timer objects. The
            interrupt must be cleared before running the LSU algorithm again
            (see :meth:`clear_interrupt`).
        """

        self.oracle.interrupt()

    def clear_interrupt(self):
        """
            Clears an interruption.
        """

        self.oracle.clear_interrupt()

    def oracle_time(self):
        """
            Method for calculating and reporting the total SAT solving time.
        """

        return self.oracle.time_accum()
Ejemplo n.º 30
0
class MUSX(object):
    """
        MUS eXctractor using the deletion based algorithm.
    """
    def __init__(self, formula, solver='m22', verbosity=1):
        """
            Constructor.
        """

        topv, self.verbose = formula.nv, verbosity

        # clause selectors and a mapping from selectors to clause ids
        self.sels, self.vmap = [], {}

        # constructing the oracle
        self.oracle = Solver(name=solver, use_timer=True)

        # relaxing clauses and adding them to the oracle
        for i, cl in enumerate(formula.clauses):
            topv += 1

            self.sels.append(topv)
            self.vmap[topv] = i

            self.oracle.add_clause(cl + [-topv])

    def __enter__(self):
        """
            'with' constructor.
        """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """
            'with' destructor.
        """

        self.oracle.delete()
        self.oracle = None

    def delete(self):
        """
            Explicit destructor.
        """

        if self.oracle:
            self.oracle.delete()
            self.oracle = None

    def compute(self):
        """
            Compute and return a solution.
        """

        # cheking whether or not the formula is unsatisfiable
        if not self.oracle.solve(assumptions=self.sels):
            # get an overapproximation of an MUS
            approx = sorted(self.oracle.get_core())

            if self.verbose:
                print('c MUS approx:',
                      ' '.join([str(self.vmap[sel] + 1) for sel in approx]),
                      '0')

            # iterate over clauses in the approximation and try to delete them
            self._compute(approx)

            # return an MUS
            return list(map(lambda x: self.vmap[x] + 1, approx))

    def _compute(self, approx):
        """
            Deletion-based MUS extraction.
            (Try to delete clauses in the given approximation one by one.)
        """

        i = 0

        while i < len(approx):
            to_test = approx[:i] + approx[(i + 1):]
            sel, clid = approx[i], self.vmap[approx[i]]

            if self.verbose > 1:
                print('c testing clid: {0}'.format(clid), end='')

            if self.oracle.solve(assumptions=to_test):
                if self.verbose > 1:
                    print(' -> sat (keeping {0})'.format(clid))

                i += 1
            else:
                if self.verbose > 1:
                    print(' -> unsat (removing {0})'.format(clid))

                approx = to_test

    def oracle_time(self):
        """
            Report the total SAT solving time.
        """

        return self.oracle.time_accum()