import sys, json, random import numpy as np import torch sys.path.insert(0, '/home/maxwelhelp/all/math2nn') from bench import train_model, make_model, sweep_baseline, make_report, evaluate import garside_track as gt LR_GRID=[1e-3,3e-3,1e-2] EPOCHS=18 BATCH=128 def set_seed(seed): random.seed(seed); np.random.seed(seed); torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed) def make_examples(seed,n): return gt.samples(seed,n) def featurize(examples, mode): # Both systems receive 24-dimensional inputs, so the MLP topology is identical. x=np.zeros((len(examples),24),dtype=np.float32) for row,(word,base,y,k) in enumerate(examples): if mode=='raw': for g in word: x[row,g-1]+=1.0 x[row]/=len(word) else: fs=gt.factors(base) # discard p: literal Delta prefix is removable for f in fs: x[row,gt.factor_id(f) % 24]+=1.0 x[row]/=max(1,len(fs)) return x def dataset(seed,mode,ntr=400,nte=400): tr=make_examples(seed,ntr); te=make_examples(seed+5000,nte) return {'track':'braid_garside_classification','task':'classification','metric':'err', 'xtr':torch.tensor(featurize(tr,mode)), 'ytr':torch.tensor([e[2] for e in tr],dtype=torch.long), 'xte':torch.tensor(featurize(te,mode)), 'yte':torch.tensor([e[2] for e in te],dtype=torch.long), 'input_shape':(24,), 'out_dim':2} def train_one(seed,mode,lr): set_seed(seed) ds=dataset(seed,mode) net=make_model('mlp_tiny',ds['input_shape'],ds['out_dim']) _,metric,_=train_model(net,ds,epochs=EPOCHS,lr=lr,batch=BATCH,log=lambda *_:None) return float(metric) def main(): # sweep_baseline uses four seeds for selection and re-evaluates its best on all eight. base=sweep_baseline(lambda cfg: lambda seed: train_one(seed,'raw',cfg['lr']), [{'lr':v} for v in LR_GRID]) idea_sweep=[] for lr in LR_GRID: r=evaluate(lambda seed,lr=lr: train_one(seed,'factor',lr), seeds=(0,1,2,3)) idea_sweep.append({'cfg':{'lr':lr},'mean':r['mean']}) best_lr=min(idea_sweep,key=lambda z:z['mean'])['cfg']['lr'] idea=evaluate(lambda seed: train_one(seed,'factor',best_lr)) # Retest behavior on trained models, not an algebra-only signature. sig=[]; ratios=[] for seed in range(8): set_seed(seed); ds=dataset(seed,'factor'); net=make_model('mlp_tiny',(24,),2) net,_,_=train_model(net,ds,epochs=EPOCHS,lr=best_lr,batch=BATCH,log=lambda *_:None) net.eval() ex=make_examples(seed+5000,400) base_x=torch.tensor(featurize(ex,'factor')) aug=[] for w,b,y,k in ex: aug.append((gt.DELTA_WORD+b,b,y,1)) aug_x=torch.tensor(featurize(aug,'factor')) dev=next(net.parameters()).device with torch.no_grad(): a=net(base_x.to(dev)).argmax(1); bpred=net(aug_x.to(dev)).argmax(1) sig.append(float((a==bpred).float().mean())) ratios.extend([len(w)/max(1,len(gt.factors(b))) for w,b,y,k in aug]) signature={'trained_factor_prediction_agreement':float(np.mean(sig)), 'observed_generator_to_factor_ratio':float(np.mean(ratios)), 'expected_invariance':1.0,'confirmed':bool(np.mean(sig)>=0.95)} report=make_report('braid_garside_classification','mlp_tiny',base,idea, {'custom_track':{'name':'braid_garside_classification','file':'garside_track.py','domain':'algebraic braid presentations'}, 'mechanism_signature':signature, 'protocol_note':'Baseline and idea use the same MLP, seed data, epochs, batch size, and shared lr grid; only representation differs.'}) report['idea_sweep']=idea_sweep with open('bench_report.json','w') as f: json.dump(report,f,indent=2) print(json.dumps(report,indent=2)) if __name__=='__main__': main()