def create_sat_problem(sat_required, *args): """ Creates a new instance of a SAT problem and returns the list of clauses. :param sat_required: is the problem instance required to be satisfiable? :param args: list of arguments as if cnfgen is being called from the command line :return: """ # calls cnfgen to generate an instance # TODO use cnfgen programatically, if possible command_array = ['cnfgen', '-q', '-o', 'tmp.cnf'] + [str(a) for a in args] subprocess.call(command_array) f = CNF(from_file='tmp.cnf') if not sat_required: os.remove('tmp.cnf') return f.clauses else: # the instance must be satisfiable, will check with Pysat's solver while True: with Solver(name='Glucose3', bootstrap_with=f.clauses) as solver: if solver.solve(): # instance is satisfiable, return it os.remove('tmp.cnf') return f.clauses else: # generates a new instance subprocess.call(command_array) f = CNF(from_file='tmp.cnf')
def test_from_dataset(self): """ Simple test with a 2-instance dataset :return: """ dataset = [ 'instances/sat_00001_k3_v20_c91.cnf', 'instances/sat_00002_k3_v20_c91.cnf' ] env = MultiSATEnv(LocalSearchSAT, from_dataset=dataset) self.assertEqual(dataset, env.dataset) self.assertEqual(0, env.dataset_index) # at reset, the environment will load the first instance env.reset() self.assertEqual( CNF(dataset[0]).clauses, env.current_instance.original_clauses) self.assertEqual(1, env.dataset_index) # now the second instance env.reset() self.assertEqual( CNF(dataset[1]).clauses, env.current_instance.original_clauses) self.assertEqual(0, env.dataset_index) # index wrapped around # first instance again (wrapped) env.reset() self.assertEqual( CNF(dataset[0]).clauses, env.current_instance.original_clauses) self.assertEqual(1, env.dataset_index)
def solve_sudoku_SAT(sudoku, k): num_vertices = k ** 4 vertices, edges = make_sudoku_graph(k) number_colors = k ** 2 formula = CNF() # Assign a positive integer for each propositional variable. def var_number(i, c): return ((i - 1) * number_colors) + c flatten_sudoku = np.array(sudoku).reshape(num_vertices) # Clause that ensures that each vertex there is one value. for i in range(1, num_vertices + 1): clause = [] sudoku_value = int(flatten_sudoku[i - 1]) not_assigned = sudoku_value == 0 if not_assigned: for c in range(1, number_colors + 1): clause.append(var_number(i, c)) else: clause.append(var_number(i, sudoku_value)) formula.append(clause) # Ensure that only one value is assigned. for i in range(1, num_vertices + 1): for c1 in range(1, number_colors + 1): for c2 in range(c1 + 1, number_colors + 1): clause = [-1 * var_number(i, c1), -1 * var_number(i, c2)] formula.append(clause) # Ensure that the rules of sudoku are kept, no adjacent vertices should have the same color/value. for (v1, v2) in edges: for c in range(1, number_colors + 1): clause = [-1 * var_number(v1, c), -1 * var_number(v2, c)] formula.append(clause) solver = MinisatGH() solver.append_formula(formula) answer = solver.solve() flatten_vertices = np.array(vertices).reshape(num_vertices) if answer: print("The sudoku is solved.") model = solver.get_model() print(model) for i in range(1, num_vertices + 1): for c in range(1, number_colors + 1): if var_number(i, c) in model: flatten_vertices[i - 1] = c return flatten_vertices.reshape(k**2, k**2).tolist() else: print("The sudoku has no solution.") return None
def load_from(self, infile): """ Loads the encoding from an input file. """ self.enc = CNF(from_file=infile) # empty intervals for the standard encoding self.intvs, self.imaps, self.ivars = {}, {}, {} for line in self.enc.comments: if line.startswith('c i ') and 'none' not in line: f, arr = line[4:].strip().split(': ', 1) f = f.replace('-', '_') self.intvs[f], self.imaps[f], self.ivars[f] = [], {}, [] for i, pair in enumerate(arr.split(', ')): ub, symb = pair.split(' <-> ') ub = ub.strip('"') symb = symb.strip('"') if ub[0] != '+': ub = float(ub) self.intvs[f].append(ub) self.ivars[f].append(symb) self.imaps[f][ub] = i elif line.startswith('c features:'): self.feats = line[11:].strip().split(', ') elif line.startswith('c classes:'): self.nofcl = int(line[10:].strip())
def RandKCNF(k, n, m): clauses = [ randomly_flip(random.sample(range(1, int(n) + 1), k)) for _ in range(int(m)) ] return CNF(from_clauses=clauses)
def gen_formula(self): try: cnf = CNF(from_file=self.files[self.file_index]) except IndexError: raise StopIteration self.file_index += 1 return cnf
def is_satisfiable(e): formula = CNF() clauses, _, _ = _tseitin(e) for clause in clauses: formula.append(clause) with Lingeling(bootstrap_with=formula.clauses) as ling: return ling.solve()
def __init__(self): """ Constructor of the class. Always create an empty solver """ self.formula = CNF() self.quantifiers = [] self.propagate = []
def sample_SRC_aux(n, u1, c_idx, l_idx, p_geo=0.4, p_binom=0.7): """ Args: n: positive integer u1: an unsat core c_idx: a clause index l_idx: a literal index u1 must become sat if the literal at (c_idx, l_idx) is flipped. Note that if result, vs = sample_SR_aux(args...), then result + vs is a valid argument for u1 Returns: a random formula drawn from n variables containing u1 as an unsat core, and the unsat core """ result = CNF() u2 = u1.copy() u2.clauses[c_idx][l_idx] = -u2.clauses[c_idx][l_idx] with Solver(name="cdl") as S: while True: S.append_formula(u2) k = 1 + np.random.binomial(n=1, p=p_binom) + np.random.geometric(p_geo) vs = np.random.choice(n, size=min(n, k), replace=False) vs = [ int(v + 1) if random.random() < 0.5 else int(-(v + 1)) for v in vs ] S.add_clause(vs) if S.solve(): result.append(vs) else: break for cls in u1.clauses: result.append(cls) return result, u1 # TODO(jesse): output a core clause mask
def sample(self, cnf_file, max_samples=1000): """ Uses Unigen2 (https://bitbucket.org/kuldeepmeel/unigen) to generate a dataset :param cnf_file: :param max_samples: :return: """ # makes sure that unigen working dir exists for positive and negative samples pos_dir = os.path.join(self.tmp_dir, 'positives') neg_dir = os.path.join(self.tmp_dir, 'negatives') os.makedirs(pos_dir, exist_ok=True) os.makedirs(neg_dir, exist_ok=True) # gets f from the cnf file f = CNF(cnf_file) self.formula = f print("Generating positive instances.") self.run_unigen(cnf_file, pos_dir, max_samples) # the file with the positive samples is pos_dir/cnf_file_0.txt cnf_name = os.path.splitext(os.path.basename(cnf_file))[0] positives = self.retrieve_samples( os.path.join(pos_dir, f'{cnf_name}_0.txt')) print(f'Sampled {len(positives)} unique positive instances') return positives # prepare_dataset(positives, negatives, ds_path)
def sample_SR_aux(n, min_cls_len=1, p_binom=0.7, p_geo=0.4): """ Args: n: positive integer Returns: A randomly-generated formula and a clause which makes the formula unsat This procedure has no guarantees on the number of clauses in the formula. Reference implementation in source code of NeuroSAT: https://github.com/dselsam/neurosat/blob/master/python/gen_sr_dimacs.py """ result = CNF() with Solver(name="cdl") as S: while True: k = min_cls_len + np.random.binomial( n=1, p=p_binom) + np.random.geometric(p_geo) vs = np.random.choice(n, size=min(n, k), replace=False) vs = [ int(v + 1) if random.random() < 0.5 else int(-(v + 1)) for v in vs ] S.add_clause(vs) if S.solve(): result.append(vs) else: break return result, vs
def test_unigen_generate_dataset_small_formula(self): cnf_file = '/tmp/test_small.cnf' f = CNF(from_clauses=[[1, 2, 3], [4]]) ''' the formula above has 7 positive samples: positives = { (1, 2, 3, 4), (1, 2, -3, 4), (1, -2, 3, 4), (1, -2, -3, 4), (-1, 2, 3, 4), (-1, 2, -3, 4), (-1, -2, 3, 4), } ''' f.to_file(cnf_file) sampler = mlbf.positives.UnigenSampler() # a formula with very few solutions will return an empty dataset data = sampler.sample(cnf_file, 50) self.assertEqual(0, len(data)) # deletes the temp file used to store the formula os.unlink(cnf_file)
def solve_ins(instance): print(f"Solving using insertion: {instance}", flush=True) f = CNF(from_file=instance).clauses global calls calls = 0 mus = list() while len(f) > 0: s = mus.copy() ci = 0 clause = None while solve_CNF(s): clause = f[ci] s.append(clause) ci += 1 if clause == None: break mus.append(clause) s.pop(-1) f = s return (calls, mus)
def solve_ins_csr(instance): print(f"Solving using insertion and clause-set refinement: {instance}", flush=True) f = CNF(from_file=instance).clauses global calls calls = 0 mus = list() while len(f) > 0: solve_with = mus.copy() solve_with.extend(f) if len(solve_with) == 0: break c = solve_with.pop(-1) res = solve_CNF_core(solve_with) print(res) if res[0]: mus.append(c) f.pop(-1) else: f = res[1] for m in mus: f.remove(m) return (calls, mus)
def proccess_sat_file(filename, sat, solver): start_time = time.time() result = "{} || solver {} ".format(filename, solver) formula = CNF(from_file="./InstanciasSAT/" + filename) clauses = formula.clauses[:] nv = formula.nv original_time = time.time() original_solution = solve_clauses(clauses, solver) result += "|| original: {} || Tiempo: {:.10f} segundos ".format(original_solution, time.time() - original_time) clauses, nv = sat_to_3_sat(clauses, nv) if sat > 3: x_sat = 3 while x_sat < sat: clauses, nv = reduce_to_x_sat(clauses, nv) x_sat += 1 x_sat_time = time.time() x_sat_solution = solve_clauses(clauses, solver) result += "|| {}-SAT: {} || Tiempo: {:.10f} segundos ".format(sat, x_sat_solution, time.time() - x_sat_time) formula.clauses = clauses formula.nv = nv formula.to_file("./X-SAT/" + filename) result += "|| Tiempo total: {:.10f} segundos".format(time.time() - start_time) print(result)
def __init__(self, lits=[], ubound=1, top_id=None): """ Constructor. """ # internal totalizer object self.tobj = None # its characteristics self.lits = [] self.ubound = 0 self.top_id = 0 # encoding result self.cnf = CNF() # CNF formula encoding the totalizer object self.rhs = [] # upper bounds on the number of literals (rhs) # number of new clauses self.nof_new = 0 # this newly created totalizer object is not yet merged in any other self._merged = False if lits: self.new(lits=lits, ubound=ubound, top_id=top_id)
def U_tile_dynamics(self): clauses = [] for i in range(self.rows): for j in range(self.cols): for t in range(self.t_max + 1): # first if t == 0: clauses.append([ -self.obj2id[f"U_{i}_{j}^{t}"], self.obj2id[f"U_{i}_{j}^{t + 1}"] ]) # middle if t > 0 and t != self.t_max: clauses.append([ -self.obj2id[f"U_{i}_{j}^{t}"], self.obj2id[f"U_{i}_{j}^{t + 1}"] ]) clauses.append([ -self.obj2id[f"U_{i}_{j}^{t}"], self.obj2id[f"U_{i}_{j}^{t - 1}"] ]) # last if t == self.t_max: clauses.append([ -self.obj2id[f"U_{i}_{j}^{t}"], self.obj2id[f"U_{i}_{j}^{t - 1}"] ]) return CNF(from_clauses=clauses)
def reduce_board(size: int, rows: [[int]], cols: [[int]]): """Reduces the the description of a Nonogram puzzle to a CNF formula :param size: The number of cells in each row/column :param rows: The hints for each row :param cols: The hints for each column :return: A CNF object representing the board's possible solutions """ clauses = [] base = size * size for i, row in enumerate(rows): uvars = [j + (i * size) + 1 for j in range(size)] reduction = reduce_set(size, row, uvars, base) clauses += reduction.clauses base += len(reduction.auxvars) for i, col in enumerate(cols): uvars = [i + (j * size) + 1 for j in range(size)] reduction = reduce_set(size, col, uvars, base) clauses += reduction.clauses base += len(reduction.auxvars) cnf = CNF() for clause in clauses: cnf.append(clause) return cnf
def resolver(passo: int, conjuntoVertices: bool = False, ordemVertices: bool = True): """Resolve o caminho eureliano do grafo. :param passo: Recebe um array contendo os primeiros passos de cada possível começo do problema :param conjuntoVertices: Exibir o uma lista fora de ordem com o conjunto dos vértices que resolvem o problema. (Default value = False) :param ordemVertices: Exibir o caminho pelos vértices a ser percorrido para resolver o problema. (Default value = True) """ formula = CNF() g = Glucose4() getClausulas(matriz, formula) formula.append([passo]) g.append_formula(formula) print( f'O passo inicial para prova é: \33[32m{passo}\33[m, o caminho com ele é \33[34m{g.solve()}\33[m' ) model = g.get_model() if model: valoracaoValida = valoresValidos(model) print(f'Passos válidos: \33[35m{valoracaoValida}\33[m') if conjuntoVertices: print( f'Esse são os conjuntos de vértices usados (fora de ordem): \33[36m{caminhosUsados(model)}\33[m' ) if ordemVertices: print(f"Ordem de passagem pelos vértices: ") printEmOrdem(valoracaoValida) print()
def read_observations(self): clauses = [] for t in range(self.num_observations): for i in range(self.rows): for j in range(self.cols): if self.observations[t][i][j] == "S": clauses.append([ self.obj2id[f"S0_{i}_{j}^{t}"], self.obj2id[f"S1_{i}_{j}^{t}"], self.obj2id[f"S2_{i}_{j}^{t}"] ]) continue if self.observations[t][i][j] == "Q": clauses.append([ self.obj2id[f"Q0_{i}_{j}^{t}"], self.obj2id[f"Q1_{i}_{j}^{t}"] ]) continue if self.observations[t][i][j] == "U": clauses.append([self.obj2id[f"U_{i}_{j}^{t}"]]) continue if self.observations[t][i][j] == "H": clauses.append([self.obj2id[f"H_{i}_{j}^{t}"]]) continue if self.observations[t][i][j] == "I": clauses.append([ self.obj2id[f"I0_{i}_{j}^{t}"], self.obj2id[f"I_{i}_{j}^{t}"] ]) continue return CNF(from_clauses=clauses)
def build_cnf(self): self.formula = CNF() colors = list(range(1, self.ncolors + 1)) for n1, n2 in self.g.edges(): for c in colors: self.formula.append( [-self.cmap.enc(n1, c), -self.cmap.enc(n2, c)]) #specials = [28, 194, 242, 355, 387, 397, 468] #ii = 1 #for n in specials: # self.formula.append([self.cmap.enc(n, ii)]) # ii += 1 for n in self.g.nodes(): #if not n in specials: self.formula.append([self.cmap.enc(n, c) for c in colors]) for c1 in colors: for c2 in colors: if c1 < c2: self.formula.append( [-self.cmap.enc(n, c1), -self.cmap.enc(n, c2)]) return self.formula
def get_formula(observations, decision_variables_p, p_to_atom, decision_variables_a, a_to_atom, no_police, no_medics, indices_list, state_codes, no_rounds): formula = CNF() initial_state_clauses = get_initial_state_clauses(observations, decision_variables_p, p_to_atom, state_codes, indices_list) action_precondition_clauses = get_action_precondition_clauses( decision_variables_a, p_to_atom, a_to_atom, no_police, no_medics, indices_list) action_interference_clauses = get_action_interference_clauses( decision_variables_a, a_to_atom, state_codes, no_rounds) fact_achievement_clauses = get_fact_achievement_clauses( decision_variables_p, p_to_atom, decision_variables_a, a_to_atom) permanent_clauses = get_permanent_clauses(p_to_atom, decision_variables_a, a_to_atom, no_police, no_medics, indices_list, state_codes) clauses = initial_state_clauses + action_precondition_clauses + action_interference_clauses +\ fact_achievement_clauses + permanent_clauses formula.extend(clauses) return formula
def build_cnf(self, ): self.formula = CNF() colors = list(range(1, self.ncolors + 1)) for clique in nx.find_cliques(self.g): col = 1 for v in clique: self.formula.append([self.cmap.enc(v, col)]) col += 1 break for n1, n2 in self.g.edges(): for c in colors: self.formula.append([-self.cmap.enc(n1, c), -self.cmap.enc(n2, c)]) for n in self.g.nodes(): #if not n in specials: self.formula.append([self.cmap.enc(n, c) for c in colors]) #for c1 in colors: # for c2 in colors: # if c1 < c2: # self.formula.append([-self.cmap.enc(n, c1), -self.cmap.enc(n, c2)]) return self.formula
def __init__(self, cnf_file=None, formula=None, choice_function=None): """ Creates a DPLL search instance. Either the cnf_file or the formula must be supplied :param cnf_file: path to a .cnf file :param formula: pysat.formula.CNF instance :param choice_function: function that receives a formula (pysat.formula.CNF) and a model (dict(var->assignment in DIMACS notation)) and chooses the next literal to branch on """ if cnf_file is None and formula is None: raise ValueError('Please provide either a cnf file or a formula') self.choose_literal = choice_function if choice_function is not None else dpll.choose_random_literal self.formula = formula if formula is not None else CNF( from_file=cnf_file) self.n_vars = self.formula.nv self.statistics = { 'branches': 0, 'unit_propagations': 0, 'purifications': 0, 'up_clauses_cleaned': 0, 'up_literals_cleaned': 0, } self.solved = False self.model_count = 0
def solve_sudoku_SAT(sudoku, k): ############# # this solution is adjusted from https://github.com/taufanardi/sudoku-sat-solver/blob/master/Sudoku.py # what I have done differently: # 1. Adjusted so that it can generate to k-sized problem, not just hardcoded k=3 in the original post # 2. Refactored the code to make it more readable and splitted into smaller functions instead of chunk of code # 3. Rewrited the `add_distinct_clauses` code to make it more robust and easy to understand ############# # make clauses clauses = create_clauses(sudoku, k) # append clauses to formula formula = CNF() for c in clauses: formula.append(c) # solve the SAT problem solver = MinisatGH() solver.append_formula(formula) answer = solver.solve() if not answer: return None # get the solution solution = solver.get_model() # reformat the solution into a suduko representation for i in range(1, k**2 + 1): for j in range(1, k**2 + 1): sudoku[i - 1][j - 1] = extract_digit_from_solution( i, j, solution, k) return sudoku
def _optimize(epeg, constraints, top_id, lower_bound, upper_bound, model): if lower_bound <= upper_bound: cost = int((2 * lower_bound + upper_bound) / 3) lits = [] weights = [] for key, value in epeg.nodes.items(): lits.append(key) weights.append(value.cost) object_function = PBEnc.leq(lits=lits, weights=weights, bound=cost, top_id=top_id) cnf = CNF(from_clauses=constraints.clauses + object_function.clauses) solver = Minisat22() solver.append_formula(cnf.clauses) if solver.solve(): model = solver.get_model() return _optimize(epeg, constraints, top_id, lower_bound, cost - 1, model) else: return _optimize(epeg, constraints, top_id, cost + 1, upper_bound, model) return model, lower_bound
def __init__(self, dimacs_file, csv_file, verbose=False): self.__dimacs = DimacsFile(dimacs_file) self.__csv = CSVFile(csv_file) self.__cnf = CNF(from_file=dimacs_file) self.__formula = Solver(bootstrap_with=self.__cnf.clauses) assert self.__formula.solve() is True, "initial formula is UNSAT" self.__config_d = None self.__verbose = verbose
def test_step_flips_correct_var(self): f = CNF(from_clauses=[[-1, -2], [2], [2, -3, -4]]) env = LocalSearchSAT(f.clauses) exp_model = np.copy(env.values) # flips the first var in the expected model and compares if it is equal to the environment's obs, reward, done, info = env.step(0) exp_model[0] = -exp_model[0] self.assertTrue((exp_model == obs['values']).all(), f'exp=\n{exp_model}\nactual=\n{obs["values"]}')
def unsat_core_example(): fmla = CNF() fmla.append([1, 2, 3]) fmla.append([-1, 2, 3]) fmla.append([2, -3]) fmla.append([-2, -3]) fmla.append([-2, 3]) return fmla
def validate_get_unsat_core_pysat(fmla, result, bad_asms): core = CNF(from_clauses=[fmla.clauses[i] for i in result]) for asm in bad_asms: core.append([asm]) with Solver(name="cdl") as S: S.append_formula(core) assert S.solve() is False print("ok")