first commit!

This commit is contained in:
Alex Thannhauser 2025-06-02 15:46:40 -05:00
commit 9ff2d22e51
2 changed files with 340 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
simplifications.bin

337
main.py Executable file
View file

@ -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]));