// 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