qox commited on
Commit
e5b8970
Β·
verified Β·
1 Parent(s): c3c79c1

Upload inference.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. 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)