// CPU reference EqPropNet, faithful to fhn.html.
// Used as the ground truth for GPU validation.
export const MN_ALPHA=1.08, MN_A1=(1-1/MN_ALPHA);
export const fhnF = (u)=> MN_A1*u - u*u*u;
export const fhnRho= (u)=> u<0?0:(u>1?1:u);
export const fhnRhoP=(u)=> (u>0 && u<1) ? 1 : 0;
export const sg = (u)=> 1/(1+Math.exp(-4*(u-0.5)));
export const sgp= (u)=>{ const s=sg(u); return 4*s*(1-s); };
export function rng(seed){ let s=seed>>>0; return ()=>{ s=(Math.imul(s,1664525)+1013904223)>>>0; return s/4294967296; }; }
// matrix utilities (double precision for orth — matches fhn.html)
function mm(A,B,m,k,n){ const C=new Float64Array(m*n);
for(let i=0;i=n){ const Gt=tr(G,m,n); const S=mm(Gt,G,n,m,n); const is=invSqrtSPD(S,n); return mm(G,is,m,n,n); }
else { const Gt=tr(G,m,n); const S=mm(G,Gt,m,n,m); const is=invSqrtSPD(S,m); return mm(is,G,m,m,n); }
}
export class EqPropNet {
// sizes:[D,H,...,O] mode:'adaptive'|'fhn' opt:'sgd'|'adagrad'|'adago'
constructor(sizes, seed=7, mode='adaptive', opt='adago'){
this.sizes=sizes; this.L=sizes.length; this.mode=mode; this.opt=opt;
const r=rng(seed>>>0||1); this.W=[]; this.b=[];
for(let l=0;lnew Float64Array(w.length));
this.GB=this.b.map(b=>new Float64Array(b.length));
this.MW=this.W.map(w=>new Float64Array(w.length));
this.MB=this.b.map(b=>new Float64Array(b.length));
this.vW=new Float64Array(this.L-1);
this.vB=new Float64Array(this.L-1);
this.OW=new Array(this.L-1).fill(null);
this.OW_K=4; this.bc=0;
this.gamma=0.6; this.betaN=(mode==='fhn')?0.9:0.5;
this.adpC=0.15; this.adpSteps=3; this.tauInv=0.0;
this.rmin=0.1; this.escale=0.4;
}
relax(x, iters, dt, beta=0, target=null){
const sz=this.sizes, L=this.L, ad=(this.mode==='adaptive');
const rho=ad?sg:fhnRho, rhop=ad?sgp:fhnRhoP;
const u=sz.map(n=>new Float32Array(n)); u[0].set(x); for(let l=1;lnew Float32Array(n)) : null;
for(let t=0;t1.2)nu=1.2; ul[i]=nu; }
}
}
}
return u;
}
outputs(x,iters,dt){ return this.relax(x,iters,dt,0,null)[this.L-1]; }
predict(x,iters,dt){ const o=this.outputs(x,iters,dt); let bi=0,bv=-1e9; for(let i=0;ibv){bv=o[i];bi=i;} return bi; }
accum(x,label,iters,nIters,dt,gW,gB){
const L=this.L, sz=this.sizes, ad=(this.mode==='adaptive'); const rho=ad?sg:fhnRho;
const tgt=new Float32Array(sz[L-1]); tgt[label]=1; const bN=this.betaN;
const uf=this.relax(x,iters,dt,0,null);
const o=uf[L-1]; let loss=0; for(let i=0;i0? this.MB[l][k]/bn : 0);
}
}
}
newGradBuffers(){ return [this.W.map(w=>new Float64Array(w.length)), this.b.map(b=>new Float64Array(b.length))]; }
}
// MNIST loader (browser side: fetches the JSON pack served from disk).
export async function loadMnist(url){
const j = await (await fetch(url)).json();
const dec = (b64)=>{ const bin=atob(b64); const u=new Uint8Array(bin.length); for(let i=0;i