Topological Reachable-Set Coverage Scheduler / stage2_bench.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  1import json, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5
  6import sys
  7sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  8from bench import get_dataset, make_model, train_model, sweep_baseline, evaluate, make_report
  9
 10TRACK='dynamics'; MODEL='rnn_small'
 11# Equal-budget settings; the union is used on both sides.
 12LR_GRID=[1e-3, 3e-3, 6e-3]
 13EPOCHS=12
 14N_TRAIN=400
 15N_POOL=1600
 16SEEDS=tuple(range(8))
 17
 18
 19def seed_all(s):
 20    random.seed(s); np.random.seed(s); torch.manual_seed(s)
 21    if torch.cuda.is_available():
 22        try: torch.cuda.manual_seed_all(s)
 23        except Exception: pass
 24
 25
 26def state_view(x):
 27    # Last observed pendulum state in each 8-step input window.
 28    return np.asarray(x, dtype=np.float64).reshape(len(x), 8, 3)[:, -1, :]
 29
 30
 31def residual(x):
 32    # Conservative, task-independent safety margin: theta and angular velocity.
 33    z=state_view(x)
 34    return np.minimum(1.25-np.abs(z[:,0]), 2.0-0.35*np.abs(z[:,1]))
 35
 36
 37def cover_radius(query, samples):
 38    # Small pool: exact vectorized nearest-neighbor radius.
 39    q=np.asarray(query); s=np.asarray(samples)
 40    best=np.full(len(q), np.inf)
 41    for j in range(0,len(s),128):
 42        d=((q[:,None,:]-s[None,j:j+128,:])**2).sum(2)
 43        best=np.minimum(best, np.sqrt(d.min(1)))
 44    return float(best.max()), best
 45
 46
 47def adaptive_indices(pool_x, n, seed, gamma=0.35):
 48    z=state_view(pool_x)
 49    rng=np.random.default_rng(seed)
 50    # Four seed states, then q = normalized distance + gamma*low-margin.
 51    chosen=list(rng.choice(len(pool_x), size=8, replace=False))
 52    chosen_set=set(chosen)
 53    scale=np.std(z,axis=0)+1e-6
 54    zn=z/scale
 55    r=residual(pool_x)
 56    eps=0.15
 57    while len(chosen)<n:
 58        sel=np.asarray(chosen)
 59        best=np.full(len(pool_x), np.inf)
 60        for j in range(0,len(sel),128):
 61            d=((zn[:,None,:]-zn[sel[j:j+128]][None,:,:])**2).sum(2)
 62            best=np.minimum(best, np.sqrt(d.min(1)))
 63        q=best + gamma*np.maximum(0.,eps-r)
 64        q[list(chosen_set)]=-np.inf
 65        k=int(np.argmax(q)); chosen.append(k); chosen_set.add(k)
 66    return np.asarray(chosen,dtype=int)
 67
 68
 69def prepare(seed, mode):
 70    # Pool and test set are generated from the same benchmark dynamics task.
 71    base=get_dataset(TRACK, seed, n_train=N_TRAIN, n_test=1000)
 72    pool=get_dataset(TRACK, seed+10000, n_train=N_POOL, n_test=1000)
 73    rng=np.random.default_rng(seed+991)
 74    if mode=='baseline': idx=rng.choice(N_POOL,size=N_TRAIN,replace=False)
 75    else: idx=adaptive_indices(pool['xtr'].numpy(),N_TRAIN,seed)
 76    ds=dict(base)
 77    ds['xtr']=pool['xtr'][idx].clone(); ds['ytr']=pool['ytr'][idx].clone()
 78    return ds, pool, idx
 79
 80
 81def run_one(seed, mode, cfg, return_net=False):
 82    seed_all(seed + (0 if mode=='baseline' else 100000))
 83    ds,pool,idx=prepare(seed,mode)
 84    net=make_model(MODEL, ds['input_shape'], ds['out_dim'])
 85    net,metric,hist=train_model(net,ds,epochs=EPOCHS,lr=float(cfg['lr']),batch=128,log=lambda *_:None)
 86    if return_net:
 87        return float(metric),net,ds,pool,idx
 88    return float(metric)
 89
 90
 91def math_check():
 92    rng=np.random.default_rng(4); cloud=rng.uniform(-1,1,(500,3)); samples=cloud[rng.choice(500,40,False)]
 93    # r(x)=0.2+L||x-c|| is L-Lipschitz; verify the stated lower bound.
 94    L=0.7; c=np.array([0.1,-0.2,0.3]); r=lambda a:0.2+L*np.linalg.norm(a-c,axis=1)
 95    d=cover_radius(cloud,samples)[0]; assert r(cloud).min() >= r(samples).min()-L*d-1e-10
 96    return {'checked':True,'L':L,'delta':d,'sample_min':float(r(samples).min()),'cloud_min':float(r(cloud).min()),'bound':float(r(samples).min()-L*d)}
 97
 98
 99def main():
100    check=math_check()
101    grid=[{'lr':x} for x in LR_GRID]
102    base=sweep_baseline(lambda cfg: lambda s: run_one(s,'baseline',cfg),grid,seeds=(0,1,2,3))
103    # Explicitly evaluate idea at all three settings; best is selected on the same tuning seeds.
104    idea_cfg_results=[]
105    for cfg in grid:
106        rr=evaluate(lambda s,cfg=cfg: run_one(s,'idea',cfg),seeds=(0,1,2,3))
107        idea_cfg_results.append((rr['mean'],cfg))
108    idea_cfg=min(idea_cfg_results,key=lambda x:x[0])[1]
109    idea=evaluate(lambda s: run_one(s,'idea',idea_cfg),seeds=SEEDS)
110    # Signature uses trained models, not a toy identity: compare pool coverage and
111    # boundary/general-region prediction errors for one paired seed.
112    bm,bnet,bds,bpool,bidx=run_one(0,'baseline',base['best_cfg'],True)
113    im,inet,ids,ipool,iidx=run_one(0,'idea',idea_cfg,True)
114    with torch.no_grad():
115        bx=bpool['xtr']; by=bpool['ytr']; ix=ipool['xtr']; iy=ipool['ytr']
116        bdev=next(bnet.parameters()).device; idev=next(inet.parameters()).device
117        bp=bnet(bx.to(bdev)).detach().cpu().numpy().reshape(-1); ip=inet(ix.to(idev)).detach().cpu().numpy().reshape(-1)
118    bz=state_view(bpool['xtr'].numpy()); iz=state_view(ipool['xtr'].numpy())
119    bdelta=cover_radius(bz,state_view(bpool['xtr'][bidx].numpy()))[0]
120    idelta=cover_radius(iz,state_view(ipool['xtr'][iidx].numpy()))[0]
121    br=residual(bpool['xtr'].numpy()); ir=residual(ipool['xtr'].numpy())
122    sig={'prediction':'adaptive reachable-state selection should reduce empirical coverage radius at equal simulator budget',
123         'observed':{'baseline_delta':bdelta,'idea_delta':idelta,'baseline_pool_mse':float(np.mean((bp-by.numpy())**2)),'idea_pool_mse':float(np.mean((ip-iy.numpy())**2)), 'baseline_min_sample_residual':float(br[bidx].min()),'idea_min_sample_residual':float(ir[iidx].min())},
124         'relative_delta_reduction_pct':float(100*(bdelta-idelta)/bdelta),
125         'confirmed':bool(idelta < bdelta)}
126    report=make_report(TRACK,MODEL,base,idea,extra={'math_check':check,'mechanism_signature':sig,'idea_grid_results':idea_cfg_results,'selected_idea_cfg':idea_cfg,'budget':{'epochs':EPOCHS,'train_samples':N_TRAIN,'pool_samples':N_POOL}})
127    Path('bench_report.json').write_text(json.dumps(report,indent=2))
128    print(json.dumps(report,indent=2))
129
130if __name__=='__main__': main()