commit 9ff2d22e51ad478eb51269c7234c0af6514305eb Author: Alex Thannhauser Date: Mon Jun 2 15:46:40 2025 -0500 first commit! diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a07cda8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +simplifications.bin + diff --git a/main.py b/main.py new file mode 100755 index 0000000..7b80a1c --- /dev/null +++ b/main.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 + +import readline + +import pickle; + +from heapq import heappush, heappop + +W = 0b0101_0101_0101_0101 +X = 0b0011_0011_0011_0011 +Y = 0b0000_1111_0000_1111 +Z = 0b0000_0000_1111_1111 + +MASK = 0b1111_1111_1111_1111 + +def pretty(exp): + match exp: + case ("literal", x): + return str(x); + case ("variable", x): + return x + case ("not", inner): + return f"(!{pretty(inner)})" + case ("or", left, right): + return f"({pretty(left)} || {pretty(right)})" + case ("nor", left, right): + return f"({pretty(left)} !| {pretty(right)})" + case ("and", left, right): + return f"({pretty(left)} && {pretty(right)})" + case ("nand", left, right): + return f"({pretty(left)} !& {pretty(right)})" + case ("less-than", left, right): + return f"({pretty(left)} < {pretty(right)})" + case ("less-than-equal", left, right): + return f"({pretty(left)} <= {pretty(right)})" + case ("greater-than", left, right): + return f"({pretty(left)} > {pretty(right)})" + case ("greater-than-equal", left, right): + return f"({pretty(left)} >= {pretty(right)})" + case ("equal", left, right): + return f"({pretty(left)} == {pretty(right)})" + case ("not-equal", left, right): + return f"({pretty(left)} != {pretty(right)})" + case _: + print(exp); + assert(not "TODO"); + +def calculate_simplifications(): + lookup = dict() # truthtable -> expression + + costs = dict() # truthtable -> cost + + todo = [set() for _ in range(100)] # indexed by cost, set of truthtables. + + def prequeue(truthtable, expression, cost): + lookup[truthtable] = expression; + costs[truthtable] = cost; + todo[cost].add(truthtable); + + # literals: + prequeue(0b1111_1111_1111_1111, ("literal", 1), cost = 1); + prequeue(0b0000_0000_0000_0000, ("literal", 0), cost = 1); + + # variables: + prequeue(W, ("variable", "w"), cost = 0); + prequeue(X, ("variable", "x"), cost = 0); + prequeue(Y, ("variable", "y"), cost = 0); + prequeue(Z, ("variable", "z"), cost = 0); + + # completely unnecessary critera, alphabetical variables is more + # aesthetically pleasing, says Benson. + def is_aesthetically_better(this, that): + def extract_variables(exp): + match exp: + case ("literal", x): + return (); + case ("variable", x): + return (x, ) + case ("not", inner): + return extract_variables(inner); + case (_, left, right): + return extract_variables(left) + extract_variables(right); + case _: + print(exp); + assert(not "TODO"); + + return extract_variables(this) < extract_variables(that); + + unary_operators = { + 'not': lambda x: ~x, + } + + binary_operators = { + 'or': lambda x, y: x | y, + 'and': lambda x, y: x & y, + + 'nor': lambda x, y: ~(x | y) & MASK, + 'nand': lambda x, y: ~(x & y) & MASK, + + 'less-than': lambda x, y: ~x & y, + 'less-than-equal': lambda x, y: ~x | y, + + 'greater-than': lambda x, y: x & ~y, + 'greater-than-equal': lambda x, y: x | ~y, + + 'equal': lambda x, y: ~(x ^ y), + 'not-equal': lambda x, y: (x ^ y), + } + + min_cost = 0; + + while sum(len(x) for x in todo): + truthtables = todo[min_cost]; + + if not truthtables: + min_cost += 1; + continue; + + todo_count = sum(len(x) for x in todo); + + assert(todo_count <= 65536); + + my_truthtable = min(truthtables); + truthtables.discard(my_truthtable); + my_cost = min_cost; + my_expression = lookup[my_truthtable]; + + # print(f'{65536 - todo_count} of 65536 ({(65536 - todo_count) / 65536 * 100:.2f}%)'); + + print(f'{65536 - todo_count} of 65536 ({(65536 - todo_count) / 65536 * 100:.2f}%): [{my_cost}] {pretty(my_expression)}'); + + # print([len(x) for x in todo]) + + # print(f'{len(todo)}; {my_cost}; {len(lookup)}') + + # print(f'{my_truthtable:016b}: {my_expression}') + # print(f'{my_truthtable:016b}: {pretty(my_expression)}') + + def consider(new_truthtable, new_expression, new_cost): + if new_truthtable not in costs: + todo[new_cost].add(new_truthtable); + + costs[new_truthtable] = new_cost + lookup[new_truthtable] = new_expression + elif new_cost < costs[new_truthtable] or (new_cost == costs[new_truthtable] and is_aesthetically_better(new_expression, lookup[new_truthtable])): + current_cost = costs[new_truthtable]; + + assert(new_cost >= min_cost); + + todo[current_cost].discard(new_truthtable); + todo[new_cost].add(new_truthtable); + + costs[new_truthtable] = new_cost + lookup[new_truthtable] = new_expression + + # consider unary operators: + for name, function in sorted(unary_operators.items()): + unary_truthtable = function(my_truthtable) & MASK; + unary_expression = (name, my_expression); + unary_cost = my_cost + 1; + + consider(unary_truthtable, unary_expression, unary_cost); + + # consider binary operators: + for name, function in sorted(binary_operators.items()): + for other_truthtable, other_expression in sorted(lookup.items()): + # x + y + binary_truthtable = function(my_truthtable, other_truthtable); + binary_truthtable = binary_truthtable & MASK + binary_expression = (name, my_expression, other_expression); + binary_cost = my_cost + 1 + costs[other_truthtable]; + + consider(binary_truthtable, binary_expression, binary_cost); + + # y + x + binary_truthtable = function(other_truthtable, my_truthtable); + binary_truthtable = binary_truthtable & MASK + binary_expression = (name, other_expression, my_expression); + binary_cost = my_cost + 1 + costs[other_truthtable]; + + consider(binary_truthtable, binary_expression, binary_cost); + + return costs, lookup + +pathname = "simplifications.bin" + +try: + with open(pathname, "rb") as stream: + costs, lookup = pickle.load(stream); +except FileNotFoundError: + print("Oh! looks like you're running this for the first time"); + print("I'll have to build up my cache of simplifications"); + print("This may take a while."); + print("I'll only have to do this once."); + + print(); + input("any input to start:"); + print(); + + costs, lookup = calculate_simplifications(); + + print(); + print("done!"); + print(); + + with open(pathname, "wb") as stream: + pickle.dump((costs, lookup), stream); + + print(); + print("saved!"); + print(); + +# for truthtable, exp in lookup.items(): +# print(f'{truthtable:016b}: {pretty(exp)}') + +def create_parser(): + import pyparsing as pp + + from pyparsing import Forward, Suppress, Keyword, Group, ZeroOrMore, Optional, Literal + + root = Forward() + + literal = pp.Word('01'); + + variable = pp.Word('wxyz') + + highest = literal | variable | (Suppress('(') + root + Suppress(')')); + + prefix_expression = Group(Literal("!") + highest) | highest + + relational_expression = \ + Group(prefix_expression + Literal('<=') + prefix_expression) \ + | Group(prefix_expression + Literal('>=') + prefix_expression) \ + | Group(prefix_expression + Literal('>') + prefix_expression) \ + | Group(prefix_expression + Literal('<') + prefix_expression) \ + | Group(prefix_expression + Literal('==') + prefix_expression) \ + | Group(prefix_expression + Literal('!=') + prefix_expression) \ + | prefix_expression; + + logical_and_expression = Forward(); + logical_and_expression <<= \ + Group(relational_expression + Literal('&&') + logical_and_expression) \ + | Group(relational_expression + Literal('!&') + logical_and_expression) \ + | relational_expression; + + logical_or_expression = Forward() + logical_or_expression <<= \ + Group(logical_and_expression + Literal('||') + logical_or_expression) \ + | Group(logical_and_expression + Literal('!|') + logical_or_expression) \ + | logical_and_expression; + + root <<= logical_or_expression; + return root; + +parser = create_parser(); + +print(""" +Please give a C-style expression using only variables 'w', 'x', 'y' and 'z'. +You can use any of the following operators: '!' (not), '&&' (and), '||' (or), +'<' ('and' with left argument negated), '>' ('and' with right argument negated), +'<=' ('or' with left argument negated), '>=' ('or' with right argument negated), +'!=' (xor), '==' (negated xor), '!|' (nor), '!&' (nand). +The "simpliest" expression is the one that uses the fewest number of operators. +It should be noted that more than one expression could be considered the +"simpliest". This program chooses one arbitrarily (alphabetical order). +"""); + +def evaluate(exp): + match exp: + case "0": + return 0; + case "1": + return MASK; + case "w": + return W; + case "x": + return X; + case "y": + return Y; + case "z": + return Z; + case ("!", subexp): + return ~evaluate(subexp) & MASK; + case (left, "||", right): + return evaluate(left) | evaluate(right); + case (left, "!|", right): + return ~(evaluate(left) | evaluate(right)) & MASK; + case (left, "&&", right): + return evaluate(left) & evaluate(right); + case (left, "!&", right): + return ~(evaluate(left) & evaluate(right)) & MASK; + case (left, "<", right): + return (~evaluate(left) & evaluate(right)) & MASK; + case (left, "<=", right): + return (~evaluate(left) | evaluate(right)) & MASK; + case (left, ">", right): + return (evaluate(left) & ~evaluate(right)) & MASK; + case (left, ">=", right): + return (evaluate(left) | ~evaluate(right)) & MASK; + case (left, "==", right): + return ~(evaluate(left) ^ evaluate(right)) & MASK; + case (left, "!=", right): + return evaluate(left) ^ evaluate(right); + case _: + print(exp); + assert(not "TODO"); + +while True: + try: + raw_exp = input(">>> "); + except EOFError: + print("exit"); + break; + + try: + exp = parser.parseString(raw_exp, parseAll = True).asList()[0] + except: + print("syntax error"); + continue; + + truthtable = evaluate(exp); + + print(f"done in {costs[truthtable]} operations:"); + + print(f"0b{truthtable:016b}: {pretty(lookup[truthtable])}"); + + # print(pretty(lookup[truthtable])); + + + + + + + + + +