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)))
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()
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
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)))
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
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
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)))
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)))
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)))
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), ))
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
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
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)))
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), ))
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)
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()
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
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)))
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), ))
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
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()
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()
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
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
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()
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)
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()
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
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()
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()