class LogicFormula: def __init__(self, inputs, startLiteral=None, overwriteLiterals=True): self.inputs = inputs self.assertedInputWires = self.inputs self.detectedInputWires = [] self.constantWires = [] if overwriteLiterals: LogicFormula.assignVariables(inputs, startLiteral) self.usedLiterals = self.getAllUsedVariables(self.inputs) if startLiteral is None: startLiteral = max(self.usedLiterals) + 1 self.cnfForm = CNF() self.getTseytinCNF() self.rawCnf = self.cnfForm.rawCNF() if DEBUG: print(str(len(self.rawCnf)) + ' clauses generated.') self.unusedVars = self.cnfForm.usedVariables().symmetric_difference( set(range(1, max(self.cnfForm.usedVariables()) + 1))) assert len(self.unusedVars) == 0, \ "There shouldn't be unused variables in the Tseytin transform. Something is clearly wrong" def getConstantClauses(self, visitedPoints): clauses = [] self.constantWires = [] for wire in visitedPoints: if isinstance( wire, Wire ) and wire.constant is not None and wire not in self.constantWires: self.constantWires.append(wire) if wire.constant: clauses.append([wire.variable]) else: clauses.append([-wire.variable]) return clauses def getTseytinCNF(self): self.cnfForm = CNF() if len(self.inputs) == 0: return visited = [] componentQueue = deque(self.assertedInputWires) # componentQueue = deque(self.inputs) visited.append(componentQueue[0]) visitedGates = set() if DEBUG: print( 'Translating circuit graph into CNF using Tseytin Transform.') t0 = time.time() while len(componentQueue) != 0: v = componentQueue.popleft() if isinstance(v, Wire): if v.variable is None: raise Exception( 'All wire components must have a variable bound.') if v not in visited: visited.append(v) for gate in v.gatesIn: if gate not in visited: visited.append(gate) componentQueue.append(gate) if v.gateOut not in visited and v.gateOut is not None: visited.append(v.gateOut) componentQueue.append(v.gateOut) elif issubclass(type(v), Gate): if v not in visitedGates: visitedGates.add(v) self.cnfForm.mergeWithRaw(self.getTseytinSingleGate(v)) if v.output not in visited: visited.append(v.output) componentQueue.append(v.output) for inputWire in v.inputs: if inputWire not in visited: visited.append(inputWire) componentQueue.append(inputWire) else: raise Exception( "Logic structure should only contain Wires and Gates") if DEBUG: total = time.time() - t0 print('Finished translating ' + str(len(visited)) + ' components. (' + str(total) + ' seconds)') self.cnfForm.mergeWithRaw(self.getConstantClauses(visited)) def getTseytinSingleGate(self, gate): if not issubclass(type(gate), Gate): raise Exception("Must be of type gate") # If you manage to get here with inputs/outputs as None I'm impressed! if isinstance(gate, Gate2): varA = gate.inputA.variable varB = gate.inputB.variable varOut = gate.output.variable if gate.gateType == LogicStructure.AND: # print(str(varA) + ' AND ' + str(varB) + ' = ' + str(varOut)) newClauses, _ = Tseytin.AND(varA, varB, varOut) return newClauses elif gate.gateType == LogicStructure.NAND: newClauses, _ = Tseytin.NAND(varA, varB, varOut) # print(str(varA) + ' NAND ' + str(varB) + ' = ' + str(varOut)) return newClauses elif gate.gateType == LogicStructure.OR: newClauses, _ = Tseytin.OR(varA, varB, varOut) # print(str(varA) + ' OR ' + str(varB) + ' = ' + str(varOut)) return newClauses elif gate.gateType == LogicStructure.NOR: newClauses, _ = Tseytin.NOR(varA, varB, varOut) # print(str(varA) + ' NOR ' + str(varB) + ' = ' + str(varOut)) return newClauses elif gate.gateType == LogicStructure.XOR: newClauses, _ = Tseytin.XOR(varA, varB, varOut) # print(str(varA) + ' XOR ' + str(varB) + ' = ' + str(varOut)) return newClauses elif gate.gateType == LogicStructure.XNOR: newClauses, _ = Tseytin.XNOR(varA, varB, varOut) # print(str(varA) + ' XNOR ' + str(varB) + ' = ' + str(varOut)) return newClauses elif gate.gateType == LogicStructure.IMPLIES: newClauses, _ = Tseytin.IMPLIES(varA, varB, varOut) # print(str(varA) + ' IMPLIES ' + str(varB) + ' = ' + str(varOut)) return newClauses else: raise Exception('Unknown gate') elif isinstance(gate, Gate1): varA = gate.inputA.variable varOut = gate.output.variable if gate.gateType == LogicStructure.NOT: newClauses, _ = Tseytin.NOT(varA, varOut) # print('NOT ' + str(varA) + ' = ' + str(varOut)) return newClauses else: raise Exception('Unknown gate') elif isinstance(gate, GateCustom): raise Exception("Custum logic structures aren't always gates") def getAllUsedVariables(self, inputs): if DEBUG: print('Determining used variables.') t0 = time.time() if len(inputs) == 0: return [] self.detectedInputWires = [] self.freeInputs = [] visited = [] componentQueue = deque(inputs) usedVariables = [] visited.append(componentQueue[0]) while len(componentQueue) != 0: v = componentQueue.popleft() if isinstance(v, Wire): if v.variable is None: raise Exception( 'All wire components must have a variable bound.') elif v.variable not in usedVariables: usedVariables.append(v.variable) if v not in visited: visited.append(v) if v.gateOut is None: if v not in self.detectedInputWires: self.detectedInputWires.append(v) if v.constant is None and v not in self.freeInputs: self.freeInputs.append(v) for gate in v.gatesIn: if gate not in visited: visited.append(gate) componentQueue.append(gate) if v.gateOut not in visited and v.gateOut is not None: visited.append(v.gateOut) componentQueue.append(v.gateOut) elif issubclass(type(v), Gate): if v.output not in visited: visited.append(v.output) componentQueue.append(v.output) for inputWire in v.inputs: if inputWire not in visited: visited.append(inputWire) componentQueue.append(inputWire) else: raise Exception( "Logic structure should only contain Wires and Gates") if DEBUG: total = time.time() - t0 print( str(len(usedVariables)) + ' used variables found. (' + str(total) + ' seconds)') return usedVariables @staticmethod def assignVariables(inputs, startLiteral=None): if len(inputs) == 0: return if startLiteral is None: startLiteral = 1 literalTracker = startLiteral visited = [] componentQueue = deque(inputs) visited.append(componentQueue[0]) if DEBUG: print('Assigning variables.') t0 = time.time() while len(componentQueue) != 0: v = componentQueue.popleft() if isinstance(v, Wire): v.variable = literalTracker literalTracker += 1 # if v.name is not None: # print(v.name + ' assigned: ' + str(v.variable)) if v not in visited: visited.append(v) for gate in v.gatesIn: if gate not in visited: # print('Gate added') visited.append(gate) componentQueue.append(gate) if v.gateOut not in visited and v.gateOut is not None: visited.append(v.gateOut) componentQueue.append(v.gateOut) elif issubclass(type(v), Gate): if v.output not in visited: visited.append(v.output) componentQueue.append(v.output) # print('Wire added') for inputWire in v.inputs: if inputWire not in visited: visited.append(inputWire) componentQueue.append(inputWire) # print('Wire added') else: raise Exception( "Logic structure should only contain Wires and Gates") if DEBUG: total = time.time() - t0 print('Variable assignment completed. (' + str(total) + ' seconds)') return literalTracker - 1 # https://en.wikipedia.org/wiki/Tseytin_transformation @staticmethod def WikipediaExample(): x1 = Wire() x2 = Wire() x3 = Wire() gate1 = Wire() not1 = Gate1(LogicStructure.NOT, x1, gate1) gate3_5 = Wire() not2 = Gate1(LogicStructure.NOT, x2, gate3_5) gate2 = Wire() and1 = Gate2(LogicStructure.AND, gate1, x2, gate2) gate4 = Wire() and2 = Gate2(LogicStructure.AND, x1, gate3_5, gate4) gate6 = Wire() and3 = Gate2(LogicStructure.AND, gate3_5, x3, gate6) gate7 = Wire() or1 = Gate2(LogicStructure.OR, gate2, gate4, gate7) gate8 = Wire() or2 = Gate2(LogicStructure.OR, gate7, gate6, gate8) y = gate8 return [x1, x2, x3], [y] @staticmethod def Peg1DExample(): x = [Wire() for _ in range(5)] y = [Wire() for _ in range(5)] offsets = [i for i in range(-2, 3)] for i in range(5): inputs = [] for j in offsets: if i + j not in range(5): inputs.append(None) else: inputs.append(x[i + j]) for j in offsets: if i + j not in range(5): inputs.append(None) else: inputs.append(y[i + j]) GateCustom().PegSolitaireFlatNextState(inputs, Wire()) return x + y, []
class EulerSquare: def __init__(self, n, symbols): self.n = n self.symbols = symbols self.board = None self.la = LiteralAllocator() self.literal2board = dict() self.board2literal = dict() self.cnf = CNF() self.InitSquare() self.Assert() def InitSquare(self): self.board = [] for row in range(self.n): cols = [] for col in range(self.n): symbols = [] for symbol in range(self.symbols): symbolVals = [] for symbolVal in range(self.n): literal = self.la.getLiteral() self.board2literal[(row, col, symbol, symbolVal)] = literal symbolVals.append(literal) symbols.append(symbolVals) cols.append(symbols) self.board.append(cols) # number of possible symbol values | how many symbol types | columns | rows # self.board = [[[[self.la.getLiteral() for _ in range(self.n)] for _ in range(self.symbols)] for _ in range(self.n)] for _ in range(self.n)] # pp.pprint(self.board) def Assert(self): # Asserts that for each symbol, it doesn't share a row or column with the same symbol for symbol in range(self.symbols): for row in range(self.n): for col in range(self.n): for i in range(self.n): for j in range(self.n): if (row == i) ^ (col == j): for symbolVal in range(self.n): self.cnf.addClause([ -self.board[row][col][symbol] [symbolVal], -self.board[i][j][symbol][symbolVal] ]) # Assert that each symbol has a single value assigned for symbol in range(self.symbols): for row in range(self.n): for col in range(self.n): self.cnf.mergeWithRaw( SATUtils.exactlyOne(self.board[row][col][symbol], forceInefficient=True)[0]) # For each pair of symbols, they never appear twice for symbolA, symbolB in itertools.combinations(range(self.symbols), 2): for symbolValA in range(self.n): for symbolValB in range(self.n): # get pairs of literals and AND them together. That value must be # true exactly 1 time for each pairing of symbol types. impliedLiterals = [] for row in range(self.n): for col in range(self.n): clauses, C = Tseytin.AND( self.board[row][col][symbolA][symbolValA], self.board[row][col][symbolB][symbolValB], self.la.getLiteral()) impliedLiterals.append(C) self.cnf.mergeWithRaw(clauses) self.cnf.mergeWithRaw( SATUtils.atLeast(impliedLiterals, 1)[0]) # pp.pprint(sorted(self.cnf.rawCNF(), key=lambda x: [abs(_) for _ in x])) def Solve(self): finalVals = pycosat.solve(self.cnf.rawCNF()) if finalVals == 'UNSAT': print(finalVals) return rows = [] for row in range(self.n): cols = [] for col in range(self.n): symbols = [] for symbol in range(self.symbols): for symbolVal in range(self.n): val = self.board2literal[(row, col, symbol, symbolVal)] if val in finalVals: symbols.append(symbolVal) break cols.append(symbols) rows.append(cols) pp.pprint(rows)
class PegSolitaire: def __init__(self, height=0, width=0): self.height = height self.width = width self.game = None self.literalAllocator = LiteralAllocator() self.cnf = CNF() def Test1D(self): self.height = 1 self.width = 7 numGen = 3 self.game = PegGameInstance(height=self.height, width=self.width) self.game.boards = [ PegBoardState(height=self.height, width=self.width, time=t) for t in range(numGen) ] self.literalAllocator = LiteralAllocator() # Start state for i in range(self.width): if i != 3: self.game[0][0][i].state = PegState.ALIVE else: self.game[0][0][i].state = PegState.DEAD # Intermediate states for t in range(1, numGen): for i in range(self.width): self.game[t][0][i].state = PegState.DONTCARE def DefaultGame(self): self.height = 7 self.width = 7 self.game = PegGameInstance(height=7, width=7) self.game.boards = [ PegBoardState(height=7, width=7, time=t) for t in range(31) ] self.literalAllocator = LiteralAllocator() # Start state for i in range(7): for j in range(7): if i in range(2, 5) or j in range(2, 5): if i == j and i == 3: self.game[0][i][j].state = PegState.DEAD else: self.game[0][i][j].state = PegState.ALIVE # Intermediate states for t in range(1, 30): for i in range(7): for j in range(7): if i in range(2, 5) or j in range(2, 5): self.game[t][i][j].state = PegState.DONTCARE # Final state for i in range(7): for j in range(7): if i in range(2, 5) or j in range(2, 5): if i == j and i == 3: self.game[30][i][j].state = PegState.ALIVE else: self.game[30][i][j].state = PegState.DEAD def BasicTest(self): self.height = 5 self.width = 5 numGen = 3 self.game = PegGameInstance(height=self.height, width=self.width) self.game.boards = [ PegBoardState(height=self.height, width=self.width, time=t) for t in range(numGen) ] self.literalAllocator = LiteralAllocator() # Start state for i in range(self.height): for j in range(self.width): if i == j and i == 2: self.game[0][i][j].state = PegState.DEAD else: self.game[0][i][j].state = PegState.ALIVE # Intermediate states for t in range(1, numGen): for i in range(self.height): for j in range(self.width): self.game[t][i][j].state = PegState.DONTCARE def assignLiterals(self): for t in range(len(self.game.boards)): for i in range(self.height): for j in range(self.width): if self.game[t][i][j].state != PegState.DNE: self.game[t][i][ j].variable = self.literalAllocator.getLiteral() print(self.game) def FillInSequence(self): sequence = self.game.boards if len(sequence) <= 1: raise Exception('A sequence needs at least 2 elements!') for board in sequence: assert isinstance(board, PegBoardState) assert board.height == sequence[0].height and board.width == sequence[0].width, \ 'Cannot compare boards with different dimensions.' self.assignLiterals() height = sequence[0].height width = sequence[0].width self.assertTransitions() self.assertOneMovePerTurn() self.assertFixedStates() for solution in pycosat.itersolve(self.cnf.rawCNF()): updatedSequence = [] for tiling in sequence: newTilingA = PegBoardState(tiling.height, tiling.width, tiling.time) for row in range(tiling.height): for col in range(tiling.width): if tiling[row][col].state != PegState.DNE: if tiling[row][col].variable in solution: if tiling[row][ col].state == PegState.ALIVE or tiling[ row][ col].state == PegState.DONTCARE: # This means that we either forced the cell to be alive or we derived a possible value newTilingA[row][col].state = PegState.ALIVE else: # raise Exception("Computed state is incompatible with original state") pass elif -tiling[row][col].variable in solution: if tiling[row][ col].state == PegState.DEAD or tiling[ row][ col].state == PegState.DONTCARE: # This means that we either forced the cell to be dead or we derived a possible value newTilingA[row][col].state = PegState.DEAD pass else: # raise Exception("Computed state is incompatible with original state") pass else: raise Exception( "Input wasn't even in the solution! Something is clearly wrong here." ) updatedSequence.append(newTilingA) gameSolution = PegGameInstance() gameSolution.SetFrames(updatedSequence) print(gameSolution) break def assertTransitions(self): self.assertAllTriplets() def assertFixedStates(self): fixedVals = [] for t in range(1): #len(self.game.boards)): for row in range(self.height): for col in range(self.width): if self.game.boards[t][row][col].state == PegState.ALIVE: fixedVals.append( self.game.boards[t][row][col].variable) if self.game.boards[t][row][col].state == PegState.DEAD: fixedVals.append( -self.game.boards[t][row][col].variable) self.cnf.mergeWithRaw([[x] for x in fixedVals]) def assertTriplet(self, jumper, skipped, endpoint, time): a = self.game.boards[time][jumper[0]][jumper[1]].variable b = self.game.boards[time][skipped[0]][skipped[1]].variable c = self.game.boards[time][endpoint[0]][endpoint[1]].variable x = self.game.boards[time + 1][jumper[0]][jumper[1]].variable y = self.game.boards[time + 1][skipped[0]][skipped[1]].variable z = self.game.boards[time + 1][endpoint[0]][endpoint[1]].variable clauses = [[-a, -b, c, -x, y], [-a, -b, c, -x, -z], [-a, -b, c, x, -y], [-a, -b, c, -y, -z], [-a, -b, c, x, z], [-a, -b, c, y, z]] self.cnf.mergeWithRaw(clauses) def assertAllTriplets(self): for t in range(len(self.game.boards) - 1): for row in range(self.height): for col in range(self.width): for direction in [[-1, 0], [1, 0], [0, -1], [0, 1]]: xDir = direction[0] yDir = direction[1] jumper = [row, col] skipped = [row + xDir, col + yDir] endpoint = [row + 2 * xDir, col + 2 * yDir] if self.isValidTriple(jumper, skipped, endpoint): self.assertTriplet(jumper, skipped, endpoint, t) def assertOneMovePerTurn(self): initPop = 0 for row in range(self.height): for col in range(self.width): if self.game.boards[0][row][col].state == PegState.ALIVE: initPop += 1 for t in range(len(self.game.boards)): boardLiterals = [] for row in range(self.height): for col in range(self.width): if self.game.boards[t][row][col].state != PegState.DNE: boardLiterals.append( self.game.boards[t][row][col].variable) # Try swapping for SATUtils.atLeast to check for faster speeds newClauses, highestLiteral = SATUtils.exactlyR( boardLiterals, initPop - t, startLiteral=self.literalAllocator.getCurrLiteral()) if highestLiteral >= self.literalAllocator.getCurrLiteral(): self.literalAllocator.getLiterals( highestLiteral - self.literalAllocator.getCurrLiteral() + 1) self.cnf.mergeWithRaw(newClauses) def isValidTriple(self, jumper, skipped, endpoint): if endpoint[0] not in range(self.width) or endpoint[1] not in range( self.height): return False if skipped[0] not in range(self.width) or skipped[1] not in range( self.height): return False if self.game.boards[0][jumper[0]][jumper[1]].state == PegState.DNE: return False if self.game.boards[0][skipped[0]][skipped[1]].state == PegState.DNE: return False if self.game.boards[0][endpoint[0]][endpoint[1]].state == PegState.DNE: return False return True