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 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 __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 __init__( self, nodeDict, d, minColors=1, # (15) Every vertex has at least one color adjacentDifColor=0, # (16) adjacent vertices have different colors maxColors=None #Maximum unique colors per node ): self.d = d self.minColors = minColors self.maxColors = maxColors self.adjacentDifColor = adjacentDifColor self.nodeDict = nodeDict self.literalToIndexTuple = None self.literalToColor = None self.nodeToLiteral = None self.clauses = [] self.solution = [] self.auxLiteral = -1 self.origAuxLiteral = -1 self.nodeToLiteralDict = dict() self.literaToNodeDict = dict() self.cnf = CNF()
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 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 GraphColoring: def __init__( self, nodeDict, d, minColors=1, # (15) Every vertex has at least one color adjacentDifColor=0, # (16) adjacent vertices have different colors maxColors=None #Maximum unique colors per node ): self.d = d self.minColors = minColors self.maxColors = maxColors self.adjacentDifColor = adjacentDifColor self.nodeDict = nodeDict self.literalToIndexTuple = None self.literalToColor = None self.nodeToLiteral = None self.clauses = [] self.solution = [] self.auxLiteral = -1 self.origAuxLiteral = -1 self.nodeToLiteralDict = dict() self.literaToNodeDict = dict() self.cnf = CNF() def generateClauses(self): if self.auxLiteral == -1: raise TypeError() # # (15) Every vertex has at least minColors colors if self.minColors != None: self.enforceMinColors() # Every vertex has at most maxColors colors if self.maxColors != None: self.enforceMaxColors() # (16) adjacent vertices have different colors if self.adjacentDifColor != None: self.enforceAdjacentDifColor() def enforceAdjacentDifColor(self): for i in self.nodeDict: for j in self.nodeDict: if self.isAdjacent(i, j): self.assertNodesDifferInColorByR(i, j, self.adjacentDifColor) def enforceMinColors(self): for vertex in self.nodeDict.keys(): literalsToAssertGEKtrue = [ self.nodeToLiteral(GraphNode(vertex, k)) for k in range(self.d) ] [subclauses, newHighestLiteral] = SATUtils.atLeast(literalsToAssertGEKtrue, self.minColors, self.auxLiteral) if verboseExpressions: for clause in subclauses: self.cnf.addClause( Clause(clause, groupComment=str(vertex) + ' has at least ' + str(self.minColors) + ' colors')) self.auxLiteral = newHighestLiteral + 1 self.clauses += subclauses def enforceMaxColors(self): for vertex in self.nodeDict.keys(): literalsToAssertLEKtrue = [ self.nodeToLiteral(GraphNode(vertex, k)) for k in range(self.d) ] [subclauses, newHighestLiteral] = SATUtils.atMost(literalsToAssertLEKtrue, self.minColors, self.auxLiteral) if verboseExpressions: for clause in subclauses: self.cnf.addClause( Clause(clause, groupComment=str(vertex) + ' has at most ' + str(self.minColors) + ' colors')) self.auxLiteral = newHighestLiteral + 1 self.clauses += subclauses def assertNodesDifferInColorByR(self, nodeA, nodeB, rVal): newClauses = [] #print([-self.nodeToLiteral(GraphNode(nodeA, 0)), -self.nodeToLiteral(GraphNode(nodeB, 0))], [nodeA, nodeB]) for r in range(rVal): for k in range(self.d): if k + r < self.d: newClause = [ -self.nodeToLiteral(GraphNode(nodeA, k)), -self.nodeToLiteral(GraphNode(nodeB, k + r)) ] newClauses.append(newClause) if verboseExpressions: self.cnf.addClause( Clause(newClause, comment='not ' + str(k) + ' and ' + str(k + r) + ' at the same time', groupComment=str(nodeA) + ' and ' + str(nodeB) + ' differ in color by ' + str(rVal))) # make sure to assert the other way too when it's not symmetrical if r != 0: newClause = [ -self.nodeToLiteral(GraphNode(nodeA, k + r)), -self.nodeToLiteral(GraphNode(nodeB, k)) ] newClauses.append(newClause) if verboseExpressions: self.cnf.addClause( Clause(newClause, comment='not ' + str(k + r) + ' and ' + str(k) + ' at the same time', groupComment=str(nodeA) + ' and ' + str(nodeB) + ' differ in color by ' + str(rVal))) self.clauses += newClauses # https://en.wikipedia.org/wiki/L(h,_k)-coloring # L(h, k) Coloring # L(2, 1) is equivalent to the radio coloring problem def L(self, h, k): for i in self.nodeDict: for j in self.nodeDict: if self.nodeToLiteral(GraphNode(i, 0)) < self.nodeToLiteral( GraphNode(j, 0)): if self.isAdjacent(i, j): self.assertNodesDifferInColorByR(i, j, h) elif self.sharesNeighbor(i, j): self.assertNodesDifferInColorByR(i, j, k) def viewClauses(self): for clause in self.clauses: pp.pprint([(sign(literal), self.literalToIndexTuple(abs(literal)), self.literalToColor(abs(literal))) if literal <= self.origAuxLiteral else (sign(literal), 'aux') for literal in clause]) def viewSolution(self): self.solution = list(pycosat.solve(self.clauses)) nodeColors = dict() if self.solution == list('UNSAT'): print(self.solution) return False for literal in self.solution: if literal > 0 and literal < self.origAuxLiteral: identifier = self.literalToIndexTuple(literal) if identifier not in nodeColors: nodeColors[identifier] = [self.literalToColor(literal)] else: nodeColors[identifier].append(self.literalToColor(literal)) pp.pprint(nodeColors) return True def viewSolutions(self): self.solution = list(pycosat.solve(self.clauses)) if self.solution == list('UNSAT'): print(self.solution) return for solution in list(pycosat.itersolve(self.clauses)): nodeColors = dict() for literal in solution: if literal > 0 and literal < self.origAuxLiteral: identifier = self.literalToIndexTuple(literal) if identifier not in nodeColors: nodeColors[identifier] = [self.literalToColor(literal)] else: nodeColors[identifier].append( self.literalToColor(literal)) pp.pprint(nodeColors) def defineNodeLiteralConversion(self, literalToID=None, literalToColor=None, GraphNodeToLiteral=None): if literalToID == None or literalToColor == None or GraphNodeToLiteral == None: self.literaToNodeDict = dict() self.nodeToLiteralDict = dict() self.auxLiteral = 1 for node in self.nodeDict.keys(): self.nodeToLiteralDict[node] = self.auxLiteral self.literaToNodeDict[self.auxLiteral] = node self.auxLiteral += 1 literalToID = lambda literal: self.literaToNodeDict[ (literal - 1) % len(self.nodeDict) + 1] literalToColor = lambda literal: (literal - 1) // len(self.nodeDict ) GraphNodeToLiteral = lambda gnode: self.nodeToLiteralDict[ gnode.ID] + gnode.color * len(self.nodeDict) for color in range(self.d): for testNode in self.nodeDict.keys(): literal = GraphNodeToLiteral(GraphNode(testNode, color)) nodeIdentifier = literalToID(literal) nodeColor = literalToColor(literal) if (testNode, color) != (nodeIdentifier, nodeColor): raise TypeError() # print((testNode, color)) # print((nodeIdentifier, nodeColor)) self.literalToIndexTuple = literalToID self.literalToColor = literalToColor self.nodeToLiteral = GraphNodeToLiteral # initialize the auxLiteral to 1 larger than the largest node found during the verification process self.auxLiteral = 1 + max([ max([ abs(self.nodeToLiteral(GraphNode(x, color))) for x in self.nodeDict.keys() ]) for color in range(self.d) ]) self.origAuxLiteral = self.auxLiteral # given 2 vertices u, v, asserts those 2 vertices differ in at least r colors def assertRdiffColors(self, u, v, r): groups = [[[self.nodeToLiteral(GraphNode(u, color))], [-self.nodeToLiteral(GraphNode(v, color))]] for color in range(0, self.d)] [subclauses, newHighestLiteral, cardinalityClauses] = SATUtils.atLeastRsub( \ groups, r, self.auxLiteral) self.auxLiteral = newHighestLiteral + 1 self.clauses += subclauses #repeat for positive literals groups = [[[-self.nodeToLiteral(GraphNode(u, color))], [self.nodeToLiteral(GraphNode(v, color))]] for color in range(0, self.d)] [subclauses, newHighestLiteral, cardinalityClauses] = SATUtils.atLeastRsub(\ groups, r, self.auxLiteral) self.auxLiteral = newHighestLiteral + 1 self.clauses += subclauses # returns whether or not u and v share a common neighbor def sharesNeighbor(self, u, v): if u == v: # a node doesn't count as its own neighbor's neighbor return False # u and v share a neighbor if v is in the neighbor list of any of u's neighbors for neighbor in self.nodeDict[u]: if self.isAdjacent(v, neighbor): return True return False # returns whether or not u and v are adjacent def isAdjacent(self, u, v): return u in self.nodeDict[v] @staticmethod def generateKClique(nodesInClique): nodeDict = dict() for i, a in enumerate(nodesInClique): for b in nodesInClique[i + 1:]: if a != b: if a not in nodeDict: nodeDict[a] = [b] else: nodeDict[a].append(b) if b not in nodeDict: nodeDict[b] = [a] else: nodeDict[b].append(a) return nodeDict @staticmethod def mergeAintoB(adjListA, adjListB): for k, v in adjListA.items(): if k not in adjListB: adjListB[k] = v else: adjListB[k] += v @staticmethod def merged(adjListA, adjListB): bCopy = adjListB.copy() GraphColoring.mergeAintoB(adjListA, bCopy) return bCopy #Path graph # A graph where n-2 vertices have exactly 2 neighbors and 2 vertices have 1 neighbor and all vertices are connected # AKA a binary tree with only left or only right children @staticmethod def P(n, zeroIndexed=True): if n < 1: raise ValueError() if n == 1: return {0 if zeroIndexed else 1: []} nodeDict = dict() if zeroIndexed: for i in range(0, n): if i == 0: nodeDict[0] = (1, ) elif i == n - 1: nodeDict[n - 1] = (n - 2, ) else: nodeDict[i] = (i - 1, i + 1) else: for i in range(1, n + 1): if i == 1: nodeDict[1] = (2, ) elif i == n: nodeDict[n] = (n - 1, ) else: nodeDict[i] = (i - 1, i + 1) return nodeDict # Cycle graph # A graph where all vertices are part of a single loop of length n @staticmethod def C(n, zeroIndexed=True): if n < 1: raise ValueError() if n == 1: return {0 if zeroIndexed else 1: []} nodeDict = dict() if zeroIndexed: for i in range(0, n): nodeDict[i] = ((i - 1) % n, (i + 1) % n) else: for i in range(1, n + 1): nodeDict[i] = (((i - 2) % n) + 1, ((i) % n) + 1) return nodeDict # Cycle graph # A graph where all vertices connected to all other vertices @staticmethod def K(n, zeroIndexed=True): if n < 1: raise ValueError() if n == 1: return {0 if zeroIndexed else 1: []} nodeDict = dict() if zeroIndexed: for i in range(0, n): nodeDict[i] = tuple(list(range(0, i)) + list(range(i + 1, n))) else: for i in range(1, n + 1): nodeDict[i] = tuple( list(range(1, n + 1)[:(i - 1)]) + list(range(1, n + 1)[i:])) return nodeDict # disconnects each connected edge and connects each disconnected edge @staticmethod def invert(nodeDict): nodeSet = set(nodeDict.keys()) newNodeDict = dict() for vertex, edges in nodeDict.items(): newNodeDict[vertex] = tuple( nodeSet.difference(set(list(edges) + [vertex]))) return newNodeDict # https://en.wikipedia.org/wiki/Cartesian_product_of_graphs @staticmethod def cartesianProduct(G, H): vertexSet = itertools.product(G.keys(), H.keys()) print(list(vertexSet)) nodeDict = dict() for i, vertexA in enumerate( list(itertools.product(G.keys(), H.keys()))[:-1]): for vertexB in list(itertools.product(G.keys(), H.keys()))[i:]: u = vertexA[0] up = vertexA[1] v = vertexB[0] vp = vertexB[1] # distinct vertices (u,u') and (v,v') are adjacent iff: # u=v and u' is adjacent to v' if u == v and up in H[vp]: if vertexA not in nodeDict: nodeDict[vertexA] = [vertexB] else: nodeDict[vertexA] += [vertexB] if vertexB not in nodeDict: nodeDict[vertexB] = [vertexA] else: nodeDict[vertexB] += [vertexA] # u'=v' and u is adjacent to v if up == vp and u in G[v]: if vertexA not in nodeDict: nodeDict[vertexA] = [vertexB] else: nodeDict[vertexA] += [vertexB] if vertexB not in nodeDict: nodeDict[vertexB] = [vertexA] else: nodeDict[vertexB] += [vertexA] return nodeDict # https://en.wikipedia.org/wiki/Lexicographic_product_of_graphs @staticmethod def lexicographicProduct(G, H, fromScratch=True): vertexSet = itertools.product(G.keys(), H.keys()) print(list(vertexSet)) nodeDict = dict() for i, vertexA in enumerate( list(itertools.product(G.keys(), H.keys()))[:-1]): for vertexB in list(itertools.product(G.keys(), H.keys()))[i:]: u = vertexA[0] up = vertexA[1] v = vertexB[0] vp = vertexB[1] # distinct vertices (u,u') and (v,v') are adjacent iff: # u is adjacent to v if u in G[v]: if vertexA not in nodeDict: nodeDict[vertexA] = [vertexB] else: nodeDict[vertexA] += [vertexB] if vertexB not in nodeDict: nodeDict[vertexB] = [vertexA] else: nodeDict[vertexB] += [vertexA] # u=v and u' is adjacent to v' if u == v and up in H[vp]: if vertexA not in nodeDict: nodeDict[vertexA] = [vertexB] else: nodeDict[vertexA] += [vertexB] if vertexB not in nodeDict: nodeDict[vertexB] = [vertexA] else: nodeDict[vertexB] += [vertexA] return nodeDict # https://en.wikipedia.org/wiki/Strong_product_of_graphs @staticmethod def strongProduct(G, H, fromScratch=True): if fromScratch: vertexSet = itertools.product(G.keys(), H.keys()) print(list(vertexSet)) nodeDict = dict() for i, vertexA in enumerate( list(itertools.product(G.keys(), H.keys()))[:-1]): for vertexB in list(itertools.product(G.keys(), H.keys()))[i:]: u = vertexA[0] up = vertexA[1] v = vertexB[0] vp = vertexB[1] # distinct vertices (u,u') and (v,v') are adjacent iff: # u=v and u' is adjacent to v' if u == v and up in H[vp]: if vertexA not in nodeDict: nodeDict[vertexA] = [vertexB] else: nodeDict[vertexA] += [vertexB] if vertexB not in nodeDict: nodeDict[vertexB] = [vertexA] else: nodeDict[vertexB] += [vertexA] # u'=v' and u is adjacent to v if up == vp and u in G[v]: if vertexA not in nodeDict: nodeDict[vertexA] = [vertexB] else: nodeDict[vertexA] += [vertexB] if vertexB not in nodeDict: nodeDict[vertexB] = [vertexA] else: nodeDict[vertexB] += [vertexA] # u is adjacent to v and u' is adjacent to v' if u in G[v] and up in H[vp]: if vertexA not in nodeDict: nodeDict[vertexA] = [vertexB] else: nodeDict[vertexA] += [vertexB] if vertexB not in nodeDict: nodeDict[vertexB] = [vertexA] else: nodeDict[vertexB] += [vertexA] return nodeDict else: return GraphColoring.merged(GraphColoring.cartesianProduct(G, H), GraphColoring.tensorProduct(G, H)) # https://en.wikipedia.org/wiki/Tensor_product_of_graphs @staticmethod def tensorProduct(G, H): vertexSet = itertools.product(G.keys(), H.keys()) print(list(vertexSet)) nodeDict = dict() for i, vertexA in enumerate( list(itertools.product(G.keys(), H.keys()))[:-1]): for vertexB in list(itertools.product(G.keys(), H.keys()))[i:]: u = vertexA[0] up = vertexA[1] v = vertexB[0] vp = vertexB[1] # distinct vertices (u,u') and (v,v') are adjacent iff: # u is adjacent to v and u' is adjacent to v' if u in G[v] and up in H[vp]: if vertexA not in nodeDict: nodeDict[vertexA] = [vertexB] else: nodeDict[vertexA] += [vertexB] if vertexB not in nodeDict: nodeDict[vertexB] = [vertexA] else: nodeDict[vertexB] += [vertexA] return nodeDict # Graph corresponding to the United States state border connections # https://writeonly.wordpress.com/2009/03/20/adjacency-list-of-states-of-the-united-states-us/ # which disagrees with http://mathworld.wolfram.com/ContiguousUSAGraph.html @staticmethod def US(contiguous=False): nodeDict = { ('AK',): (),\ ('AL',): (('MS',),('TN',),('GA',),('FL',),),\ ('AR',): (('MO',),('TN',),('MS',),('LA',),('TX',),('OK',),),\ ('AZ',): (('CA',),('NV',),('UT',),('NM',),),\ ('CA',): (('OR',),('NV',),('AZ',),),\ ('CO',): (('WY',),('NE',),('KS',),('OK',),('NM',),('UT',),),\ ('CT',): (('NY',),('MA',),('RI',),),\ ('DC',): (('MD',),('VA',),),\ ('DE',): (('MD',),('PA',),('NJ',),),\ ('FL',): (('AL',),('GA',),),\ ('GA',): (('FL',),('AL',),('TN',),('NC',),('SC',),),\ ('HI',): (),\ ('IA',): (('MN',),('WI',),('IL',),('MO',),('NE',),('SD',),),\ ('ID',): (('MT',),('WY',),('UT',),('NV',),('OR',),('WA',),),\ ('IL',): (('IN',),('KY',),('MO',),('IA',),('WI',),),\ ('IN',): (('MI',),('OH',),('KY',),('IL',),),\ ('KS',): (('NE',),('MO',),('OK',),('CO',),),\ ('KY',): (('IN',),('OH',),('WV',),('VA',),('TN',),('MO',),('IL',),),\ ('LA',): (('TX',),('AR',),('MS',),),\ ('MA',): (('RI',),('CT',),('NY',),('NH',),('VT',),),\ ('MD',): (('VA',),('WV',),('PA',),('DC',),('DE',),),\ ('ME',): (('NH',),),\ ('MI',): (('WI',),('IN',),('OH',),),\ ('MN',): (('WI',),('IA',),('SD',),('ND',),),\ ('MO',): (('IA',),('IL',),('KY',),('TN',),('AR',),('OK',),('KS',),('NE',),),\ ('MS',): (('LA',),('AR',),('TN',),('AL',),),\ ('MT',): (('ND',),('SD',),('WY',),('ID',),),\ ('NC',): (('VA',),('TN',),('GA',),('SC',),),\ ('ND',): (('MN',),('SD',),('MT',),),\ ('NE',): (('SD',),('IA',),('MO',),('KS',),('CO',),('WY',),),\ ('NH',): (('VT',),('ME',),('MA',),),\ ('NJ',): (('DE',),('PA',),('NY',),),\ ('NM',): (('AZ',),('CO',),('OK',),('TX',),),\ ('NV',): (('ID',),('UT',),('AZ',),('CA',),('OR',),),\ ('NY',): (('NJ',),('PA',),('VT',),('MA',),('CT',),),\ ('OH',): (('PA',),('WV',),('KY',),('IN',),('MI',),),\ ('OK',): (('KS',),('MO',),('AR',),('TX',),('NM',),('CO',),),\ ('OR',): (('CA',),('NV',),('ID',),('WA',),),\ ('PA',): (('NY',),('NJ',),('DE',),('MD',),('WV',),('OH',),),\ ('RI',): (('CT',),('MA',),),\ ('SC',): (('GA',),('NC',),),\ ('SD',): (('ND',),('MN',),('IA',),('NE',),('WY',),('MT',),),\ ('TN',): (('KY',),('VA',),('NC',),('GA',),('AL',),('MS',),('AR',),('MO',),),\ ('TX',): (('NM',),('OK',),('AR',),('LA',),),\ ('UT',): (('ID',),('WY',),('CO',),('AZ',),('NV',),),\ ('VA',): (('NC',),('TN',),('KY',),('WV',),('MD',),('DC',),),\ ('VT',): (('NY',),('NH',),('MA',),),\ ('WA',): (('ID',),('OR',),),\ ('WI',): (('MI',),('MN',),('IA',),('IL',),),\ ('WV',): (('OH',),('PA',),('MD',),('VA',),('KY',),),\ ('WY',): (('MT',),('SD',),('NE',),('CO',),('UT',),('ID',),)\ } if contiguous: del nodeDict[('AK', )] del nodeDict[('HI', )] return nodeDict
def __init__(self, height=0, width=0): self.height = height self.width = width self.game = None self.literalAllocator = LiteralAllocator() self.cnf = CNF()
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