def sameCount(E, partialCount, boardColor): # Final partial counts should be equal to the full count for c in range(rowNum * columnNum + 1): E.add_constraint(iff(totalCount[c], partialCount[rowNum- 1][columnNum - 1][c])) # You can't have more pieces than you've already seen for i in range(rowNum): for j in range(columnNum): for c in range((i * 7) + j + 2,rowNum * columnNum + 1): E.add_constraint(~partialCount[i][j][c]) # First index: only black piece or red piece could possibly be true E.add_constraint(iff(partialCount[0][0][0], ~boardColor[0][0])) E.add_constraint(iff(partialCount[0][0][1], boardColor[0][0])) #General pattern: Looks at the other color pieces to decide the current color piece. for x in range(1, rowNum * columnNum): i = x // columnNum j = x % columnNum E.add_constraint(iff(partialCount[i][j][0], partialCount[(i-1) if (j==0) else i][(columnNum-1) if (j==0) else (j-1)][0] & ~boardColor[i][j])) for c in range(1,x+2): increased = partialCount[(i-1) if (j==0) else i][(columnNum-1) if (j==0) else (j-1)][c-1] & boardColor[i][j] stay_same = partialCount[(i-1) if (j==0) else i][(columnNum-1) if (j==0) else (j-1)][c] & ~boardColor[i][j] E.add_constraint(iff(partialCount[i][j][c], increased | stay_same)) return E
def set_truth_encodings(): """ Sets the constraints required to determine the state of the middle square """ # If there are any adjacent squares with a True y condition, the middle square # is a mine. See the boolean condition guide (top) for an explanation E.add_constraint( iff((y[1][1] | y[1][2] | y[1][3] | y[2][1] | y[2][3] | y[3][1] | y[3][2] | y[3][3]), m[2][2])) # If there are any adjacent squares with a True x condition, the middle square # is safe. See the boolean condition guide (top) for an explanation E.add_constraint( iff((x[1][1] | x[1][2] | x[1][3] | x[2][1] | x[2][3] | x[3][1] | x[3][2] | x[3][3]), s[2][2])) # If the middle square isn't a mine or safe, it is unknown E.add_constraint(iff(~m[2][2] & ~s[2][2], u[2][2]))
def validBoard(E): for i in range(rowNum): for j in range(columnNum): # If position(i, j) is empty, then neither black or red can occupy position(i, j). E.add_constraint(emptyBoard[i][j] >> (~redBoard[i][j] & ~blackBoard[i][j])) # If position(i, j) is red, then neither black and empty can occupy position(i, j) # Calls gravity constraint E.add_constraint(redBoard[i][j] >> (~blackBoard[i][j] & ~emptyBoard[i][j] & gravityRule(i, j))) # If position(i, j) is black, then neither red and empty can occupy position(i, j) # Calls gravity constraint E.add_constraint(blackBoard[i][j] >> (~redBoard[i][j] & ~emptyBoard[i][j] & gravityRule(i, j))) # Here to make sure implication works properly above, exactly one has to be true. E.add_constraint(emptyBoard[i][j] | redBoard[i][j] | blackBoard[i][j]) # General: ColorWin if and only if there is ColorRowWin, or # ColorColumnWin, or ColorDiagonalWin. E.add_constraint(iff(BlackWin, (BlackColumnWin() | BlackRowWin() | leftBlackDiagonalWin() | rightBlackDiagonalWin()))) E.add_constraint(iff(RedWin, (RedColumnWin() | RedRowWin() | leftRedDiagonalWin() | rightRedDiagonalWin()))) # General: NoWin if and only if there is notColorRowWin, and # notColorColumnWin, and notColorDiagonalWin. E.add_constraint(iff(NoWin, ((~BlackColumnWin() & ~BlackRowWin() & ~leftBlackDiagonalWin() & ~rightBlackDiagonalWin()) & (~RedColumnWin() & ~RedRowWin() & ~leftRedDiagonalWin() & ~rightRedDiagonalWin())))) #All posibilities of Connect Four Game outcome E.add_constraint(iff(BlackWin, (~RedWin & ~NoWin))) E.add_constraint(iff(RedWin, (~BlackWin & ~NoWin))) E.add_constraint(iff(NoWin, (~RedWin & ~BlackWin))) return E
def columnWin(E, winColumnColor, boardColor): for i in range(rowNum - 3): for j in range(columnNum): # Winning column and its position of either 4 red or 4 black slots within the column. E.add_constraint(iff(winColumnColor[i][j], (boardColor[i][j] & boardColor[i+1][j] & boardColor[i+2][j] & boardColor[i+3][j]))) # Checks that there is a possible route to play in order to win by a column unless top row if (i > 0): E.add_constraint(winColumnColor[i][j] >> (emptyBoard[i-1][j])) #Checks that only one column can win special = winColumnColor[i][j] false = ~true for i2 in range(rowNum - 3): for j2 in range(columnNum): if (i != i2) | (j != j2): false |= winColumnColor[i2][j2] E.add_constraint(special >> ~false) return E
def rowWin(E, winRowColor, boardColor): for i in range(rowNum): for j in range(columnNum - 3): #Winning row and its position of either 4 red or 4 black slots within the row. E.add_constraint(iff(winRowColor[i][j], (boardColor[i][j] & boardColor[i][j + 1] & boardColor[i][j + 2] & boardColor[i][j + 3]))) #Checks that there is at least one possible route to play in order to win by a row unless top row if (i > 0): E.add_constraint(winRowColor[i][j] >> (emptyBoard[i-1][j] | emptyBoard[i-1][j + 1] | emptyBoard[i-1][j + 2] | emptyBoard[i-1][j + 3])) #Checks that only a single row channel can be a winning row channel special = winRowColor[i][j] false = ~true for i2 in range(rowNum): for j2 in range(columnNum - 3): if (i != i2): false |= winRowColor[i2][j2] E.add_constraint(special >> ~false) return E
def rightDiagonalWin(E, rightWinDiagonalColor, boardColor): for i in range(rowNum - 3): for j in range(columnNum - 4, columnNum): #Winning diagonal going left and down. E.add_constraint(iff(rightWinDiagonalColor[i][j-3], (boardColor[i][j] & boardColor[i+1][j-1] & boardColor[i+2][j-2] & boardColor[i+3][j-3]))) # Checks that there is a possible route to play in order to win by a right diagonal unless top row if (i > 0): E.add_constraint(rightWinDiagonalColor[i][j-3] >> (emptyBoard[i-1][j] | emptyBoard[i][j-1] | emptyBoard[i+1][j - 2] | emptyBoard[i+2][j - 3])) # Only one right facing diagonal channel can be a winning diagonal channel special = rightWinDiagonalColor[i][j-3] false = ~true for i2 in range(rowNum - 3): for j2 in range(columnNum - 4, columnNum): if (i != i2) | (j != j2): if (i - 1 != i2) | (j + 1 != j2): if (i - 2 != i2) | (j + 2 != j2): if (i + 1 != i2) | (j - 1 != j2): if (i + 2 != i2) | (j - 2 != j2): false |= rightWinDiagonalColor[i2][j2-3] E.add_constraint(special >> ~false) return E
def test_iff(a: nnf.NNF, b: nnf.NNF): c = operators.iff(a, b) for model in nnf.all_models(c.vars()): assert ((a.satisfied_by(model) == b.satisfied_by(model) ) == c.satisfied_by(model))
def encode_circuit_search(original_theory, num_gates, models=[]): ######## # Vars # ######## # Inputs to the circuit are the variables of the input theory inputs = [Var(v) for v in original_theory.vars()] if models: input_clones = clone_varset(models, inputs, inputs) else: input_clones = {} # Gates are either & | or ~ gates = [] gate_modalities = {} for i in range(num_gates): gates.append(Var(Gate(i))) gate_modalities[gates[-1]] = {} for m in ['and', 'or', 'not']: gate_modalities[gates[-1]][m] = Var(GateType(gates[-1], m)) if models: gate_clones = clone_varset(models, inputs, gates) else: gate_clones = {} # Single (arbitrary) gate is the circuit output output = gates[0] # Connections between inputs/gates to gates, and transitivity connections = {} unconnections = {} for src in inputs + gates[1:]: connections[src] = {} for dst in gates: if src != dst: connections[src][dst] = Var(Connection(src, dst)) if dst not in unconnections: unconnections[dst] = {} unconnections[dst][src] = connections[src][dst] C = connections # convenience orders = {} for src in gates: orders[src] = {} for dst in gates: orders[src][dst] = Var(Order(src,dst)) ############### # Constraints # ############### ''' add decorators for simplifying adding constraints (i.e. a conjunct)''' conjuncts = [] # Orderings (to forbid cycles in the circuit) for g1 in gates: # Connection implies orders for src in connections: for dst in connections[src]: if isinstance(src.name, Gate) and isinstance(dst.name, Gate): conjuncts.append(~connections[src][dst] | orders[src][dst]) # Can't order before yourself conjuncts.append(~orders[g1][g1]) # Transitive closure for g2 in gates: for g3 in gates: conjuncts.append(~orders[g1][g2] | ~orders[g2][g3] | orders[g1][g3]) if FORCE_TREE: # At max one outgoing connection on a gate for src in gates[1:]: for dst1 in connections[src]: for dst2 in connections[src]: if dst1 != dst2: conjuncts.append(~connections[src][dst1] | ~connections[src][dst2]) # Every gate has at least one input for dst in unconnections: conjuncts.append(Or(unconnections[dst].values())) # Every gate has at most two inputs and negation gates have at most one for j in gates: for i1 in inputs+gates[1:]: for i2 in inputs+gates[1:]: if not unique([j,i1,i2]): continue conjuncts.append(~gate_modalities[j]['not'] | ~C[i1][j] | ~C[i2][j]) for i3 in inputs+gates[1:]: if not unique([j,i1,i2,i3]): continue conjuncts.append(~C[i1][j] | ~C[i2][j] | ~C[i3][j]) # Every gate has exactly one modality for g in gates: # At least one conjuncts.append(Or(gate_modalities[g].values())) # At most one for m1 in ['and', 'or', 'not']: remaining = set(['and', 'or', 'not']) - set([m1]) conjuncts.append(Or([~gate_modalities[g][m2] for m2 in remaining])) # Re-usable theories notneg_cache = {} def notneg(src, dst, cloned_src=None): if not cloned_src: cloned_src = src if (cloned_src,dst) not in notneg_cache: notneg_cache[(cloned_src,dst)] = ~C[src][dst] | cloned_src return notneg_cache[(cloned_src,dst)] notpos_cache = {} def notpos(src, dst, cloned_src=None): if not cloned_src: cloned_src = src if (cloned_src,dst) not in notpos_cache: notpos_cache[(cloned_src,dst)] = ~C[src][dst] | ~cloned_src return notpos_cache[(cloned_src,dst)] # Implement the gates for g in gates: ins = [i for i in inputs+gates[1:] if i != g] conjuncts.append(~gate_modalities[g]['and'] | iff(g, And([notneg(src,g) for src in ins]))) t = Or([notpos(src,g).negate() for src in ins]) conjuncts.append(~gate_modalities[g]['or'] | iff(g, t)) conjuncts.append(~gate_modalities[g]['not'] | iff(g, t.negate())) for m in models: bitvec = model_to_bitvec(m, inputs) orig_mapping = {**{input_clones[bitvec][i]: i for i in inputs if i != g}, **{gate_clones[bitvec][i]: i for i in gates[1:] if i != g}} ins = orig_mapping.keys() conjuncts.append(~gate_modalities[g]['and'] | iff(gate_clones[bitvec][g], And([notneg(orig_mapping[src],g,src) for src in ins]))) t = Or([notpos(orig_mapping[src],g,src).negate() for src in ins]) conjuncts.append(~gate_modalities[g]['or'] | iff(gate_clones[bitvec][g], t)) conjuncts.append(~gate_modalities[g]['not'] | iff(gate_clones[bitvec][g], t.negate())) # Finally, lock in the models if models: for m in models: bitvec = model_to_bitvec(m, inputs) for var in m: if m[var]: conjuncts.append(input_clones[bitvec][Var(var)]) else: conjuncts.append(~input_clones[bitvec][Var(var)]) if original_theory.satisfied_by(m): conjuncts.append(gate_clones[bitvec][output]) else: conjuncts.append(~gate_clones[bitvec][output]) else: for model in all_models(original_theory.vars()): t = false # negating the conjunction because of the implication: flips the signs for var,val in model.items(): if val: t |= ~Var(var) else: t |= Var(var) if original_theory.satisfied_by(model): t |= output else: t |= ~output conjuncts.append(t) versions = {} example = {} for c in conjuncts: cn = c.simplify().to_CNF() stats = "(%d / %d / %d) > (%d / %d / %d)" % (c.simplify().size(), c.simplify().height(), len(c.simplify().vars()), cn.size(), cn.height(), len(cn.vars())) example[stats] = str(c.simplify()) versions[stats] = versions.get(stats, 0) + 1 print("Conjunct stats:") for k in versions: print("\n - (%d) %s: %s" % (versions[k], k, example[k])) T = And(conjuncts) return T.simplify(), inputs, gates, output, connections, unconnections, gate_modalities
def build_general_constraints(self): E = self.theory t = self.props.t x = self.props.x r = self.props.r c = self.props.c A = self.props.A # Checking that each row and column have the correct number of tents # Will need to be changed from exhaustive listing to some adder later # This also verifies that the row and column hints are set properly for i in range(self.num_rows): # Ensure only one of r[i][...] is set E.add_constraint(( r[i][0] & ~r[i][1] & ~r[i][2] & ~r[i][3]) | \ (~r[i][0] & r[i][1] & ~r[i][2] & ~r[i][3]) | \ (~r[i][0] & ~r[i][1] & r[i][2] & ~r[i][3]) | \ (~r[i][0] & ~r[i][1] & ~r[i][2] & r[i][3])) # no tents E.add_constraint(iff(r[i][0], ~x[i][0] & ~x[i][1] & ~x[i][2] & ~x[i][3] & ~x[i][4])) # 1 tent E.add_constraint(iff(r[i][1], ( x[i][0] & ~x[i][1] & ~x[i][2] & ~x[i][3] & ~x[i][4]) | \ (~x[i][0] & x[i][1] & ~x[i][2] & ~x[i][3] & ~x[i][4]) | \ (~x[i][0] & ~x[i][1] & x[i][2] & ~x[i][3] & ~x[i][4]) | \ (~x[i][0] & ~x[i][1] & ~x[i][2] & x[i][3] & ~x[i][4]) | \ (~x[i][0] & ~x[i][1] & ~x[i][2] & ~x[i][3] & x[i][4]))) # 2 tents E.add_constraint(iff(r[i][2], ( x[i][0] & ~x[i][1] & x[i][2] & ~x[i][3] & ~x[i][4]) | \ ( x[i][0] & ~x[i][1] & ~x[i][2] & x[i][3] & ~x[i][4]) | \ ( x[i][0] & ~x[i][1] & ~x[i][2] & ~x[i][3] & x[i][4]) | \ (~x[i][0] & x[i][1] & ~x[i][2] & x[i][3] & ~x[i][4]) | \ (~x[i][0] & x[i][1] & ~x[i][2] & ~x[i][3] & x[i][4]) | \ (~x[i][0] & ~x[i][1] & x[i][2] & ~x[i][3] & x[i][4]))) # 3 tents E.add_constraint(iff(r[i][3], x[i][0] & ~x[i][1] & x[i][2] & ~x[i][3] & x[i][4])) for j in range(self.num_cols): # Ensure only one of c[j][...] is set E.add_constraint(( c[j][0] & ~c[j][1] & ~c[j][2] & ~c[j][3]) | \ (~c[j][0] & c[j][1] & ~c[j][2] & ~c[j][3]) | \ (~c[j][0] & ~c[j][1] & c[j][2] & ~c[j][3]) | \ (~c[j][0] & ~c[j][1] & ~c[j][2] & c[j][3])) # no tents E.add_constraint(iff(c[j][0], ~x[0][j] & ~x[1][j] & ~x[2][j] & ~x[3][j] & ~x[4][j])) # 1 tent E.add_constraint(iff(c[j][1], ( x[0][j] & ~x[1][j] & ~x[2][j] & ~x[3][j] & ~x[4][j]) | \ (~x[0][j] & x[1][j] & ~x[2][j] & ~x[3][j] & ~x[4][j]) | \ (~x[0][j] & ~x[1][j] & x[2][j] & ~x[3][j] & ~x[4][j]) | \ (~x[0][j] & ~x[1][j] & ~x[2][j] & x[3][j] & ~x[4][j]) | \ (~x[0][j] & ~x[1][j] & ~x[2][j] & ~x[3][j] & x[4][j]))) # 2 tents E.add_constraint(iff(c[j][2], ( x[0][j] & ~x[1][j] & x[2][j] & ~x[3][j] & ~x[4][j]) | \ ( x[0][j] & ~x[1][j] & ~x[2][j] & x[3][j] & ~x[4][j]) | \ ( x[0][j] & ~x[1][j] & ~x[2][j] & ~x[3][j] & x[4][j]) | \ (~x[0][j] & x[1][j] & ~x[2][j] & x[3][j] & ~x[4][j]) | \ (~x[0][j] & x[1][j] & ~x[2][j] & ~x[3][j] & x[4][j]) | \ (~x[0][j] & ~x[1][j] & x[2][j] & ~x[3][j] & x[4][j]))) # 3 tents E.add_constraint(iff(c[j][3], x[0][j] & ~x[1][j] & x[2][j] & ~x[3][j] & x[4][j])) for i in range(self.num_rows): for j in range(self.num_cols): ### Constrain that tents and trees can't be in the same square E.add_constraint((~x[i][j] & ~t[i][j]) | (~x[i][j] & t[i][j]) | ( x[i][j] & ~t[i][j])) ### Constrain that tents can't be adjacent # For each cell in the grid, if there is a tent there, then there can't be one: # to the right, below, below and to the right, or below and to the left. # I.e. # |x|1 # -+-+- # 4|2|3 # If x is a tent, then 1,2,3,4 must not be tents. # We need to be careful to not exceed the bounds of our arrays however. adjacent_cells = [] # Check if 1 is in bounds if j < (self.num_cols - 1): adjacent_cells.append(x[i][j+1]) # Check if 2 is in bounds if i < (self.num_rows - 1): adjacent_cells.append(x[i+1][j]) # 3 is in bounds iff both 1 and 2 are in bounds. if len(adjacent_cells) == 2: adjacent_cells.append(x[i+1][j+1]) # Check if 4 is in bounds if i < (self.num_rows - 1) and j > 0: adjacent_cells.append(x[i+1][j-1]) # Reduce adjacent cell list into formula saying that those cells are false # (if we have any adjacent cells) if len(adjacent_cells) > 0: formula = reduce(lambda acc,x: acc & x, (~x for x in adjacent_cells)) E.add_constraint(x[i][j] >> formula) ### Constrain that tent must be adjacent to tree # For each cell in the grid, if there is a tent there, then there must be # a tree adjacent. # I.e. # |1| # -+-+- # 3|x|4 # -+-+- # |2| # If x is a tent, then one (or more) of 1,2,3,4 must be a tree. # We need to be careful to not exceed the bounds of our arrays however. adjacent_cells = [] # Check if 1 is in bounds if i > 0: adjacent_cells.append(t[i-1][j]) # Check if 2 is in bounds if i < (self.num_rows - 1): adjacent_cells.append(t[i+1][j]) # Check if 3 is in bounds if j > 0: adjacent_cells.append(t[i][j-1]) # Check if 4 is in bounds if j < (self.num_cols - 1): adjacent_cells.append(t[i][j+1]) # Reduce adjacent cell list into formula saying that one of those cells is true # (if we have any adjacent cells) if len(adjacent_cells) > 0: formula = reduce(lambda acc,x: acc | x, adjacent_cells) E.add_constraint(x[i][j] >> formula) ### Constrain that if t(i,j) is true, then exactly one of A(d,i,j) must be true for all d # Get all A(d,i,j) that are valid (not out of bounds) adjacency_entries = [A[d][i][j] for d in range(len(TentsAndTreesTheory.DIRECTIONS)) if A[d][i][j] is not None] # Build up (A & ~B & ~C) | (~A & B & ~C) | (~A & ~B & C) expression parts = [] for positive_adj in adjacency_entries: # Build individual (A & ~B & ~C) and put it into parts part_formula = positive_adj for other_adj in adjacency_entries: if other_adj == positive_adj: continue part_formula &= ~other_adj parts.append(part_formula) # OR all individual parts together formula = reduce(lambda acc,x: acc | x, parts) E.add_constraint(t[i][j] >> formula) ### Constrain that if x(i,j) is true, then exactly one of the A entries pointing at it must be true # Get all A(d,i,j) that are valid (not out of bounds) adjacency_entries = [self.safe_A(d,i-oi,j-oj) for d,(oi,oj) in enumerate(TentsAndTreesTheory.DIRECTIONS) if self.safe_A(d,i-oi,j-oj) is not None] # Build up (A & ~B & ~C) | (~A & B & ~C) | (~A & ~B & C) expression parts = [] for positive_adj in adjacency_entries: # Build individual (A & ~B & ~C) and put it into parts part_formula = positive_adj for other_adj in adjacency_entries: if other_adj == positive_adj: continue part_formula &= ~other_adj parts.append(part_formula) # OR all individual parts together formula = reduce(lambda acc,x: acc | x, parts) E.add_constraint(x[i][j] >> formula) ### More adjacency constraints, direction-specific for d,(offset_i,offset_j) in enumerate(TentsAndTreesTheory.DIRECTIONS): # Check if grid in this direction exists by checking the A matrix if A[d][i][j] is None: continue ### Constrain that if A(d,i,j) is true, then t(i,j) is true E.add_constraint(A[d][i][j] >> t[i][j]) ### Constrain that if A(d,i,j) is true, then x((i,j) + dirn) is true E.add_constraint(A[d][i][j] >> x[i+offset_i][j+offset_j])