#!/bin/python3 class Operator(): # Represents a binary operator def __init__(self, matrix): self.matrix = tuple([tuple(j) for j in matrix]) def __eq__(self, op2): # To identify duplicates return (self.matrix == op2.matrix) def __hash__(self): # To accelerate duplicate search return hash(self.matrix) def __repr__(self): return "Operator(" + str(self.matrix) + ")" def apply(self, s1, s2): # Generate a -> (s1(a) self s2(a)) a = s1.value b = s2.value result = [self.matrix[a[i]+1][b[i]+1] for i in range(3)] return State(result) def apply_2d(self, s1, s2): # Generate (a, b) -> (s1(a) self s2(b)) result = [[self.matrix[s1.value[i]+1][s2.value[j]+1] for i in range(3)] for j in range(3)] return Operator(result) def compose_right(self, op2): # Generate (a, b) -> (a self (a op2 b)) result = [[self.matrix[i][op2.matrix[i][j]+1] for i in range(3)] for j in range(3)] return Operator(result) def compose_left(self, op2): # Generate (a, b) -> ((a op2 b) self b) result = [[self.matrix[op2.matrix[i][j]+1][j] for i in range(3)] for j in range(3)] return Operator(result) def is_degenerate(self): val = self.matrix[0][0] for i in range(3): for j in range(3): if (self.matrix[i][j] != val): return False return True def is_self_similar(self): for i in range(3): if (self.matrix[i][i]+1 != i): return False return True def is_commutative(self): for i in range(3): for j in range(3): if (self.matrix[i][j] != self.matrix[j][i]): return False return True def is_associative(self): for i in range(3): for j in range(3): for k in range(3): if (self.matrix[i][self.matrix[j][k]+1] != self.matrix[self.matrix[i][j]+1][k]): return False return True def is_distributive(self, op_or): for i in range(3): for j in range(3): for k in range(3): op_comp1 = self.matrix[op_or.matrix[i][j]+1][k] op_comp2 = op_or.matrix[self.matrix[i][k]+1][self.matrix[j][k]+1] if (op_comp1 != op_comp2): return False return True class State(): # Represents an unary operator def __init__(self, value): self.value = tuple(value) def __eq__(self, s2): return (self.value == s2.value) def __hash__(self): return hash(self.value) def __repr__(self): return "State(" + str(self.value) + ")" def apply(self, s2): # Generate a -> self(s2(a)) op_comp = [self.value[s2.value[i]+1] for i in range(3)] return State(op_comp) def apply_2d(self, op): # Generate (a, b) -> self(a op b) op_comp = [[self.value[op.matrix[i][j]+1] for i in range(3)] for j in range(3)] return Operator(op_comp) def is_degenerate(self): val = self.value[0] for i in self.value: if (val != i): return False return True def is_half_deMorgan(op_or, op_and, op_not): result = True op_comp1 = op_not.apply_2d(op_or) op_comp2 = op_and.apply_2d(op_not, op_not) return op_comp1 == op_comp2 def gen_op(a=0, b=0, gop=[[0 for i in range(3)] for j in range(3)]): op = [[gop[j][i] for i in range(3)] for j in range(3)] b1 = b+1 a1 = a if b1==3: a1 = a+1 b1 = 0 if (a >= 3 or b >= 3): yield Operator(op) else: for k in [-1, 0, 1]: op[a][b] = k yield from gen_op(a1, b1, op) def gen_not(a=0, gop=[0 for i in range(3)]): op = [gop[i] for i in range(3)] if (a >= 3): yield State(op) else: for k in [-1, 0, 1]: op[a] = k yield from gen_not(a+1, op) def chose2(vals): for i in vals: for j in vals: if (i!=j): yield (i, j) def reachables(operators): # Populate the reachable operators with identities and constants reachables_default = { Operator([[1, 1, 1], [1, 1, 1], [1, 1, 1]]), # (a, b) -> 1 Operator([[0, 0, 0], [0, 0, 0], [0, 0, 0]]), # (a, b) -> 0 Operator([[-1, -1, -1], [-1, -1, -1], [-1, -1, -1]]), # (a, b) -> i Operator([[-1, -1, -1], [0, 0, 0], [1, 1, 1]]), # (a, b) -> a Operator([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]]) # (a, b) -> b } reachables = reachables_default | set(operators) num_reachables = 0 old_reachables = set() count = 0 while (num_reachables < len(reachables) and len(reachables) < 3**9): num_reachables = len(reachables) new_reachables = reachables.copy() for f in reachables: for g in reachables: result = f.compose_right(g) # (a, b) -> f(a, g(a, b)) if (result not in new_reachables): new_reachables.add(result) #print("r", len(new_reachables), f, g, result) result = f.compose_left(g) # (a, b) -> f(g(a, b), b) if (result not in new_reachables): new_reachables.add(result) #print("l", len(new_reachables), f, g, result) if (len(new_reachables) == 3**9): break old_reachables = reachables reachables = new_reachables count += 1 #print(count, len(reachables)) return reachables def is_complete(operators): return (len(reachables(operators)) == (3**9)) def print_op3(op_or, op_and, op_not): #op_xor = make_xor(op_or, op_and, op_not) syms = ["i", "0", "1"] print(" or | i 0 1 and | i 0 1 not |")# xor | i 0 1") print("----+------- -----+------- -----+---")# -----+-------") for line1, line2, line3, sym in zip(op_or.matrix, op_and.matrix, op_not.value, syms): disp_line = " " + sym + " |" for val in line1: disp_line += " " + syms[val+1] disp_line += " " + sym + " |" for val in line2: disp_line += " " + syms[val+1] disp_line += " " + sym + " | " + syms[line3+1] print(disp_line) print() candidates = set() count = 0 for op in gen_op(): if (op.is_commutative() and op.is_associative() and not op.is_degenerate()):# and op.is_self_similar()): candidates.add(op) # An identity, to turn the "not" into an equivalent binary operator op_id2d_1 = Operator([[-1, -1, -1], [0, 0, 0], [1, 1, 1]]) for op_or, op_and in chose2(candidates): if (op_and.is_distributive(op_or) and not op_or.is_distributive(op_and)): for op_not in gen_not(): if ( not op_not.is_degenerate() and is_half_deMorgan(op_or, op_and, op_not) and # not(a+b) = not(a)⋅not(b) #is_half_deMorgan(op_and, op_or, op_not) and # not(a⋅b) = not(a)+not(b) is_complete({op_or, op_and, op_not.apply_2d(op_id2d_1)}) ): count += 1 print_op3(op_or, op_and, op_not) print(count)