def atmost(cls, lits, bound=1, top_id=None, vpool=None, encoding=EncType.seqcounter): """ This method can be used for creating a CNF encoding of an AtMostK constraint, i.e. of :math:`\sum_{i=1}^{n}{x_i}\leq k`. The method shares the arguments and the return type with method :meth:`CardEnc.atleast`. Please, see it for details. """ if encoding < 0 or encoding > 9: raise(NoSuchEncodingError(encoding)) # checking if the bound is meaningless for any encoding if bound < 0: raise ValueError('Wrong bound: {0}'.format(bound)) if encoding in (0, 4, 5) and 1 < bound < len(lits) - 1: raise(UnsupportedBound(encoding, bound)) assert not top_id or not vpool, \ 'Use either a top id or a pool of variables but not both.' # we are going to return this formula ret = CNFPlus() # if the list of literals is empty, return empty formula if not lits: return ret # obtaining the top id from the variable pool if vpool: top_id = vpool.top # making sure we are dealing with a list of literals lits = list(lits) # choosing the maximum id among the current top and the list of literals top_id = max(map(lambda x: abs(x), lits + [top_id if top_id != None else 0])) # MiniCard's native representation is handled separately if encoding == 9: ret.atmosts, ret.nv = [(lits, bound)], top_id return ret res = pycard.encode_atmost(lits, bound, top_id, encoding, int(MainThread.check())) if res: ret.clauses, ret.nv = res # updating vpool if necessary if vpool: if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv: cls._update_vids(ret, vpool) else: # here, ret.nv id is assumed to be larger than the top id vpool.top = ret.nv - 1 vpool._next() return ret
def atmost(cls, lits, bound=1, top_id=None, encoding=EncType.seqcounter): """ This method can be used for creating a CNF encoding of an AtMostK constraint, i.e. of :math:`\sum_{i=1}^{n}{x_i}\leq k`. The method shares the arguments and the return type with method :meth:`CardEnc.atleast`. Please, see it for details. """ if encoding < 0 or encoding > 9: raise (NoSuchEncodingError(encoding)) if not top_id: top_id = max(map(lambda x: abs(x), lits)) # we are going to return this formula ret = CNFPlus() # MiniCard's native representation is handled separately if encoding == 9: ret.atmosts, ret.nv = [(lits, bound)], top_id return ret # saving default SIGINT handler def_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_DFL) res = pycard.encode_atmost(lits, bound, top_id, encoding) # recovering default SIGINT handler def_sigint_handler = signal.signal(signal.SIGINT, def_sigint_handler) if res: ret.clauses, ret.nv = res return ret
def get_constraint(idpool: IDPool, id2varmap, constraint: tConstraint) -> CNFPlus: """ Generate formula for a given cardinality constraint""" validate_constraint(constraint) lits = [] for ta in constraint.tas: t1 = tuple((constraint.course_name, ta)) if t1 not in id2varmap.keys(): id1 = idpool.id(t1) id2varmap[t1] = id1 else: id1 = id2varmap[t1] lits.append(id1) if constraint.type == tCardType.GREATEROREQUALS: if (constraint.bound == 1): cnf = CNFPlus() cnf.append(lits) elif (constraint.bound > len(lits)): msg = "Num TAs available for constraint:" + constraint.con_str + "is more than the bound in the constraint. \ Changing the bound to " + str(len(lits)) + ".\n" print(msg, file=sys.stderr) constraint.bound = len(lits) cnf = CardEnc.atleast(lits, vpool=idpool, bound=constraint.bound) elif constraint.type == tCardType.LESSOREQUALS: cnf = CardEnc.atmost(lits, vpool=idpool, bound=constraint.bound) return cnf
def atmost(cls, lits, bound=1, top_id=None, vpool=None, encoding=EncType.seqcounter): """ This method can be used for creating a CNF encoding of an AtMostK constraint, i.e. of :math:`\sum_{i=1}^{n}{x_i}\leq k`. The method shares the arguments and the return type with method :meth:`CardEnc.atleast`. Please, see it for details. """ if encoding < 0 or encoding > 9: raise (NoSuchEncodingError(encoding)) assert not top_id or not vpool, \ 'Use either a top id or a pool of variables but not both.' # we are going to return this formula ret = CNFPlus() # if the list of literals is empty, return empty formula if not lits: return ret # obtaining the top id from the variable pool if vpool: top_id = vpool.top if not top_id: top_id = max(map(lambda x: abs(x), lits)) # MiniCard's native representation is handled separately if encoding == 9: ret.atmosts, ret.nv = [(lits, bound)], top_id return ret # saving default SIGINT handler def_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_DFL) res = pycard.encode_atmost(lits, bound, top_id, encoding) # recovering default SIGINT handler def_sigint_handler = signal.signal(signal.SIGINT, def_sigint_handler) if res: ret.clauses, ret.nv = res # updating vpool if necessary if vpool: if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv: cls._update_vids(ret, vpool) else: vpool.top = ret.nv - 1 vpool._next() return ret
def __init__(self, k): self.map_atoms = {} self.atoms_counter = 1 self.cnf = CNFPlus() self.min_card = math.inf self.number_of_diagnoses = 0 self.time = 0 self.k = k self.atmost_cluse = []
print(' -s, --solver SAT solver to use') print(' Available values: g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = m22)') print(' -v, --verbose Be verbose') # #============================================================================== if __name__ == '__main__': solver, cardenc, verbose, files = parse_options() if files: # parsing the input formula if re.search('\.wcnf[p|+]?(\.(gz|bz2|lzma|xz))?$', files[0]): formula = WCNFPlus(from_file=files[0]) else: # expecting '*.cnf[,p,+].*' formula = CNFPlus(from_file=files[0]).weighted() with FM(formula, solver=solver, enc=cardenc, verbose=verbose) as fm: res = fm.compute() if res: print('s OPTIMUM FOUND') print('o {0}'.format(fm.cost)) if verbose > 2: print('v', ' '.join([str(l) for l in fm.model]), '0') else: print('s UNSATISFIABLE') if verbose > 1: print('c oracle time: {0:.4f}'.format(fm.oracle_time()))
def atleast(cls, lits, bound=1, top_id=None, vpool=None, encoding=EncType.seqcounter): """ This method can be used for creating a CNF encoding of an AtLeastK constraint, i.e. of :math:`\sum_{i=1}^{n}{x_i}\geq k`. The method takes 1 mandatory argument ``lits`` and 3 default arguments can be specified: ``bound``, ``top_id``, ``vpool``, and ``encoding``. :param lits: a list of literals in the sum. :param bound: the value of bound :math:`k`. :param top_id: top variable identifier used so far. :param vpool: variable pool for counting the number of variables. :param encoding: identifier of the encoding to use. :type lits: iterable(int) :type bound: int :type top_id: integer or None :type vpool: :class:`.IDPool` :type encoding: integer Parameter ``top_id`` serves to increase integer identifiers of auxiliary variables introduced during the encoding process. This is helpful when augmenting an existing CNF formula with the new cardinality encoding to make sure there is no collision between identifiers of the variables. If specified, the identifiers of the first auxiliary variable will be ``top_id+1``. Instead of ``top_id``, one may want to use a pool of variable identifiers ``vpool``, which is automatically updated during the method call. In many circumstances, this is more convenient than using ``top_id``. Also note that parameters ``top_id`` and ``vpool`` **cannot** be specified *simultaneusly*. The default value of ``encoding`` is :attr:`Enctype.seqcounter`. The method *translates* the AtLeast constraint into an AtMost constraint by *negating* the literals of ``lits``, creating a new bound :math:`n-k` and invoking :meth:`CardEnc.atmost` with the modified list of literals and the new bound. :raises CardEnc.NoSuchEncodingError: if encoding does not exist. :rtype: a :class:`.CNFPlus` object where the new \ clauses (or the new native atmost constraint) are stored. """ if encoding < 0 or encoding > 9: raise (NoSuchEncodingError(encoding)) assert not top_id or not vpool, \ 'Use either a top id or a pool of variables but not both.' # we are going to return this formula ret = CNFPlus() # if the list of literals is empty, return empty formula if not lits: return ret # obtaining the top id from the variable pool if vpool: top_id = vpool.top if not top_id: top_id = max(map(lambda x: abs(x), lits)) # Minicard's native representation is handled separately if encoding == 9: ret.atmosts, ret.nv = [([-l for l in lits], len(lits) - bound) ], top_id return ret # saving default SIGINT handler def_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_DFL) res = pycard.encode_atleast(lits, bound, top_id, encoding) # recovering default SIGINT handler def_sigint_handler = signal.signal(signal.SIGINT, def_sigint_handler) if res: ret.clauses, ret.nv = res # updating vpool if necessary if vpool: if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv: cls._update_vids(ret, vpool) else: vpool.top = ret.nv - 1 vpool._next() return ret
def atleast(cls, lits, bound=1, top_id=None, encoding=EncType.seqcounter): """ This method can be used for creating a CNF encoding of an AtLeastK constraint, i.e. of :math:`\sum_{i=1}^{n}{x_i}\geq k`. The method takes 1 mandatory argument ``lits`` and 3 default arguments can be specified: ``bound``, ``top_id``, and ``encoding``. :param lits: a list of literals in the sum. :param bound: the value of bound :math:`k`. :param top_id: top variable identifier used so far. :param encoding: identifier of the encoding to use. :type lits: iterable(int) :type bound: int :type top_id: integer or None :type encoding: integer Parameter ``top_id`` serves to increase integer identifiers of auxiliary variables introduced during the encoding process. This is helpful when augmenting an existing CNF formula with the new cardinality encoding to make sure there is no collision between identifiers of the variables. If specified the identifiers of the first auxiliary variable will be ``top_id+1``. The default value of ``encoding`` is :attr:`Enctype.seqcounter`. The method *translates* the AtLeast constraint into an AtMost constraint by *negating* the literals of ``lits``, creating a new bound :math:`n-k` and invoking :meth:`CardEnc.atmost` with the modified list of literals and the new bound. :raises CardEnc.NoSuchEncodingError: if encoding does not exist. :rtype: a :class:`.CNFPlus` object where the new \ clauses (or the new native atmost constraint) are stored. """ if encoding < 0 or encoding > 9: raise (NoSuchEncodingError(encoding)) # we are going to return this formula ret = CNFPlus() # if the list of literals is empty, return empty formula if not lits: return ret if not top_id: top_id = max(map(lambda x: abs(x), lits)) # Minicard's native representation is handled separately if encoding == 9: ret.atmosts, ret.nv = [([-l for l in lits], len(lits) - bound) ], top_id return ret # saving default SIGINT handler def_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_DFL) res = pycard.encode_atleast(lits, bound, top_id, encoding) # recovering default SIGINT handler def_sigint_handler = signal.signal(signal.SIGINT, def_sigint_handler) if res: ret.clauses, ret.nv = res return ret
class MinimalSubset_2: def __init__(self, k): self.map_atoms = {} self.atoms_counter = 1 self.cnf = CNFPlus() self.min_card = math.inf self.number_of_diagnoses = 0 self.time = 0 self.k = k self.atmost_cluse = [] def add_soft(self, c): if self.map_atoms.get(c) is None: self.map_atoms[c] = self.atoms_counter self.atoms_counter += 1 self.cnf.append([self.map_atoms[c], self.k]) def add_atmost(self, ls): self.atmost_cluse = ls def create_dictionary(self, statement): atoms = statement.atoms() for letter in atoms: if self.map_atoms.get(str(letter)) is None: self.map_atoms[str(letter)] = self.atoms_counter self.atoms_counter = self.atoms_counter + 1 def convert_letters_to_integer(self, atom): key = atom.replace('~', '') int_value = self.map_atoms[key] if '~' in atom: return int_value * -1 return int_value def convert_integer_to_letters(self, integer): for k, v in self.map_atoms.items(): if v == abs(integer): if integer < 0: return '~' + k else: return k def convert_statement(self, statement): statement_cnf = str(statement) statement_cnf = statement_cnf.replace('(', "") statement_cnf = statement_cnf.replace(')', "") list_statements = statement_cnf.split('&') literals_list = [] res = [] for s in list_statements: literals_list.append(s.split('|')) for k in literals_list: clu = [] for x_temp in k: x_temp = x_temp.replace(" ", "") clu.append(self.convert_letters_to_integer(x_temp)) res.append(clu) self.cnf.append(clu) return res def find_mini_card(self): solver = Minicard(bootstrap_with=self.cnf) solver.add_atmost(self.atmost_cluse, self.k) flag = True while flag: flag = solver.solve() ans = solver.get_model() if self.k == 0: self.k += 1 self.min_card = self.k break if not flag: self.k += 1 self.min_card = self.k + 1 break if flag: self.k = self.k - 1 solver = Minicard(bootstrap_with=self.cnf) solver.add_atmost(self.atmost_cluse, self.k) def run_solver(self): start_time = time.time() current_time = None self.find_mini_card() solver = Minicard(bootstrap_with=self.cnf) solver.add_atmost(self.atmost_cluse, self.k) while solver.solve(): ans = solver.get_model() ans = [self.convert_integer_to_letters(x) for x in ans] counter = 0 for x in ans: if x.startswith('~') and 'gate' in x: counter = counter + 1 solver.add_clause( [abs(self.convert_letters_to_integer(x))]) if counter == 0: break self.number_of_diagnoses = self.number_of_diagnoses + 1 current_time = time.time() if (current_time - start_time) >= 60: print('finish run out of time') self.time = np.nan self.min_card = np.nan self.number_of_diagnoses = np.nan return if current_time is None: current_time = time.time() self.time = current_time - start_time
""" print('Usage:', os.path.basename(sys.argv[0]), '[options] dimacs-file') print('Options:') print( ' -e, --enum=<int> Compute at most this number of models' ) print( ' Available values: [1 .. INT_MAX], all (default: 1)' ) print(' -h, --help Show this message') print(' -s, --solver=<string> SAT solver to use') print( ' Available values: cd, g3, g4, lgl, mcb, mcm, mpl, m22, mc, mgh (default = g3)' ) # #============================================================================== if __name__ == '__main__': # parsing command-line options to_enum, solver, files = parse_options() # reading an input formula either from a file or from stdin if files: formula = CNFPlus(from_file=files[0]) else: formula = CNFPlus(from_fp=sys.stdin) enumerate_models(formula, to_enum, solver)
def _build_clauses(initial, var, vpool): num_medics = initial['medics'] num_police = initial['police'] observations = initial['observations'] num_turns = len(observations) map = initial['observations'][0] num_rows = len(map) num_cols = len(map[0]) clauses = [] cell_types = ['S', 'H', 'U'] cell_types = cell_types + ['I'] if num_medics > 0 else cell_types cell_types = cell_types + ['Q'] if num_police > 0 else cell_types cnf = CNFPlus() observations = update_observations(observations, num_turns) for turn, obs in enumerate(observations): for i, row in enumerate(obs): for j, cell_type in enumerate(row): # always_relevant_clauses clauses.extend(_always_relevant_clauses(vpool, var, cell_types, cell_type, i, j, turn, num_turns, num_rows, num_cols)) # ###################### End of always true clauses ####################### # Make sure no I and Q on turn 0 if turn == 0: if num_medics > 0: clauses.append([-var(t='I', pos=(i, j), turn=0)]) if num_police > 0: clauses.append([-var(t='Q', pos=(i, j), turn=0)]) # Handle S timer, clauses related to police if turn + 1 < num_turns: s_timer_clauses = [] if num_police == 0: # If S2 at turn x --> S1 at turn x+1 s_timer_clauses.append([-var(t='S2', pos=(i, j), turn=turn), var('S1', pos=(i, j), turn=turn + 1)]) # If S1 at turn x --> S0 at turn x+1 s_timer_clauses.append([-var(t='S1', pos=(i, j), turn=turn), var('S0', pos=(i, j), turn=turn + 1)]) # If S0 at turn x --> H at turn x+1 s_timer_clauses.append([-var(t='S0', pos=(i, j), turn=turn), var('H', pos=(i, j), turn=turn + 1)]) else: # If S2 at turn x --> S1 or Q1 at turn x+1 s_timer_clauses.append([-var(t='S2', pos=(i, j), turn=turn), var('S1', pos=(i, j), turn=turn + 1), var('Q1', pos=(i, j), turn=turn + 1)]) # If S1 at turn x --> S0 or Q1 at turn x+1 s_timer_clauses.append([-var(t='S1', pos=(i, j), turn=turn), var('S0', pos=(i, j), turn=turn + 1), var('Q1', pos=(i, j), turn=turn + 1)]) # If S0 at turn x --> H or Q1 at turn x+1 s_timer_clauses.append([-var(t='S0', pos=(i, j), turn=turn), var('H', pos=(i, j), turn=turn + 1), var('Q1', pos=(i, j), turn=turn + 1)]) clauses.extend(s_timer_clauses) # Handle Q timer if num_police > 0: if turn == 1: # Q >> Q1 q_timer_clauses = [[-var('Q', pos=(i, j), turn=turn), var('Q1', pos=(i, j), turn=turn)]] elif turn > 1: # Q >> (Q0 | Q1) q_timer_clauses = [[-var('Q', pos=(i, j), turn=turn), var('Q0', pos=(i, j), turn=turn), var('Q1', pos=(i, j), turn=turn)]] if turn >= 1: # (Q0 >> Q) & (Q1 >> Q) for k in range(2): q_timer_clauses.append([-var(t=f'Q{k}', pos=(i, j), turn=turn), var(t='Q', pos=(i, j), turn=turn)]) # not both Q0 and Q1 q_timer_clauses.append([-var(t=f'Q0', pos=(i, j), turn=turn), -var(t=f'Q1', pos=(i, j), turn=turn)]) if turn + 1 < num_turns: # If Q1 at turn x --> Q0 at turn x+1 q_timer_clauses.append([-var(t='Q1', pos=(i, j), turn=turn), var('Q0', pos=(i, j), turn=turn + 1)]) # If Q0 at turn x --> H at turn x+1 q_timer_clauses.append([-var(t='Q0', pos=(i, j), turn=turn), var('H', pos=(i, j), turn=turn + 1)]) # If Q0 at turn x+1 --> Q1 at turn x q_timer_clauses.append([-var(t='Q0', pos=(i, j), turn=turn + 1), var('Q1', pos=(i, j), turn=turn)]) # If Q1 at turn x+1 --> S at turn x clauses.append([-var('Q1', pos=(i, j), turn=turn + 1), var('S', pos=(i, j), turn=turn)]) clauses.extend(q_timer_clauses) if turn + 1 < num_turns: # If I at turn x --> I at turn x+1 if num_medics > 0: clauses.append([-var('I', pos=(i, j), turn=turn), var('I', pos=(i, j), turn=turn + 1)]) # if H at turn x --> I or S or H at turn x+1 if num_medics > 0: clauses.append([-var('H', pos=(i, j), turn=turn), var('I', pos=(i, j), turn=turn + 1), var('S', pos=(i, j), turn=turn + 1), var('H', pos=(i, j), turn=turn + 1)]) else: clauses.append([-var('H', pos=(i, j), turn=turn), var('S', pos=(i, j), turn=turn + 1), var('H', pos=(i, j), turn=turn + 1)]) add_H_clause = True if add_H_clause: """ H_clause if H and all neighbours not S (after bound checking) at turn x --> H or I (if num medcis >0) at turn x+1 to_cnf((a & (b &c&d&e)) >> (f|g), simplify=True) f∨g∨¬a∨¬b∨¬c∨¬d∨¬e """ H_clause = [-var('H', pos=(i, j), turn=turn), var('H', pos=(i, j), turn=turn + 1)] if num_medics > 0: H_clause.append(var('I', pos=(i, j), turn=turn + 1)) if i + 1 < num_rows: H_clause.append(var('S', pos=(i + 1, j), turn=turn)) if i - 1 >= 0: H_clause.append(var('S', pos=(i - 1, j), turn=turn)) if j + 1 < num_cols: H_clause.append(var('S', pos=(i, j + 1), turn=turn)) if j - 1 >= 0: H_clause.append(var('S', pos=(i, j - 1), turn=turn)) clauses.append(H_clause) add_S_clauses = True if add_S_clauses: """ S_clauses if num police = 0 and num medic =0: If H and atleast one neighbour is S at turn x --> S2 at turn x+1 if num police > 0 and num medic =0: If H and atleast one neighbour is S at turn x --> S2 or H at turn x+1 if num police = 0 and num medic >0: If H and atleast one neighbour is S at turn x --> S2 or I at turn x+1 if num police > 0 and num medic >0: If H and atleast one neighbour is S at turn x --> S2 or H or I at turn x+1 to_cnf(((a & (b |c|d))) >> (f|g), simplify=True) (f∨g∨¬a∨¬b)∧(f∨g∨¬a∨¬c)∧(f∨g∨¬a∨¬d) """ S_clauses = [] if i + 1 < num_rows: prop1 = [-var('S', pos=(i + 1, j), turn=turn), -var('H', pos=(i, j), turn=turn), var('S2', pos=(i, j), turn=turn + 1)] if num_medics > 0: prop1.append(var('I', pos=(i, j), turn=turn + 1)) if num_police > 0: prop1.append(var('H', pos=(i, j), turn=turn + 1)) S_clauses.append(prop1) if i - 1 >= 0: prop2 = [-var('S', pos=(i - 1, j), turn=turn), -var('H', pos=(i, j), turn=turn), var('S2', pos=(i, j), turn=turn + 1)] if num_medics > 0: prop2.append(var('I', pos=(i, j), turn=turn + 1)) if num_police > 0: prop2.append(var('H', pos=(i, j), turn=turn + 1)) S_clauses.append(prop2) if j + 1 < num_cols: prop3 = [-var('S', pos=(i, j + 1), turn=turn), -var('H', pos=(i, j), turn=turn), var('S2', pos=(i, j), turn=turn + 1)] if num_medics > 0: prop3.append(var('I', pos=(i, j), turn=turn + 1)) if num_police > 0: prop3.append(var('H', pos=(i, j), turn=turn + 1)) S_clauses.append(prop3) if j - 1 >= 0: prop4 = [-var('S', pos=(i, j - 1), turn=turn), -var('H', pos=(i, j), turn=turn), var('S2', pos=(i, j), turn=turn + 1)] if num_medics > 0: prop4.append(var('I', pos=(i, j), turn=turn + 1)) if num_police > 0: prop4.append(var('H', pos=(i, j), turn=turn + 1)) S_clauses.append(prop4) clauses.extend(S_clauses) add_h_then_h_clause = True if add_h_then_h_clause: # If H at turn x and H at turn x+1 --> each neighbour in turn x was: # 1. not S # or # 2. was S at turn x # but Q at turn x+1 h_then_h_clause = [-var('H', pos=(i, j), turn=turn), -var('H', pos=(i, j), turn=turn + 1)] if i + 1 < num_rows: h_then_h_clause_d = h_then_h_clause.copy() h_then_h_clause_d.append(-var('S', pos=(i + 1, j), turn=turn)) if num_police > 0: h_then_h_clause_d.append(var('Q', pos=(i + 1, j), turn=turn + 1)) clauses.append(h_then_h_clause_d) if i - 1 >= 0: h_then_h_clause_u = h_then_h_clause.copy() h_then_h_clause_u.append(-var('S', pos=(i - 1, j), turn=turn)) if num_police > 0: h_then_h_clause_u.append(var('Q', pos=(i - 1, j), turn=turn + 1)) clauses.append(h_then_h_clause_u) if j + 1 < num_cols: h_then_h_clause_r = h_then_h_clause.copy() h_then_h_clause_r.append(-var('S', pos=(i, j + 1), turn=turn)) if num_police > 0: h_then_h_clause_r.append(var('Q', pos=(i, j + 1), turn=turn + 1)) clauses.append(h_then_h_clause_r) if j - 1 >= 0: h_then_h_clause_l = h_then_h_clause.copy() h_then_h_clause_l.append(-var('S', pos=(i, j - 1), turn=turn)) if num_police > 0: h_then_h_clause_l.append(var('Q', pos=(i, j - 1), turn=turn + 1)) clauses.append(h_then_h_clause_l) if turn > 0: # because there are no Q or I at turn 0 if num_medics >= 1 or num_police >= 1: # parse map for ? (of different types) and Q cells unk_was_unk = [] unk_was_not_s_nor_unk = [] unk_was_s = [] unk_was_s_or_unk = [] it_is_q = [] it_is_i = [] unk_was_h = [] new_i = [] was_h = [] was_s = [] unk_was_not_h_nor_unk = [] for i, row in enumerate(obs): for j, cell_type in enumerate(row): cell_in_prev_turn = observations[turn - 1][i][j] if cell_type == '?': if cell_in_prev_turn == 'S': unk_was_s.append((cell_type, (i, j))) elif cell_in_prev_turn == '?': unk_was_unk.append((cell_type, (i, j))) else: unk_was_not_s_nor_unk.append((cell_type, (i, j))) if cell_in_prev_turn == 'H': unk_was_h.append((cell_type, (i, j))) elif cell_in_prev_turn != '?': unk_was_not_h_nor_unk.append((cell_type, (i, j))) elif cell_type == 'Q': it_is_q.append((cell_type, (i, j))) elif cell_type == 'I': it_is_i.append((cell_type, (i, j))) if cell_in_prev_turn == 'H': new_i.append((cell_type, (i, j))) if cell_in_prev_turn == 'H': was_h.append((cell_type, (i, j))) elif cell_in_prev_turn == 'S': was_s.append((cell_type, (i, j))) unk_was_s_or_unk.extend(unk_was_s) unk_was_s_or_unk.extend(unk_was_unk) if num_police >= 1: # if Q and was S before, or if Q and is Q next turn num_certain_q1 = 0 for i, row in enumerate(obs): for j, cell_type in enumerate(row): cell_in_prev_turn = observations[turn - 1][i][j] if cell_type == 'Q' and \ (cell_in_prev_turn == 'S' or (turn + 1 < num_turns and observations[turn + 1][i][j] == 'Q')): num_certain_q1 += 1 num_possible_q = num_police - num_certain_q1 # Q1 at turn x --> S or ? at turn x-1 # Can be Q1 only if was S or ? the turn before # Translated to if it was not S and not ? then it is not Q1 for cell_type, cell_pos in unk_was_not_s_nor_unk: clauses.append([-var('Q1', pos=cell_pos, turn=turn)]) # at most num_police cells are Q1 at this turn, among all cells that are Q or ?(that where S or ?) possible_q1 = it_is_q.copy() possible_q1.extend(unk_was_s_or_unk) atmost_q1_among_possible_q = CardEnc.atmost(lits=[var('Q1', pos=(i, j), turn=turn) for _, (i, j) in possible_q1], vpool=vpool, bound=num_police).clauses cnf.extend(atmost_q1_among_possible_q) # If we didn't see the Q1 then any one of ? can be Q1 aka atmost num_possible_q is Q1. atmost_q1_among_unk = CardEnc.atmost(lits=[var('Q1', pos=(i, j), turn=turn) for _, (i, j) in unk_was_s_or_unk], vpool=vpool, bound=num_possible_q).clauses cnf.extend(atmost_q1_among_unk) # at turn x: atleast K cells are Q1 among all map - Q or (? that were (S or ?))t # K = min(num_police, #S prev turn) # possible_q1_among_unk = ? that was S or ? num_atleast_possible_q = min(num_police, len(was_s)) atleast_q1 = CardEnc.atleast(lits=[var('Q1', pos=(i, j), turn=turn) for _, (i, j) in possible_q1], vpool=vpool, bound=num_atleast_possible_q).clauses cnf.extend(atleast_q1) # at turn x: atleast K cells become Q1 among possible_q1_among_unk # K = min(max(num_police - #new_Q1 - #is?was?, 0), #?_was_S) # possible_i_among_unk = ? that was H or ? possible_q1_among_unk = unk_was_s_or_unk num_atleast_q1_among_unk = min(max(num_police-num_certain_q1-len(unk_was_unk), 0), len(unk_was_s)) atleast_q1_among_unk_clause = CardEnc.atleast(lits=[var('I', pos=(i, j), turn=turn) for _, (i, j) in possible_q1_among_unk], vpool=vpool, bound=num_atleast_q1_among_unk).clauses cnf.extend(atleast_q1_among_unk_clause) if num_medics >= 1: # at most turn*num_medics among possible_i_among_unk can be I # possible_i_among_unk is ? that was H or ? num_known_i = len(it_is_i) num_new_i = len(new_i) possible_i_among_unk = unk_was_unk.copy() possible_i_among_unk.extend(unk_was_h) num_atmost_i_among_unk = min(num_medics * turn - num_known_i, num_medics - num_new_i) atmost_i_among_possible_i = CardEnc.atmost(lits=[var('I', pos=(i, j), turn=turn) for _, (i, j) in possible_i_among_unk], vpool=vpool, bound=num_atmost_i_among_unk).clauses cnf.extend(atmost_i_among_possible_i) # all other ? cannot be I for cell_type, cell_pos in unk_was_not_h_nor_unk: clauses.append([-var('I', pos=cell_pos, turn=turn)]) # at turn x: atleast K cells are I among all map (possible_i_among_unk and I) # K = min(num_medics * x, #H prev turn) # possible_i_among_unk = ? that was H or ? num_atleast_i = min(num_medics * turn, len(was_h)) possible_i = possible_i_among_unk.copy() possible_i.extend(it_is_i) atleast_i_clause = CardEnc.atleast(lits=[var('I', pos=(i, j), turn=turn) for _, (i, j) in possible_i], vpool=vpool, bound=num_atleast_i).clauses cnf.extend(atleast_i_clause) # at turn x: atleast K cells become I among possible_i_among_unk # K = min(max(num_medics - #new_I - #is?was?, 0), #?_was_h) # possible_i_among_unk = ? that was H or ? num_atleast_i_among_unk = min(max(num_medics-len(new_i)-len(unk_was_unk), 0), len(unk_was_h)) atleast_i_among_unk_clause = CardEnc.atleast(lits=[var('I', pos=(i, j), turn=turn) for _, (i, j) in possible_i_among_unk], vpool=vpool, bound=num_atleast_i_among_unk).clauses cnf.extend(atleast_i_among_unk_clause) cnf.extend(clauses) return cnf