Upload inference.py with huggingface_hub
Browse files- inference.py +566 -0
inference.py
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
MBA Expression Simplifier β Standalone Inference
|
| 3 |
+
=================================================
|
| 4 |
+
Usage:
|
| 5 |
+
python inference.py "(x & y) + (x | y)"
|
| 6 |
+
from inference import simplify
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import json, re, sys, time
|
| 10 |
+
from dataclasses import dataclass
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Union, Optional
|
| 13 |
+
|
| 14 |
+
_HERE = Path(__file__).resolve().parent
|
| 15 |
+
|
| 16 |
+
# ββ AST βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
+
|
| 18 |
+
@dataclass(frozen=True)
|
| 19 |
+
class Var:
|
| 20 |
+
name: str
|
| 21 |
+
def __repr__(self): return self.name
|
| 22 |
+
|
| 23 |
+
@dataclass(frozen=True)
|
| 24 |
+
class Const:
|
| 25 |
+
val: int
|
| 26 |
+
def __repr__(self): return str(self.val)
|
| 27 |
+
|
| 28 |
+
@dataclass(frozen=True)
|
| 29 |
+
class BinOp:
|
| 30 |
+
op: str; left: 'Expr'; right: 'Expr'
|
| 31 |
+
def __repr__(self): return f"({self.op} {self.left} {self.right})"
|
| 32 |
+
|
| 33 |
+
@dataclass(frozen=True)
|
| 34 |
+
class UnOp:
|
| 35 |
+
op: str; arg: 'Expr'
|
| 36 |
+
def __repr__(self): return f"({self.op} {self.arg})"
|
| 37 |
+
|
| 38 |
+
Expr = Union[Var, Const, BinOp, UnOp]
|
| 39 |
+
|
| 40 |
+
def size(e) -> int:
|
| 41 |
+
match e:
|
| 42 |
+
case Var(_) | Const(_): return 1
|
| 43 |
+
case BinOp(_, l, r): return 1 + size(l) + size(r)
|
| 44 |
+
case UnOp(_, a): return 1 + size(a)
|
| 45 |
+
|
| 46 |
+
# ββ EVALUATOR βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 47 |
+
|
| 48 |
+
def make_ev(bits: int):
|
| 49 |
+
MASK = (1 << bits) - 1
|
| 50 |
+
def ev(e, env: dict) -> int:
|
| 51 |
+
match e:
|
| 52 |
+
case Var(n): return env.get(n, 0) & MASK
|
| 53 |
+
case Const(v): return v & MASK
|
| 54 |
+
case BinOp('+',l,r): return (ev(l,env) + ev(r,env)) & MASK
|
| 55 |
+
case BinOp('-',l,r): return (ev(l,env) - ev(r,env)) & MASK
|
| 56 |
+
case BinOp('*',l,r): return (ev(l,env) * ev(r,env)) & MASK
|
| 57 |
+
case BinOp('&',l,r): return ev(l,env) & ev(r,env)
|
| 58 |
+
case BinOp('|',l,r): return ev(l,env) | ev(r,env)
|
| 59 |
+
case BinOp('^',l,r): return ev(l,env) ^ ev(r,env)
|
| 60 |
+
case UnOp('~',a): return (~ev(a,env)) & MASK
|
| 61 |
+
case UnOp('-',a): return (-ev(a,env)) & MASK
|
| 62 |
+
return ev
|
| 63 |
+
|
| 64 |
+
_ev8 = make_ev(8)
|
| 65 |
+
|
| 66 |
+
# ββ BFS FINGERPRINT (must match bfs_table_6.json exactly) ββββββββββββββββββββ
|
| 67 |
+
# Fixed 16-point evaluation set from poly_synth_final.py.
|
| 68 |
+
# Changing these invalidates the pre-computed table.
|
| 69 |
+
|
| 70 |
+
_EVAL_POINTS = [
|
| 71 |
+
(0,0),(1,0),(0,1),(1,1),
|
| 72 |
+
(2,3),(5,7),(11,13),(17,19),
|
| 73 |
+
(64,128),(128,64),(255,0),
|
| 74 |
+
(170,85),(204,51),(85,170),
|
| 75 |
+
(7,15),(31,63),
|
| 76 |
+
]
|
| 77 |
+
|
| 78 |
+
def _fp8(expr, v0: str, v1: str) -> tuple:
|
| 79 |
+
return tuple(_ev8(expr, {v0: px, v1: py}) for px, py in _EVAL_POINTS)
|
| 80 |
+
|
| 81 |
+
# ββ PREFIX PARSER (for loading bfs_table_6.json values) ββββββββββββββββββββββ
|
| 82 |
+
|
| 83 |
+
def _parse_prefix(s: str) -> Expr:
|
| 84 |
+
pos = [0]
|
| 85 |
+
|
| 86 |
+
def skip():
|
| 87 |
+
while pos[0] < len(s) and s[pos[0]] == ' ':
|
| 88 |
+
pos[0] += 1
|
| 89 |
+
|
| 90 |
+
def parse():
|
| 91 |
+
skip()
|
| 92 |
+
if pos[0] >= len(s): raise ValueError(f"Unexpected end: {s!r}")
|
| 93 |
+
c = s[pos[0]]
|
| 94 |
+
if c == '(':
|
| 95 |
+
pos[0] += 1; skip()
|
| 96 |
+
op_s = pos[0]
|
| 97 |
+
while pos[0] < len(s) and s[pos[0]] not in (' ', ')'):
|
| 98 |
+
pos[0] += 1
|
| 99 |
+
op = s[op_s:pos[0]]; skip()
|
| 100 |
+
left = parse(); skip()
|
| 101 |
+
if pos[0] < len(s) and s[pos[0]] == ')':
|
| 102 |
+
pos[0] += 1; return UnOp(op, left)
|
| 103 |
+
right = parse(); skip()
|
| 104 |
+
if pos[0] < len(s) and s[pos[0]] == ')': pos[0] += 1
|
| 105 |
+
return BinOp(op, left, right)
|
| 106 |
+
elif c.isdigit():
|
| 107 |
+
start = pos[0]
|
| 108 |
+
while pos[0] < len(s) and s[pos[0]].isdigit(): pos[0] += 1
|
| 109 |
+
return Const(int(s[start:pos[0]]))
|
| 110 |
+
elif c.isalpha() or c == '_':
|
| 111 |
+
start = pos[0]
|
| 112 |
+
while pos[0] < len(s) and (s[pos[0]].isalnum() or s[pos[0]] == '_'):
|
| 113 |
+
pos[0] += 1
|
| 114 |
+
return Var(s[start:pos[0]])
|
| 115 |
+
else:
|
| 116 |
+
raise ValueError(f"Unexpected {c!r} in {s!r}")
|
| 117 |
+
|
| 118 |
+
return parse()
|
| 119 |
+
|
| 120 |
+
# ββ BFS TABLE LOAD ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 121 |
+
|
| 122 |
+
_bfs_table: Optional[dict] = None
|
| 123 |
+
|
| 124 |
+
def _load_bfs() -> dict:
|
| 125 |
+
global _bfs_table
|
| 126 |
+
if _bfs_table is not None: return _bfs_table
|
| 127 |
+
path = _HERE / 'bfs_table_6.json'
|
| 128 |
+
if not path.exists():
|
| 129 |
+
print(f"Warning: {path} not found β BFS disabled.", file=sys.stderr)
|
| 130 |
+
_bfs_table = {}; return _bfs_table
|
| 131 |
+
t0 = time.perf_counter()
|
| 132 |
+
with open(path) as f: raw = json.load(f)
|
| 133 |
+
table = {}
|
| 134 |
+
for fp_str, expr_str in raw.items():
|
| 135 |
+
table[tuple(json.loads(fp_str))] = _parse_prefix(expr_str)
|
| 136 |
+
_bfs_table = table
|
| 137 |
+
print(f"BFS: {len(table):,} entries ({(time.perf_counter()-t0)*1000:.0f}ms)", flush=True)
|
| 138 |
+
return _bfs_table
|
| 139 |
+
|
| 140 |
+
def _rename_vars(expr, var_map: dict) -> Expr:
|
| 141 |
+
match expr:
|
| 142 |
+
case Var(name): return Var(var_map.get(name, name))
|
| 143 |
+
case Const(_): return expr
|
| 144 |
+
case BinOp(op,l,r): return BinOp(op, _rename_vars(l,var_map), _rename_vars(r,var_map))
|
| 145 |
+
case UnOp(op,a): return UnOp(op, _rename_vars(a,var_map))
|
| 146 |
+
|
| 147 |
+
# ββ ORACLE VERIFY βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 148 |
+
|
| 149 |
+
def _verify(e1, e2, bits: int, vars_tuple: tuple, n: int = 512) -> bool:
|
| 150 |
+
import random
|
| 151 |
+
MASK = (1 << bits) - 1
|
| 152 |
+
ev = make_ev(bits)
|
| 153 |
+
rng = random.Random(7)
|
| 154 |
+
for _ in range(n):
|
| 155 |
+
env = {v: rng.randint(0, MASK) for v in vars_tuple}
|
| 156 |
+
if ev(e1, env) != ev(e2, env): return False
|
| 157 |
+
return True
|
| 158 |
+
|
| 159 |
+
# ββ SYMBOLIC ENGINE (inlined from pipeline_v5.py) βββββββββββββββββββββββββββββ
|
| 160 |
+
|
| 161 |
+
def make_symbolic(bits: int):
|
| 162 |
+
MASK = (1 << bits) - 1
|
| 163 |
+
|
| 164 |
+
def rw(e):
|
| 165 |
+
match e:
|
| 166 |
+
case BinOp('+',BinOp('&',a,b),BinOp('|',c,d)) if {a,b}=={c,d}:
|
| 167 |
+
return BinOp('+',min(a,b,key=repr),max(a,b,key=repr))
|
| 168 |
+
case BinOp('+',BinOp('|',a,b),BinOp('&',c,d)) if {a,b}=={c,d}:
|
| 169 |
+
return BinOp('+',min(a,b,key=repr),max(a,b,key=repr))
|
| 170 |
+
case BinOp('-',BinOp('|',a,b),BinOp('&',c,d)) if {a,b}=={c,d}:
|
| 171 |
+
return BinOp('^',min(a,b,key=repr),max(a,b,key=repr))
|
| 172 |
+
case BinOp('-',BinOp('+',a,b),BinOp('*',Const(2),BinOp('&',c,d))) if {a,b}=={c,d}:
|
| 173 |
+
return BinOp('^',min(a,b,key=repr),max(a,b,key=repr))
|
| 174 |
+
case BinOp('-',BinOp('+',a,b),BinOp('*',BinOp('&',c,d),Const(2))) if {a,b}=={c,d}:
|
| 175 |
+
return BinOp('^',min(a,b,key=repr),max(a,b,key=repr))
|
| 176 |
+
case BinOp('-',UnOp('-',x),Const(1)): return UnOp('~',x)
|
| 177 |
+
case BinOp('+',UnOp('-',x),Const(v)) if (v&MASK)==MASK: return UnOp('~',x)
|
| 178 |
+
case BinOp('+',UnOp('~',x),Const(1)): return UnOp('-',x)
|
| 179 |
+
case BinOp('+',Const(1),UnOp('~',x)): return UnOp('-',x)
|
| 180 |
+
case BinOp('^',UnOp('~',a),UnOp('~',b)):
|
| 181 |
+
return BinOp('^',min(a,b,key=repr),max(a,b,key=repr))
|
| 182 |
+
case UnOp('~',BinOp('&',UnOp('~',a),UnOp('~',b))):
|
| 183 |
+
return BinOp('|',min(a,b,key=repr),max(a,b,key=repr))
|
| 184 |
+
case UnOp('~',BinOp('|',UnOp('~',a),UnOp('~',b))):
|
| 185 |
+
return BinOp('&',min(a,b,key=repr),max(a,b,key=repr))
|
| 186 |
+
case BinOp('|',a,BinOp('&',b,c)) if a==b or a==c: return a
|
| 187 |
+
case BinOp('|',BinOp('&',a,b),c) if c==a or c==b: return c
|
| 188 |
+
case BinOp('&',a,BinOp('|',b,c)) if a==b or a==c: return a
|
| 189 |
+
case BinOp('&',BinOp('|',a,b),c) if c==a or c==b: return c
|
| 190 |
+
case BinOp('^',a,BinOp('&',b,c)) if a==b: return BinOp('&',a,UnOp('~',c))
|
| 191 |
+
case BinOp('^',a,BinOp('&',b,c)) if a==c: return BinOp('&',a,UnOp('~',b))
|
| 192 |
+
case BinOp('^',BinOp('&',a,b),c) if c==a: return BinOp('&',c,UnOp('~',b))
|
| 193 |
+
case BinOp('^',BinOp('&',a,b),c) if c==b: return BinOp('&',c,UnOp('~',a))
|
| 194 |
+
case UnOp('-',BinOp('-',a,b)): return BinOp('-',b,a)
|
| 195 |
+
case BinOp('+',a,UnOp('-',b)) if a==b: return Const(0)
|
| 196 |
+
case BinOp('+',UnOp('-',a),b) if a==b: return Const(0)
|
| 197 |
+
case BinOp('-',a,UnOp('-',b)): return BinOp('+',a,b)
|
| 198 |
+
case BinOp('&',a,b) if a==b: return a
|
| 199 |
+
case BinOp('|',a,b) if a==b: return a
|
| 200 |
+
case BinOp('^',a,b) if a==b: return Const(0)
|
| 201 |
+
case BinOp('-',a,b) if a==b: return Const(0)
|
| 202 |
+
case BinOp('&',_,Const(0))|BinOp('&',Const(0),_): return Const(0)
|
| 203 |
+
case BinOp('|',x,Const(0))|BinOp('|',Const(0),x): return x
|
| 204 |
+
case BinOp('^',x,Const(0))|BinOp('^',Const(0),x): return x
|
| 205 |
+
case BinOp('^',x,Const(v)) if (v&MASK)==MASK: return UnOp('~',x)
|
| 206 |
+
case BinOp('^',Const(v),x) if (v&MASK)==MASK: return UnOp('~',x)
|
| 207 |
+
case BinOp('&',x,Const(v)) if (v&MASK)==MASK: return x
|
| 208 |
+
case BinOp('&',Const(v),x) if (v&MASK)==MASK: return x
|
| 209 |
+
case BinOp('|',_,Const(v))|BinOp('|',Const(v),_) if (v&MASK)==MASK: return Const(MASK)
|
| 210 |
+
case BinOp('&',a,UnOp('~',b)) if a==b: return Const(0)
|
| 211 |
+
case BinOp('&',UnOp('~',a),b) if a==b: return Const(0)
|
| 212 |
+
case BinOp('|',a,UnOp('~',b)) if a==b: return Const(MASK)
|
| 213 |
+
case BinOp('|',UnOp('~',a),b) if a==b: return Const(MASK)
|
| 214 |
+
case BinOp('^',a,UnOp('~',b)) if a==b: return Const(MASK)
|
| 215 |
+
case BinOp('+',x,Const(0))|BinOp('+',Const(0),x): return x
|
| 216 |
+
case BinOp('-',x,Const(0)): return x
|
| 217 |
+
case BinOp('*',x,Const(1))|BinOp('*',Const(1),x): return x
|
| 218 |
+
case BinOp('*',_,Const(0))|BinOp('*',Const(0),_): return Const(0)
|
| 219 |
+
case UnOp('-',UnOp('-',x)): return x
|
| 220 |
+
case UnOp('~',UnOp('~',x)): return x
|
| 221 |
+
case UnOp('-',Const(0)): return Const(0)
|
| 222 |
+
case UnOp('-',UnOp('~',x)): return BinOp('+',x,Const(1&MASK))
|
| 223 |
+
case UnOp('~',UnOp('-',x)): return BinOp('-',x,Const(1&MASK))
|
| 224 |
+
case BinOp('-',BinOp('+',a,b),c) if b==c: return a
|
| 225 |
+
case BinOp('-',BinOp('+',a,b),c) if a==c: return b
|
| 226 |
+
case BinOp('+',BinOp('-',a,b),c) if b==c: return a
|
| 227 |
+
case BinOp('-',a,BinOp('+',b,c)) if a==b: return UnOp('-',c)
|
| 228 |
+
case BinOp('-',a,BinOp('+',b,c)) if a==c: return UnOp('-',b)
|
| 229 |
+
case BinOp('-',BinOp('+',a,b),BinOp('+',c,d)) if a==c: return BinOp('-',b,d)
|
| 230 |
+
case BinOp('-',BinOp('+',a,b),BinOp('+',c,d)) if b==d: return BinOp('-',a,c)
|
| 231 |
+
case BinOp('-',BinOp('+',a,b),BinOp('+',c,d)) if a==d: return BinOp('-',b,c)
|
| 232 |
+
case BinOp('-',BinOp('+',a,b),BinOp('+',c,d)) if b==c: return BinOp('-',a,d)
|
| 233 |
+
case BinOp(op,Const(a),Const(b)):
|
| 234 |
+
match op:
|
| 235 |
+
case '+': return Const((a+b)&MASK)
|
| 236 |
+
case '-': return Const((a-b)&MASK)
|
| 237 |
+
case '*': return Const((a*b)&MASK)
|
| 238 |
+
case '&': return Const(a&b&MASK)
|
| 239 |
+
case '|': return Const((a|b)&MASK)
|
| 240 |
+
case '^': return Const((a^b)&MASK)
|
| 241 |
+
case UnOp('-',Const(a)): return Const((-a)&MASK)
|
| 242 |
+
case UnOp('~',Const(a)): return Const((~a)&MASK)
|
| 243 |
+
case BinOp(op,a,b) if op in('+','*','&','|','^') and repr(a)>repr(b):
|
| 244 |
+
return BinOp(op,b,a)
|
| 245 |
+
return None
|
| 246 |
+
|
| 247 |
+
def sym_pass(e):
|
| 248 |
+
match e:
|
| 249 |
+
case Var(_)|Const(_): return e
|
| 250 |
+
case BinOp(op,l,r):
|
| 251 |
+
nl=sym_pass(l); nr=sym_pass(r); ne=BinOp(op,nl,nr)
|
| 252 |
+
r2=rw(ne)
|
| 253 |
+
if r2 is None: return ne
|
| 254 |
+
return Const(r2.val&MASK) if isinstance(r2,Const) else r2
|
| 255 |
+
case UnOp(op,a):
|
| 256 |
+
na=sym_pass(a); ne=UnOp(op,na)
|
| 257 |
+
r2=rw(ne)
|
| 258 |
+
if r2 is None: return ne
|
| 259 |
+
return Const(r2.val&MASK) if isinstance(r2,Const) else r2
|
| 260 |
+
|
| 261 |
+
def sym(e, passes=30):
|
| 262 |
+
for _ in range(passes):
|
| 263 |
+
ne=sym_pass(e)
|
| 264 |
+
if ne==e: break
|
| 265 |
+
e=ne
|
| 266 |
+
return e
|
| 267 |
+
|
| 268 |
+
return sym, rw, sym_pass
|
| 269 |
+
|
| 270 |
+
# ββ NEURAL LAYER (optional) βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 271 |
+
|
| 272 |
+
class NeuralLayer:
|
| 273 |
+
def __init__(self, model_path: str, rw_fn, bits: int):
|
| 274 |
+
self.rw_fn=rw_fn; self.bits=bits; self.loaded=False
|
| 275 |
+
try:
|
| 276 |
+
import torch, torch.nn as nn
|
| 277 |
+
self.torch=torch; self.nn=nn
|
| 278 |
+
ckpt=torch.load(model_path, map_location='cpu', weights_only=False)
|
| 279 |
+
cfg=ckpt['config']
|
| 280 |
+
self.rules=ckpt['rule_vocab']; self.n_rules=len(self.rules)
|
| 281 |
+
self.char2id=ckpt['char_vocab']; self.max_len=cfg['max_len']
|
| 282 |
+
self.model=self._build(cfg); self.model.load_state_dict(ckpt['model_state'])
|
| 283 |
+
self.model.eval()
|
| 284 |
+
self.reduction_rules=[i for i,r in enumerate(self.rules)
|
| 285 |
+
if not r.startswith('comm_') and not r.startswith('fold_')]
|
| 286 |
+
self.loaded=True
|
| 287 |
+
n_params=sum(p.numel() for p in self.model.parameters())
|
| 288 |
+
print(f"Neural: {self.n_rules} rules, {n_params/1e6:.1f}M params", flush=True)
|
| 289 |
+
except Exception as ex:
|
| 290 |
+
print(f"Neural disabled: {ex}", file=sys.stderr)
|
| 291 |
+
|
| 292 |
+
def _build(self, cfg):
|
| 293 |
+
import torch.nn as nn; torch=self.torch
|
| 294 |
+
class RC(nn.Module):
|
| 295 |
+
def __init__(s,vocab_size,d_model,n_heads,n_layers,d_ff,max_len,n_rules,dropout=0.1):
|
| 296 |
+
super().__init__()
|
| 297 |
+
s.embed=nn.Embedding(vocab_size,d_model,padding_idx=0)
|
| 298 |
+
s.pos=nn.Embedding(max_len,d_model)
|
| 299 |
+
el=nn.TransformerEncoderLayer(d_model,n_heads,d_ff,dropout,batch_first=True,norm_first=True)
|
| 300 |
+
s.enc=nn.TransformerEncoder(el,n_layers)
|
| 301 |
+
s.norm=nn.LayerNorm(d_model); s.head=nn.Linear(d_model,n_rules)
|
| 302 |
+
def forward(s,x):
|
| 303 |
+
pm=(x==0); pos=torch.arange(x.size(1),device=x.device).unsqueeze(0)
|
| 304 |
+
e=s.embed(x)+s.pos(pos); enc=s.enc(e,src_key_padding_mask=pm)
|
| 305 |
+
L=(~pm).float().sum(1,keepdim=True).clamp(min=1)
|
| 306 |
+
return s.head(s.norm((enc*(~pm).unsqueeze(-1).float()).sum(1)/L))
|
| 307 |
+
RC.torch=self.torch
|
| 308 |
+
return RC(**cfg)
|
| 309 |
+
|
| 310 |
+
def _tok(self, expr_str):
|
| 311 |
+
ids=[self.char2id.get(c,1) for c in expr_str[:self.max_len]]
|
| 312 |
+
ids+=[0]*(self.max_len-len(ids))
|
| 313 |
+
return self.torch.tensor([ids],dtype=self.torch.long)
|
| 314 |
+
|
| 315 |
+
def search(self, expr, max_steps=50, top_k=5):
|
| 316 |
+
if not self.loaded: return expr
|
| 317 |
+
best=expr; bsz=size(expr); cur=expr; vis={repr(cur)}
|
| 318 |
+
for _ in range(max_steps):
|
| 319 |
+
try: rules=self._top_rules(repr(cur),top_k)
|
| 320 |
+
except Exception: break
|
| 321 |
+
improved=False
|
| 322 |
+
for rid in rules:
|
| 323 |
+
for cand in self._apply_everywhere(cur,self.rules[rid]):
|
| 324 |
+
k=repr(cand)
|
| 325 |
+
if k in vis: continue
|
| 326 |
+
if size(cand)<size(cur):
|
| 327 |
+
cur=cand; vis.add(k)
|
| 328 |
+
if size(cur)<bsz: best=cur; bsz=size(cur)
|
| 329 |
+
improved=True; break
|
| 330 |
+
if improved: break
|
| 331 |
+
if not improved: break
|
| 332 |
+
return best
|
| 333 |
+
|
| 334 |
+
def _top_rules(self, expr_str, top_k):
|
| 335 |
+
with self.torch.no_grad():
|
| 336 |
+
lgt=self.model(self._tok(expr_str))[0]
|
| 337 |
+
m=self.torch.full_like(lgt,float('-inf'))
|
| 338 |
+
for i in self.reduction_rules: m[i]=lgt[i]
|
| 339 |
+
return m.topk(min(top_k,len(self.reduction_rules))).indices.tolist()
|
| 340 |
+
|
| 341 |
+
def _apply_everywhere(self, expr, rule_name):
|
| 342 |
+
rw=self.rw_fn; results=[]
|
| 343 |
+
def try_at(node,rebuild):
|
| 344 |
+
r=rw(node)
|
| 345 |
+
if r is not None: results.append(rebuild(r))
|
| 346 |
+
match node:
|
| 347 |
+
case BinOp(op,l,r2):
|
| 348 |
+
try_at(l,lambda nl,op=op,r2=r2,rb=rebuild: rb(BinOp(op,nl,r2)))
|
| 349 |
+
try_at(r2,lambda nr,op=op,l=l,rb=rebuild: rb(BinOp(op,l,nr)))
|
| 350 |
+
case UnOp(op,a):
|
| 351 |
+
try_at(a,lambda na,op=op,rb=rebuild: rb(UnOp(op,na)))
|
| 352 |
+
try_at(expr, lambda x: x); results.sort(key=size); return results
|
| 353 |
+
|
| 354 |
+
# ββ INFIX PARSER + PRINTER ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 355 |
+
|
| 356 |
+
def _tokenize(s: str) -> list:
|
| 357 |
+
tokens=[]; i=0
|
| 358 |
+
while i<len(s):
|
| 359 |
+
c=s[i]
|
| 360 |
+
if c.isspace(): i+=1
|
| 361 |
+
elif s[i:i+2].lower().startswith('0x'):
|
| 362 |
+
j=i+2
|
| 363 |
+
while j<len(s) and s[j] in '0123456789abcdefABCDEF': j+=1
|
| 364 |
+
tokens.append(s[i:j]); i=j
|
| 365 |
+
elif c.isdigit():
|
| 366 |
+
j=i+1
|
| 367 |
+
while j<len(s) and s[j].isdigit(): j+=1
|
| 368 |
+
tokens.append(s[i:j]); i=j
|
| 369 |
+
elif c.isalpha() or c=='_':
|
| 370 |
+
j=i+1
|
| 371 |
+
while j<len(s) and (s[j].isalnum() or s[j]=='_'): j+=1
|
| 372 |
+
tokens.append(s[i:j]); i=j
|
| 373 |
+
elif c in '+-*&|^~()': tokens.append(c); i+=1
|
| 374 |
+
else: raise ValueError(f"Unknown char {c!r} in: {s!r}")
|
| 375 |
+
return tokens
|
| 376 |
+
|
| 377 |
+
def _parse_infix(tokens: list, bits: int) -> Expr:
|
| 378 |
+
MASK=(1<<bits)-1; pos=[0]
|
| 379 |
+
def peek(): return tokens[pos[0]] if pos[0]<len(tokens) else None
|
| 380 |
+
def consume(e=None):
|
| 381 |
+
t=tokens[pos[0]]
|
| 382 |
+
if e is not None and t!=e: raise ValueError(f"Expected {e!r}, got {t!r}")
|
| 383 |
+
pos[0]+=1; return t
|
| 384 |
+
def parse_or():
|
| 385 |
+
L=parse_xor()
|
| 386 |
+
while peek()=='|': consume(); L=BinOp('|',L,parse_xor())
|
| 387 |
+
return L
|
| 388 |
+
def parse_xor():
|
| 389 |
+
L=parse_and()
|
| 390 |
+
while peek()=='^': consume(); L=BinOp('^',L,parse_and())
|
| 391 |
+
return L
|
| 392 |
+
def parse_and():
|
| 393 |
+
L=parse_add()
|
| 394 |
+
while peek()=='&': consume(); L=BinOp('&',L,parse_add())
|
| 395 |
+
return L
|
| 396 |
+
def parse_add():
|
| 397 |
+
L=parse_mul()
|
| 398 |
+
while peek() in('+','-'):
|
| 399 |
+
op=consume(); L=BinOp(op,L,parse_mul())
|
| 400 |
+
return L
|
| 401 |
+
def parse_mul():
|
| 402 |
+
L=parse_un()
|
| 403 |
+
while peek()=='*': consume(); L=BinOp('*',L,parse_un())
|
| 404 |
+
return L
|
| 405 |
+
def parse_un():
|
| 406 |
+
if peek()=='~': consume(); return UnOp('~',parse_un())
|
| 407 |
+
if peek()=='-':
|
| 408 |
+
consume()
|
| 409 |
+
if peek() is not None and re.match(r'^\d+$',peek()):
|
| 410 |
+
return Const((-int(consume()))&MASK)
|
| 411 |
+
return UnOp('-',parse_un())
|
| 412 |
+
return parse_atom()
|
| 413 |
+
def parse_atom():
|
| 414 |
+
tok=peek()
|
| 415 |
+
if tok=='(': consume('('); e=parse_or(); consume(')'); return e
|
| 416 |
+
if tok and re.match(r'^0[xX][0-9a-fA-F]+$',tok): consume(); return Const(int(tok,16)&MASK)
|
| 417 |
+
if tok and re.match(r'^\d+$',tok): consume(); return Const(int(tok)&MASK)
|
| 418 |
+
if tok and re.match(r'^[a-zA-Z_]\w*$',tok): consume(); return Var(tok)
|
| 419 |
+
raise ValueError(f"Unexpected token {tok!r}")
|
| 420 |
+
ast=parse_or()
|
| 421 |
+
if pos[0]!=len(tokens): raise ValueError(f"Trailing: {tokens[pos[0]:]}")
|
| 422 |
+
return ast
|
| 423 |
+
|
| 424 |
+
_PREC={'|':1,'^':2,'&':3,'+':4,'-':4,'*':5}
|
| 425 |
+
|
| 426 |
+
def _to_infix(expr) -> str:
|
| 427 |
+
def emit(node,mp=0,ir=False):
|
| 428 |
+
match node:
|
| 429 |
+
case Var(n): return n
|
| 430 |
+
case Const(v): return str(v)
|
| 431 |
+
case UnOp(op,a): return f"{op}{emit(a,6)}"
|
| 432 |
+
case BinOp(op,l,r):
|
| 433 |
+
p=_PREC[op]; s=f"{emit(l,p,False)} {op} {emit(r,p,True)}"
|
| 434 |
+
return f"({s})" if p<mp or (p==mp and ir) else s
|
| 435 |
+
return emit(expr)
|
| 436 |
+
|
| 437 |
+
def _extract_vars(expr) -> tuple:
|
| 438 |
+
seen=[]
|
| 439 |
+
def w(n):
|
| 440 |
+
match n:
|
| 441 |
+
case Var(name):
|
| 442 |
+
if name not in seen: seen.append(name)
|
| 443 |
+
case BinOp(_,l,r): w(l); w(r)
|
| 444 |
+
case UnOp(_,a): w(a)
|
| 445 |
+
w(expr); return tuple(sorted(seen))
|
| 446 |
+
|
| 447 |
+
# ββ GLOBAL STATE (lazy) βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 448 |
+
|
| 449 |
+
_neural: Optional[NeuralLayer] = None
|
| 450 |
+
_sym_cache: dict = {}
|
| 451 |
+
|
| 452 |
+
def _get_sym(bits):
|
| 453 |
+
if bits not in _sym_cache: _sym_cache[bits]=make_symbolic(bits)
|
| 454 |
+
return _sym_cache[bits]
|
| 455 |
+
|
| 456 |
+
def _get_neural(model_path: Optional[str]) -> Optional[NeuralLayer]:
|
| 457 |
+
global _neural
|
| 458 |
+
if _neural is not None: return _neural
|
| 459 |
+
if model_path is None:
|
| 460 |
+
default = _HERE / 'mba_classifier_v2.pt'
|
| 461 |
+
if default.exists(): model_path=str(default)
|
| 462 |
+
else: return None
|
| 463 |
+
_,rw,_=_get_sym(8)
|
| 464 |
+
_neural=NeuralLayer(str(model_path), rw, bits=8)
|
| 465 |
+
return _neural
|
| 466 |
+
|
| 467 |
+
# ββ PUBLIC API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 468 |
+
|
| 469 |
+
def simplify(
|
| 470 |
+
expr: str,
|
| 471 |
+
bits: int = 8,
|
| 472 |
+
model_path: Optional[str] = None,
|
| 473 |
+
) -> dict:
|
| 474 |
+
"""
|
| 475 |
+
Simplify an MBA expression string.
|
| 476 |
+
|
| 477 |
+
Args:
|
| 478 |
+
expr: Infix expression. Operators: + - * & | ^ ~ (unary: - ~)
|
| 479 |
+
Variables: identifiers [a-zA-Z_]+.
|
| 480 |
+
Example: "(x & y) + (x | y)"
|
| 481 |
+
bits: Bit width β 8 or 32. Default 8.
|
| 482 |
+
model_path: Path to mba_classifier_v2.pt. Auto-detected if omitted.
|
| 483 |
+
|
| 484 |
+
Returns:
|
| 485 |
+
dict:
|
| 486 |
+
simplified (str) β simplified infix expression
|
| 487 |
+
original (str) β input
|
| 488 |
+
reduction (float) β size reduction ratio 0.0β1.0
|
| 489 |
+
strategy (str) β bfs / symbolic / neural / none
|
| 490 |
+
latency_ms (float) β wall-clock time
|
| 491 |
+
|
| 492 |
+
Raises:
|
| 493 |
+
ValueError: on parse error or unsupported bit width.
|
| 494 |
+
"""
|
| 495 |
+
if not isinstance(expr, str) or not expr.strip():
|
| 496 |
+
raise ValueError("expr must be a non-empty string")
|
| 497 |
+
if bits not in (8, 32):
|
| 498 |
+
raise ValueError("bits must be 8 or 32")
|
| 499 |
+
|
| 500 |
+
t0=time.perf_counter()
|
| 501 |
+
tokens=_tokenize(expr.strip())
|
| 502 |
+
ast=_parse_infix(tokens, bits)
|
| 503 |
+
vars_tuple=_extract_vars(ast)
|
| 504 |
+
if not vars_tuple: vars_tuple=('x',)
|
| 505 |
+
orig_size=size(ast); strategy="none"; result=ast
|
| 506 |
+
|
| 507 |
+
# 1. BFS (8-bit, β€2 vars)
|
| 508 |
+
if bits==8 and len(vars_tuple)<=2:
|
| 509 |
+
table=_load_bfs()
|
| 510 |
+
if table:
|
| 511 |
+
v0=vars_tuple[0]
|
| 512 |
+
v1=vars_tuple[1] if len(vars_tuple)>1 else '__'
|
| 513 |
+
fp=_fp8(ast, v0, v1)
|
| 514 |
+
if fp in table:
|
| 515 |
+
bfs_e=table[fp]
|
| 516 |
+
vm={}
|
| 517 |
+
if len(vars_tuple)>=1: vm['x']=vars_tuple[0]
|
| 518 |
+
if len(vars_tuple)>=2: vm['y']=vars_tuple[1]
|
| 519 |
+
bfs_e=_rename_vars(bfs_e, vm)
|
| 520 |
+
if size(bfs_e)<=orig_size and _verify(ast, bfs_e, bits, vars_tuple):
|
| 521 |
+
result=bfs_e; strategy="bfs"
|
| 522 |
+
|
| 523 |
+
# 2. Symbolic
|
| 524 |
+
if strategy=="none":
|
| 525 |
+
sym,_,_=_get_sym(bits)
|
| 526 |
+
sr=sym(ast)
|
| 527 |
+
if sr!=ast: result=sr; strategy="symbolic"
|
| 528 |
+
|
| 529 |
+
# 3. Neural (optional)
|
| 530 |
+
if strategy=="none":
|
| 531 |
+
neural=_get_neural(model_path)
|
| 532 |
+
if neural and neural.loaded:
|
| 533 |
+
nr=neural.search(ast)
|
| 534 |
+
if nr is not ast and size(nr)<=orig_size and _verify(ast, nr, bits, vars_tuple):
|
| 535 |
+
result=nr; strategy="neural"
|
| 536 |
+
|
| 537 |
+
out_size=size(result)
|
| 538 |
+
reduction=1.0-out_size/orig_size if orig_size>0 else 0.0
|
| 539 |
+
ms=round((time.perf_counter()-t0)*1e3, 1)
|
| 540 |
+
return {
|
| 541 |
+
"simplified": _to_infix(result),
|
| 542 |
+
"original": expr.strip(),
|
| 543 |
+
"reduction": round(reduction,3),
|
| 544 |
+
"strategy": strategy,
|
| 545 |
+
"latency_ms": ms,
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
# ββ CLI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 549 |
+
if __name__=="__main__":
|
| 550 |
+
import argparse
|
| 551 |
+
p=argparse.ArgumentParser(description="MBA Expression Simplifier")
|
| 552 |
+
p.add_argument("expr", help='e.g. "(x & y) + (x | y)"')
|
| 553 |
+
p.add_argument("--bits",type=int,default=8,choices=[8,32])
|
| 554 |
+
p.add_argument("--model",type=str,default=None)
|
| 555 |
+
args=p.parse_args()
|
| 556 |
+
try:
|
| 557 |
+
r=simplify(args.expr,bits=args.bits,model_path=args.model)
|
| 558 |
+
print(f"Input: {r['original']}")
|
| 559 |
+
print(f"Simplified: {r['simplified']}")
|
| 560 |
+
if r['original']!=r['simplified']:
|
| 561 |
+
print(f"Reduction: {r['reduction']*100:.0f}% ({r['strategy']})")
|
| 562 |
+
else:
|
| 563 |
+
print(f"Result: already minimal ({r['strategy']})")
|
| 564 |
+
print(f"Latency: {r['latency_ms']} ms")
|
| 565 |
+
except ValueError as e:
|
| 566 |
+
print(f"Error: {e}",file=sys.stderr); sys.exit(1)
|