def test_atmostk(): for l in range(5, 10): for k in range(2, l): for e in encs: cnf = CardEnc.atmost(lits=list(range(1, l + 1)), bound=k, encoding=getattr(EncType, e)) # enumerating all models with MinisatGH(bootstrap_with=cnf) as solver: for num, model in enumerate(solver.enum_models(), 1): solver.add_clause([-l for l in model[:l]]) assert num == sum([bincoeff(l, o + 1) for o in range(k)]) + 1, 'wrong number of models for AtMost-{0}-of-{1} ({2})'.format(k, l, e)
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 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 test_atmost1(): encs = list( filter(lambda name: not name.startswith('__') and name != 'native', dir(EncType))) for l in range(10, 20): for e in encs: cnf = CardEnc.atmost(lits=list(range(1, l + 1)), bound=1, encoding=getattr(EncType, e)) # enumerating all models with MinisatGH(bootstrap_with=cnf) as solver: for num, model in enumerate(solver.enum_models(), 1): solver.add_clause([-l for l in model[:l]]) assert num == l + 1, 'wrong number of models for AtMost-1-of-{0} ({1})'.format( l, e)
def solve_sudoku_SAT(sudoku, k): """ I used the rules described in here http://anytime.cs.umass.edu/aimath06/proceedings/P34.pdf """ k2 = k**2 N_propositionals = k2**3 propositions = list(range(1, N_propositionals + 1)) propositions = np.array(propositions).reshape((k2, k2, k2)) rules = CNF() ## These rules add the values we know to be true for i, row in enumerate(sudoku): for j, item in enumerate(row): props = list(propositions[i, j]) if item > 0: rules.append([int(props[item - 1])]) ## first rule each entry has a value, if it is set, that value must be True for i, row in enumerate(sudoku): for j, item in enumerate(row): rules.append([int(item) for item in propositions[i, j, :]]) ## second rule each row value appears once for y in range(k2): for z in range(k2): for x in range(k2 - 1): for i in range(x + 1, k2): props = [propositions[x, y, z], propositions[i, y, z]] rules.append([-int(item) for item in props]) ## third rule each column value appears once for x in range(k2): for z in range(k2): for y in range(k2 - 1): for i in range(y + 1, k2): props = [propositions[x, y, z], propositions[x, i, z]] rules.append([-int(item) for item in props]) ## fourth rule each 3x3 value appears once for z in range(k2): # for all values for i in range(k): # x block for j in range(k): # y block for x in range(k): # x place in block for y in range(k): # y place in block for l in range(y + 1, k): # for each props = [ propositions[k * i + x, k * j + y, z], propositions[k * i + x, k * j + l, z] ] rules.append([-int(item) for item in props]) for l in range(x + 1, k): for m in range(0, k): props = [ propositions[k * i + x, k * j + y, z], propositions[k * i + l, k * j + m, z] ] rules.append([-int(item) for item in props]) ## There is at most one number in each entry for y in range(k2): for x in range(k2): for z in range(k2 - 1): for i in range(z + 1, k2): props = [propositions[x, y, z], propositions[x, y, i]] rules.append([-int(item) for item in props]) ## each number appears at least once in row and column and 3x3 for y in range(k2): for z in range(k2): rules.append([int(item) for item in propositions[:, y, z]]) for x in range(k2): for z in range(k2): rules.append([int(item) for item in propositions[x, :, z]]) for i in range(k): # x block for j in range(k): # y block for x in range(k): # x place in block for y in range(k): # y place in block rules.append([ int(item) for item in propositions[k * i + x, k * j + y, :] ]) solver = MinisatGH() solver.append_formula(rules) answer = solver.solve() if answer == True: for i, lit in enumerate(solver.get_model()): if lit > 0: idx = lit - 1 sudoku[(idx // k2) // k2][(idx // k2) % k2] = (idx % k2) + 1 else: print("Did not find a model!") return sudoku
def solve_sudoku_SAT(sudoku, k): ### note: this solution assumes that we don't go over 2 digits in the size/numbers (so works up to 9*9 units) from pysat.formula import CNF from pysat.solvers import MinisatGH ## constraint id is calculated as follows: concatenate row num, col num and value, padded two 2 digits solver = MinisatGH() formula = CNF() ## Approach: ## We have variables for each possible value in each cell (so rowNum * colNum * potential_values = k*k * k*k * k*k) ## for each unit (row, co, box) we add a rule that at least one should be true (a or b or c or ...) ## than for each pair in a unit, we add that the two cannot be true at the same time (not a or not b) ## adding: one position can't take two values for rowInd in range(k * k): for colInd in range(k * k): for valOne in range(k * k - 1): for valTwo in range(valOne + 1, k * k): formula.append([ -int( pad_str(rowInd + 1) + pad_str(colInd + 1) + pad_str(valOne + 1)), -int( pad_str(rowInd + 1) + pad_str(colInd + 1) + pad_str(valTwo + 1)) ]) ## adding: row rules for rowInd in range(k * k): for possible_value in range(k * k): ## adding that one should be true formula.append([ int( pad_str(rowInd + 1) + pad_str(colInd + 1) + pad_str(possible_value + 1)) for colInd in range(k * k) ]) ## adding that two cannot be true for colIndOne in range(k * k - 1): for colIndTwo in range(colIndOne + 1, k * k): formula.append([ -int( pad_str(rowInd + 1) + pad_str(colIndOne + 1) + pad_str(possible_value + 1)), -int( pad_str(rowInd + 1) + pad_str(colIndTwo + 1) + pad_str(possible_value + 1)) ]) ## adding: col rules for colInd in range(k * k): for possible_value in range(k * k): ## adding that one should be true formula.append([ int( pad_str(rowInd + 1) + pad_str(colInd + 1) + pad_str(possible_value + 1)) for rowInd in range(k * k) ]) ## adding that two cannot be true for rowIndOne in range(k * k - 1): for rowIndTwo in range(rowIndOne + 1, k * k): formula.append([ -int( pad_str(rowIndOne + 1) + pad_str(colInd + 1) + pad_str(possible_value + 1)), -int( pad_str(rowIndTwo + 1) + pad_str(colInd + 1) + pad_str(possible_value + 1)) ]) ## adding: box rules for rowStart in range(0, k * k, k): for colStart in range(0, k * k, k): ## looping inside box for possible_value in range(k * k): ## adding that one should be true box_ids = [] for rowInd in range(rowStart, rowStart + k): for colInd in range(colStart, colStart + k): box_ids.append( int( pad_str(rowInd + 1) + pad_str(colInd + 1) + pad_str(possible_value + 1))) formula.append(box_ids) ## adding that two cannot be true for rowIndOne in range(rowStart, rowStart + k): for colIndOne in range(colStart, colStart + k): for rowIndTwo in range(rowIndOne, rowStart + k): for colIndTwo in range(colIndOne, colStart + k): if not (rowIndOne == rowIndTwo and colIndOne == colIndTwo): formula.append([ -int( pad_str(rowIndOne + 1) + pad_str(colIndOne + 1) + pad_str(possible_value + 1)), -int( pad_str(rowIndTwo + 1) + pad_str(colIndTwo + 1) + pad_str(possible_value + 1)) ]) ## Adding the input values as literals for rowInd in range(k * k): for colInd in range(k * k): if sudoku[rowInd][colInd] != 0: formula.append([ int( pad_str(rowInd + 1) + pad_str(colInd + 1) + pad_str(sudoku[rowInd][colInd])) ]) ## calling the solver solver.append_formula(formula) answer = solver.solve() if not answer: return None else: ### reconstruct sudoku from solution for lit in solver.get_model(): if lit > 0: lit_split = [int(x) for x in str(lit)] ## appending leading zero if needed, since int conversion removes it if len(lit_split) < 6: lit_split = [0] + lit_split print(lit_split) sudoku[10 * lit_split[0] + lit_split[1] - 1][10 * lit_split[2] + lit_split[3] - 1] = 10 * lit_split[4] + lit_split[5] return sudoku
nv = card.nv # Encode constraints for each cell for i in range(N**2): for j in range(N**2): # Atleast-1 clause F.append(V[i][j]) # Atmost-1 encoding card = pysat.card.CardEnc.atmost(lits=V[i][j], top_id=nv, bound=1, encoding=enc) F.extend(card.clauses) nv = card.nv solver = Solver(bootstrap_with=F.clauses, use_timer=True) # First compute a solution if solver.solve(): solution = [ v for v in solver.get_model() if v > 0 and v <= max_problem_var ] S = set(solution) for i in range(N**2): for j in range(N**2): vs = V[i][j] trues = [ix + 1 for (ix, v) in enumerate(vs) if v in S] print("%2d" % trues[0], end="") print() print(solver.time())
def solve_sudoku_SAT(sudoku, k): # give each possible value for cell (i, j) a (boolean) variable, resulting in in total k^6 variables variables = [[[v + k * k * (k * k * i + j) for v in range(1, k * k + 1)] for j in range(k * k)] for i in range(k * k)] formula = CNF() # iterate over rows and columns for i in range(k * k): # keep track of which variables belong to the row and column and to which values v row_vars = [[] for v in range(k * k)] col_vars = [[] for v in range(k * k)] for j in range(k * k): if sudoku[i][j] > 0: # values we are certain of are added as unit clause formula.append([variables[i][j][sudoku[i][j] - 1]]) # each cell must contain a value formula.append( variables[i][j] ) # this says: one of the variables belonging to (i,j) must be true for v1 in range(k * k): row_vars[v1].append( variables[i][j][v1] ) # save the variable belonging to the row for each value col_vars[v1].append( variables[j][i] [v1]) # same for the column: note the swapped i and j for v2 in range(v1 + 1, k * k): # prevent cells from having multiple values # in cell (i,j) any two variables v1, v2 (v1 != v2) cannot both be true formula.append( [-variables[i][j][v1], -variables[i][j][v2]]) for v in range(k * k): # each row and column must contain each possible value formula.append( row_vars[v] ) # for each possible value v, one corresponding variable must be true in row i formula.append(col_vars[v]) # same for the column for v1 in range(k * k): for v2 in range(v1 + 1, k * k): # and values in rows and columns cannot appear more than once # even though this is already implied, adding these lines speeds up the search process formula.append([-row_vars[v][v1], -row_vars[v][v2]]) formula.append([-col_vars[v][v1], -col_vars[v][v2]]) # iterate over blocks for b1 in range(k): # block vertical index for b2 in range(k): # block horizontal index # keep track of which variables belong to the block and to which values v block_vars = [[] for v in range(k * k)] for i in range(k * b1, k + k * b1): # row for j in range(k * b2, k + k * b2): # column for v in range(k * k): # value block_vars[v].append(variables[i][j][v]) for v in range(k * k): # the block must contain every possible value once formula.append(block_vars[v]) for v1 in range(k * k): for v2 in range(v1 + 1, k * k): # and two values in the block cannot be the same formula.append( [-block_vars[v][v1], -block_vars[v][v2]]) solver = MinisatGH() solver.append_formula(formula) answer = solver.solve() if answer: # if a solution is found # we convert the variables back to indices (i,j) and a value v solution = [[0 for j in range(k * k)] for i in range(k * k)] # empty k*k x k*k solution grid for l in solver.get_model(): # for each variable l in the model if l > 0: # variable l is true idx = l - 1 # this makes indexing easier i = int(idx / k**4) # row index j = int(idx / (k * k) % (k * k)) # column index v = idx % (k * k) + 1 # value solution[i][j] = v return solution return None # if no solution is found
def solve_sudoku_SAT(sudoku, k): """ Function that solves given sudoku list with SAT encoding. Edges correspond to undirected edge between cells in same row, column or k*k block. """ formula = CNF() num_vertices = (k * k) * (k * k) # 81 for k = 3 # Nd array representation of sudoku graph = np.array(sudoku).reshape(num_vertices, 1) # Create 2d np array of vertice numbers of 0 until num_vertices-1 for edges vertices = np.arange(num_vertices).reshape(k * k, k * k) edges = get_edges(vertices, k) vertices = vertices.reshape(num_vertices, 1) # Number of possible values in sudoku which we refer to as numbers (nums/n) num_nums = k * k # Return propositional variable for each vertex and num combination def var_v_n(v, n): return (v * num_nums) + n # Each cell contains a value between 1 and k*k for v in range(num_vertices): # Clause to ensure at least one num can be assigned to one vertex clause = [var_v_n(v, n) for n in range(1, num_nums + 1)] formula.append(clause) for n1 in range(1, num_nums + 1): for n2 in range(n1 + 1, num_nums + 1): # Clause ensures at most one num can be assigned to one vertex clause = [-1 * var_v_n(v, n1), -1 * var_v_n(v, n2)] formula.append(clause) # If a cell contains u in input, cell in solution must contain u if graph[v] != 0: cell = var_v_n(vertices[v].item(), graph[v].item()) formula.append([cell]) # Each two different cells in same row,col,block must contain diff values for (v1, v2) in edges: for n in range(1, num_nums + 1): # Num assigned to vertex 1 of edge should be true, OR num assigned # to other vertex of edge should be true clause = [-var_v_n(v1.item(), n), -var_v_n(v2.item(), n)] formula.append(clause) solver = MinisatGH() solver.append_formula(formula) answer = solver.solve() if answer: model = solver.get_model() # Fill in sudoku graph with answer for v in range(num_vertices): for n in range(1, num_nums + 1): if var_v_n(v, n) in model: graph[v] = n graph = graph.reshape(k * k, k * k).tolist() else: graph = None return graph
def solve_sudoku_SAT(sudoku: List[List[int]], k: int) -> Union[List[List[int]], None]: """Solve a k-by-k block Sudoku puzzle by encoding the problem as a propositional CNF formula and using a satisfiability (SAT) solver :param sudoku: A Sudoku puzzle represented as a list of lists. Values to be filled in are set to zero (0) :type sudoku: List[List[int]] :param k: Size of the Sudoku puzzle (a grid of k x k blocks) :type k: int :return: The solved sudoku puzzles as a list of lists or None if no solution is found. :rtype: Union[List[List[int]], None] """ # Record a (row, column, value) triple as a Variable ID in a PySAT ID Pool def triplet_to_id(row_index: int, column_index: int, val: int) -> int: # indexes need to start at 1 in the id pool return id_pool.id(tuple([row_index + 1, column_index + 1, val])) # Extract the (row, column, value) triple from the PySAT Variable ID def id_to_triplet(vid: int) -> Tuple[int, int, int]: row, column, val = id_pool.obj(vid) # convert back to indexes return row - 1, column - 1, val solved_sudoku = copy(sudoku) grid_size = k**2 cell_indices = list(get_cell_indices(k)) formula = CNF() id_pool = IDPool() # Build the CNF Formula using the definition of Kwon and Jain (2006) # (https://www.researchgate.net/publication/228566840_Optimized_CNF_encoding_for_sudoku_puzzles) # Extended Encoding = cell definedness AND cell uniqueness AND row definedness AND row uniqueness # AND column definedness AND column uniqueness AND block definedness # AND block uniqueness AND assigned cells # definedness: each (cell, row, column, block) has at least one number from 1 to grid_size+1 # uniqueness each (cell, row, column, block) has at most one number from 1 to grid_size+1 # Row/column/cell level # You can swap indices to declare uniqueness and definedness in a single loop for row_index, column_index in cell_indices: # Cell definedness formula.append([triplet_to_id(row_index, column_index, v) for v in range(grid_size)]) # Row definedness formula.append([triplet_to_id(row_index, v, column_index) for v in range(grid_size)]) # Column definedness formula.append([triplet_to_id(v, column_index, row_index) for v in range(grid_size)]) # Assigned cells val = sudoku[row_index][column_index] if val != 0: formula.append([triplet_to_id(row_index, column_index, val - 1)]) for val_index_i, val_index_j in itertools.combinations(range(grid_size), 2): # Cell uniqueness formula.append([ -triplet_to_id(row_index, column_index, val_index_i), -triplet_to_id(row_index, column_index, val_index_j)]) # Row uniqueness formula.append([ -triplet_to_id(row_index, val_index_i, column_index), -triplet_to_id(row_index, val_index_j, column_index)]) # Column uniqueness formula.append([ -triplet_to_id(val_index_i, row_index, column_index), -triplet_to_id(val_index_j, row_index, column_index)]) # Block level for val in range(grid_size): for block_indices in get_cell_indices_by_block(k): # Block definedness formula.append([triplet_to_id(r, c, val) for r, c in block_indices]) # Block uniqueness for block_index_tuple in itertools.combinations(block_indices, 2): (row_1, column_1), (row_2, column_2) = block_index_tuple formula.append([ -triplet_to_id(row_1, column_1, val), -triplet_to_id(row_2, column_2, val)]) # Solve solver = MinisatGH() solver.append_formula(formula) answer = solver.solve() if not answer: return None # Fill in solution model = solver.get_model() for vid in model: if vid > 0: row_index, column_index, val = id_to_triplet(vid) solved_sudoku[row_index][column_index] = val + 1 return solved_sudoku
def solve_sudoku_SAT(sudoku, k): ''' Function that solves a sudoku using a SAT solver. Creates the formula and feeds it to the MinisatGH solver. Returns: the filled in sudoku if it was solved, None otherwise. ''' kk = k * k formula = CNF() # functions to be used within this solver def var_number(i, j, n): ''' Determines a unique index for a sudoku cell based in its row, column and possible value. ''' return i * kk * kk + j * kk + n # create literals for values that are already filled in, these must always be satisfied for i in range(kk): for j in range(kk): if sudoku[i][j] != 0: clause = [var_number(i, j, sudoku[i][j])] formula.append(clause) # ensure all cells have at least one of the specified values for i in range(kk): for j in range(kk): clause = [] # iterate over all possible values, determine the unique index for n in range(1, kk + 1): clause.append(var_number(i, j, n)) # ensure at least one is satisfied formula.append(clause) # iterate over rows (needed for multiple checks) for i in range(kk): for j in range(kk): # ensure at most one value is picked for n1 in range(1, kk + 1): for n2 in range(n1 + 1, kk + 1): # only one of the two values can be true clause = [ -1 * var_number(i, j, n1), -1 * var_number(i, j, n2) ] formula.append(clause) # ensure proper row and column (no double values) for n in range(1, kk + 1): for j1 in range(kk): for j2 in range(j1 + 1, kk): # ensure no column contains same value twice clause = [ -1 * var_number(i, j1, n), -1 * var_number(i, j2, n) ] formula.append(clause) # ensure no row contains same value twice clause = [ -1 * var_number(j1, i, n), -1 * var_number(j2, i, n) ] formula.append(clause) # create list with indices of blocks indices = create_indices(k) # iterate over block indices for ind in indices: # iterate over possible values for n in range(1, kk + 1): # create every combination of block indices possible for i1 in range(len(ind)): for i2 in range(i1 + 1, len(ind)): # ensure no value appears more than once clause = [ -1 * var_number(ind[i1][0], ind[i1][1], n), -1 * var_number(ind[i2][0], ind[i2][1], n) ] formula.append(clause) # initialise the solver and give it the formula solver = MinisatGH() solver.append_formula(formula) # attempt to solve formula answer = solver.solve() # fill the sudoku when an answer has been found if answer: # get model from solver model = solver.get_model() for i in range(kk): for j in range(kk): for n in range(1, kk + 1): if var_number(i, j, n) in model: sudoku[i][j] = n return sudoku # return None when no solution was found return None
def solve_sudoku_SAT(sudoku, k): """ This function encodes the sudoku problem into SAT, and uses a SAT solver to solve the problem """ # First we create a list containing all the edges for all vertices in the sudoku length = len(sudoku) num_vertices = length**2 matrix = np.arange(num_vertices).reshape(length, length) edges = [] sudoku = np.array(sudoku).reshape(length * length) # The loop below fills the edges list with all edges in the sudoku for i in range(length): for j in range(length): # specify the current value i,j as the left-hand value for the edge tuple left = int(matrix[i][j] + 1) # iterate over values in the square col = j // k row = i // k rows = matrix[(row * k):(row * k + k)] box = [x[(col * k):(col * k + k)] for x in rows] for v in range(k): for w in range(k): right = int(box[v][w] + 1) # make sure that we're not assigning the current value as the right-hand vertix if (row * k + v, col * k + w) != (i, j): if (left, right) not in edges: edges.append((left, right)) # iterative over cells in row i, for g in range(length): right = int(matrix[i][g] + 1) if (i, g) != (i, j): if (left, right) not in edges: edges.append((left, right)) # iterate over cells in column j, for c in range(length): right = int(matrix[c][j] + 1) if (c, j) != (i, j): if (left, right) not in edges: edges.append((left, right)) # specify the range of values which can be assigned to empty cells num_values = k**2 formula = CNF() # this function will asign a positive integer for each propositional variable def var_number(i, c): return ((i - 1) * num_values) + c # we then add a clause which states that for each vertex in the graph, there must be at least one value v, # for which p__i,v is true for i in range(1, num_vertices + 1): clause = [] # if the given sudoku has a 0 at that index, we assign all possible values from num_values if int(sudoku[i - 1]) == 0: for c in range(1, num_values + 1): clause.append(var_number(i, c)) formula.append(clause) # if the given sudoku already has an assigned value for that index, we only add a single clause else: clause.append(var_number(i, int(sudoku[i - 1]))) formula.append(clause) # at most one value for which p__i,v is true for i in range(1, num_vertices + 1): for c1 in range(1, num_values + 1): for c2 in range(c1 + 1, num_values + 1): clause = [-1 * var_number(i, c1), -1 * var_number(i, c2)] formula.append(clause) # ensure that values are assigned "properly" for (i1, i2) in edges: for c in range(1, num_values + 1): clause = [-1 * var_number(i1, c), -1 * var_number(i2, c)] formula.append(clause) solver = MinisatGH() solver.append_formula(formula) answer = solver.solve() # reshape the resulting matrix so that we can index it with a single value matrix = matrix.reshape(length**2) if answer == True: print("The sudoku is solved.") model = solver.get_model() for i in range(1, num_vertices + 1): for c in range(1, num_values + 1): if var_number(i, c) in model: matrix[i - 1] = c return matrix.reshape(length, length).tolist() else: print("The sudoku has no solution.") return None
def solve_sudoku_SAT(sudoku, k): # creating the formula for the SAT solver formula = CNF() # creating a sudoku array with individual vertice numbers num_vertices = k**4 list_vertices = list(range(1, (k**2) * (k**2) + 1)) vertice_list = [] for i in range(k**2): vertice_list.append(list_vertices[:k**2]) del list_vertices[:k**2] array_vertices = np.asarray(vertice_list) # generating the edges edges = make_edges(k) # filling in the formula with clauses # adding the clauses for every cell containing a unique index for every value num_digits = k**2 for i in range(1, num_vertices + 1): clause = [] for d in range(1, num_digits + 1): clause.append(var_number(i, d, num_digits)) formula.append(clause) # adding the clause that every cell can only contain one value for i in range(1, num_vertices + 1): for c1 in range(1, num_digits + 1): for c2 in range(c1 + 1, num_digits + 1): clause = [ -1 * var_number(i, c1, num_digits), -1 * var_number(i, c2, num_digits) ] formula.append(clause) #adding the clauses that edges cannot contain the same value for (i, j) in edges: for d in range(1, num_digits + 1): clause = [ int(-var_number(i, d, num_digits)), int(-var_number(j, d, num_digits)) ] formula.append(clause) # adding the clauses that contain the true values for the cells which we are certain about (that are already filled in) for i in range(k**2): for j in range(k**2): if sudoku[i][j] != 0: clause = [ int( var_number(array_vertices[i, j], sudoku[i][j], num_digits)) ] formula.append(clause) # creating sat solver and checking for an answer solver = MinisatGH() solver.append_formula(formula) answer = solver.solve() # check if sudoku is solvable and fill in sudoku and return sudoku, otherwise return None if answer == True: model = solver.get_model() sudoku = array_vertices for i in range(k**2): for j in range(k**2): for d in range(1, k**2 + 1): if var_number(array_vertices[i, j], d, num_digits) in model: sudoku[i, j] = d break sudoku = sudoku.tolist() return sudoku else: return None
def solve_sudoku_SAT(sudoku, k): formula = CNF() num_rows = num_cols = num_values = k**2 # creates propositional variables indicated as unique positive integers def s(row, col, value): return (k**4) * row + (k**2) * col + value def s_extract(row, col, pos_lit): return pos_lit - ((k**4) * row + (k**2) * col) # at least one number in each entry for row in range(num_rows): for col in range(num_cols): clause = [] # if entry already given append unit clause if sudoku[row][col] != 0: clause.append(s(row, col, sudoku[row][col])) # else append disjunction else: for value in range(1, num_values + 1): clause.append(s(row, col, value)) formula.append(clause) # each value can appear at most once in every row for col in range(num_cols): for value in range(1, num_values + 1): for row1 in range(num_rows - 1): for row2 in range(row1 + 1, num_rows): clause = [-s(row1, col, value), -s(row2, col, value)] formula.append(clause) # each value can appear at most once in every column for row in range(num_cols): for value in range(1, num_values + 1): for col1 in range(num_cols - 1): for col2 in range(col1 + 1, num_cols): clause = [-s(row, col1, value), -s(row, col2, value)] formula.append(clause) # each value can appear at most once in every block for value in range(1, num_values + 1): for i in range(k): for j in range(k): for x in range(k): for y in range(k): for y1 in range(y + 1, k): clause = [ -s(k * i + x, k * j + y, value), -s(k * i + x, k * j + y1, value) ] formula.append(clause) for value in range(1, num_values + 1): for i in range(k): for j in range(k): for x in range(k): for y in range(k): for x1 in range(x + 1, k): for l in range(k): clause = [ -s(k * i + x, k * j + y, value), -s(k * i + x1, k * j + l, value) ] formula.append(clause) solver = MinisatGH() solver.append_formula(formula) answer = solver.solve() pos_lits = [value for value in solver.get_model() if value > 0] count = 0 for row in range(num_rows): for col in range(num_cols): if sudoku[row][col] == 0: sudoku[row][col] = s_extract(row, col, pos_lits[count]) count += 1 return sudoku
def hads_to_graphs(all_columns=True, transpose=True): if DEBUGGING: start_time = datetime.datetime.now() translate = { '0': [-1, -1, -1, -1], '1': [-1, -1, -1, 1], '2': [-1, -1, 1, -1], '3': [-1, -1, 1, 1], '4': [-1, 1, -1, -1], '5': [-1, 1, -1, 1], '6': [-1, 1, 1, -1], '7': [-1, 1, 1, 1], '8': [1, -1, -1, -1], '9': [1, -1, -1, 1], 'A': [1, -1, 1, -1], 'B': [1, -1, 1, 1], 'C': [1, 1, -1, -1], 'D': [1, 1, -1, 1], 'E': [1, 1, 1, -1], 'F': [1, 1, 1, 1] } #tracks the number of graphs and hadamard matrices hctr = 0 gctr = 0 mults_to_clauses = dict() emap = dict() ectr = 0 for a in range(K): for b in range(a + 1, K): ectr += 1 emap[ectr] = (a, b) emap[(a, b)] = ectr re_emap = dict() ectr = 0 for b in range(K): for a in range(b): ectr += 1 #re_emap[(a,b)] = ectr re_emap[emap[(a, b)]] = ectr print('relabeled index of', (a, b), 'from', emap[(a, b)], 'to', ectr, file=sys.stderr) #return if all_columns: max_col = K - 1 else: max_col = 0 for curr_line in fileinput.input(): myh = [] #print('curr line is:') #print(curr_line) #print('just did it') #print('currline[:-1] is:') #print(curr_line[:-1]) #print('just did it') #print() if DEBUGGING: print('the entries of curr line are:', file=sys.stderr) for s in curr_line: print(s) for s in curr_line[:-1]: if DEBUGGING: print(s, file=sys.stderr) print(translate[s], file=sys.stderr) myh.extend(translate[s]) myh = matrix([myh[i * K:(i + 1) * K] for i in range(K)]) #for line in chunk.splitlines(): # myh = [] # for s in line: # myh.extend(translate[s]) #myh = matrix([myh[i*K:(i+1)*K] for i in range(K)]]) if VERBOSE: print('trying matrix', hctr, '...', file=sys.stderr) for col in range(max_col + 1): hh = swap_cols(myh, 0, col) dd = diagonal_matrix([hh[j, 0] for j in range(K)]) hh = dd * hh dd = diagonal_matrix([hh[0, j] for j in range(K)]) hh = hh * dd if transpose: newhh = [] for i in range(K): newhh.append([hh[j, i] for j in range(K)]) hh = matrix(newhh) nclauses = 0 nvars = K * (K - 1) / 2 if SOLVER_NUM == 0: g = Glucose3() if SOLVER_NUM == 1: g = Glucose4() if SOLVER_NUM == 2: g = Lingeling() if SOLVER_NUM == 3: g = MapleChrono() if SOLVER_NUM == 4: g = MapleCM() if SOLVER_NUM == 5: g = Maplesat() if SOLVER_NUM == 6: g = Minicard() if SOLVER_NUM == 7: g = Minisat22() if SOLVER_NUM == 8: g = MinisatGH() matchings = dict() ectr = 0 for b in range(K - 1): ectr += 1 my_row = [0 for i in range(K - 1)] my_row[b] = K my_row = tuple(my_row) matchings[my_row] = [(ectr, 0, b)] for a in range(1, K): for b in range(a + 1, K): if DEBUGGING: print('trying', (a, b), file=sys.stderr) ectr += 1 hab = tuple([hh[a, j] * hh[b, j] for j in range(K)]) my_row = [] for j in range(1, K): dot = 0 for k in range(K): dot += hab[k] * hh[j, k] my_row.append(dot) my_row = tuple(my_row) if my_row in matchings: matchings[my_row].append((ectr, a, b)) old_ectr = matchings[my_row][0][0] if DEBUGGING: print('the old ectr,ectr=', old_ectr, ectr, file=sys.stderr) g.add_clause((re_emap[old_ectr], -re_emap[ectr])) g.add_clause((-re_emap[old_ectr], re_emap[ectr])) nclauses += 2 else: matchings[my_row] = [(ectr, a, b)] coes_to_inds = dict() mults = [] for i in range(K - 1): coe = my_row[i] if coe in coes_to_inds: coes_to_inds[coe].append(i) else: coes_to_inds[coe] = [i] mults = [(coe, len(coes_to_inds[coe])) for coe in coes_to_inds if coe] mults.sort() mults = tuple(mults) if mults in mults_to_clauses: old_ectr, num_new_vars, old_coes_to_inds, old_clauses = mults_to_clauses[ mults] #must relabel the old indices to match the new ones #for each coefficient c relab = dict() for coe in old_coes_to_inds: coe_ctr = 0 for old_index in old_coes_to_inds[coe]: relab[1 + old_index] = re_emap[ 1 + coes_to_inds[coe][coe_ctr]] relab[-1 - old_index] = -re_emap[ 1 + coes_to_inds[coe][coe_ctr]] coe_ctr += 1 relab[old_ectr] = re_emap[ectr] relab[-old_ectr] = -re_emap[ectr] for i in range(1, 1 + num_new_vars): relab[old_ectr + i] = i + nvars relab[-old_ectr - i] = -i - nvars for curr_clause in old_clauses: g.add_clause([relab[k] for k in curr_clause]) nclauses += len(old_clauses) nvars += num_new_vars else: if DEBUGGING: print('dealing with', mults, 'for the first time...', file=sys.stderr) cnf = PBEnc.equals(lits=range(1, K) + [ectr], weights=list(my_row) + [-K], bound=0, encoding=4) curr_clauses, maxvar = simplify_clause_list( cnf.clauses, ectr + 1) num_new_vars = maxvar - ectr if DEBUGGING: print("", file=sys.stderr) print("", file=sys.stderr) print('adding new clauses; there are', nvars, 'vars', file=sys.stderr) for curr_clause in curr_clauses: if DEBUGGING: print('relabeling clause', c, 'with ectr=', ectr, 'and', nvars, 'vars', file=sys.stderr) new_clause = [] for k in curr_clause: if abs(k) <= ectr: if k > 0: new_clause.append(re_emap[k]) else: new_clause.append(-re_emap[-k]) else: if k > 0: if DEBUGGING: print(k, 'is the', k - ectr, 'th dummy...', 'shift it up by', nvars, file=sys.stderr) new_clause.append(k - ectr + nvars) else: new_clause.append(k + ectr - nvars) if DEBUGGING: print(k, 'is the', k + ectr, 'th dummy...', 'shift it down by', nvars, file=sys.stderr) if DEBUGGING: print(' adding', new_clause, file=sys.stderr) g.add_clause(new_clause) nvars += num_new_vars nclauses += len(curr_clauses) mults_to_clauses[mults] = (ectr, num_new_vars, coes_to_inds, curr_clauses) if DEBUGGING: print('now there are', (nvars, nclauses), 'vars,clauses', file=sys.stderr) sol_ctr = 0 if VERBOSE: print('there are', nvars, 'variables and', nclauses, 'clauses', file=sys.stderr) print('trying to find all solutions!', file=sys.stderr) if True: #nvars >= 2000: while g.solve(): new_sol = g.get_model() sol_ctr += 1 if sol_ctr % 1000 == 0: print(' ', sol_ctr, 'found so far...', file=sys.stderr) gctr += 1 #nx.write_graph6(nx.Graph([emap[k] for k in new_sol[:K*(K-1)/2] if k > 0]), sys.stdout, nodes = range(K)) #print( str([emap[k] for k in new_sol[:K*(K-1)/2] if k > 0]), file = sys.stdout ) g.add_clause( [-new_sol[1 + j * (j + 1) / 2] for j in range(K - 1)]) #print([k for k in new_sol[:K*(K-1)/2] if k > 0], file = sys.stderr) #print('which is really:', file = sys.stderr) #print([re_emap[emap[k]] for k in new_sol[:K*(K-1)/2] if k > 0], file = sys.stderr) #print('got written as\n', file = sys.stderr) print(sol_to_g6(new_sol[:K * (K - 1) / 2]), file=sys.stdout) #g.add_clause( [-new_sol[j] for j in range(K-1)] ) if VERBOSE: print(sol_ctr, 'solutions found!', file=sys.stderr) if DEBUGGING: end_time = datetime.datetime.now() elapsed_time = end_time - start_time print('total time elapsed:', elapsed_time.seconds, ":", elapsed_time.microseconds, 'matrices solved:', hctr, file=sys.stderr) else: if VERBOSE: print('too hard for now!', file=sys.stderr) hctr += 1 return