everydaytok commited on
Commit
e76c9eb
·
verified ·
1 Parent(s): cff549e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +285 -452
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Elastic Mesh Lab</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
9
  <style>
@@ -30,8 +30,11 @@
30
  <span id="b-mode" class="px-1.5 py-0.5 rounded bg-yellow-900/60 text-yellow-300 border border-yellow-800/60">TRAIN</span>
31
  <span id="b-arch" class="px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-800/60">ADDIT</span>
32
  <span id="b-alpha" class="px-1.5 py-0.5 rounded bg-orange-900/60 text-orange-300 border border-orange-800/60">α:0.45</span>
 
 
 
33
 
34
- <!-- D · U · L controls -->
35
  <span class="px-1.5 py-0.5 rounded bg-green-900/60 text-green-300 border border-green-800/60 flex items-center gap-0.5">
36
  <span class="text-yellow-300 mr-0.5">D</span>
37
  <button class="lbtn" onclick="quickL('inputs',-1)">−</button>
@@ -47,18 +50,17 @@
47
  <button class="lbtn" onclick="quickL('lower',+1)">+</button>
48
  </span>
49
 
50
- <!-- CROSS CONNECT toggle -->
51
- <button id="b-cross" onclick="toggleCross()"
52
- class="px-1.5 py-0.5 rounded border font-bold text-[8px] transition-all
53
- bg-slate-900 text-slate-600 border-slate-700">
54
- CROSS:OFF
55
- </button>
56
-
57
- <span id="b-data" class="px-1.5 py-0.5 rounded bg-pink-900/60 text-pink-300 border border-pink-800/60">HOUSNG</span>
58
  </div>
 
59
  <div class="flex items-center gap-2 ml-1">
60
- <!-- bridge counter, hidden when 0 -->
61
- <span id="bridge-lbl" class="text-[7px] text-amber-500"></span>
62
  <span id="q-lbl" class="text-[8px] text-slate-600">Q:0</span>
63
  <div id="run-dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
64
  <button onclick="openDrawer()" class="text-[10px] bg-blue-700 hover:bg-blue-600 px-2 py-1 rounded font-bold">⚙ DIALS</button>
@@ -74,7 +76,7 @@
74
  <!-- ── BOTTOM PANEL ─────────────────────────────────────────────────────────── -->
75
  <div class="glass flex-shrink-0 border-t border-slate-800" style="height:174px">
76
  <div class="flex border-b border-slate-800 text-[10px]">
77
- <button onclick="tab('nodes')" id="tab-nodes" class="flex-1 py-1.5 bg-blue-900/40 text-blue-300 font-bold">NODES</button>
78
  <button onclick="tab('springs')" id="tab-springs" class="flex-1 py-1.5 text-slate-500">SPRINGS</button>
79
  <button onclick="tab('logs')" id="tab-logs" class="flex-1 py-1.5 text-slate-500">LOGS</button>
80
  </div>
@@ -82,7 +84,7 @@
82
  <div class="w-20 flex-shrink-0 border-r border-slate-800 flex flex-col items-center justify-center p-1 gap-0.5">
83
  <div class="text-[7px] text-slate-600 uppercase tracking-widest">Tension</div>
84
  <div id="err-big" class="text-xl font-bold text-red-400 leading-none">0.00</div>
85
- <div id="pred-val" class="text-[8px] text-slate-500">P:—</div>
86
  <div id="iter-lbl" class="text-[8px] text-slate-700">IT:0</div>
87
  </div>
88
  <div class="flex-grow overflow-y-auto p-1.5 text-[10px]">
@@ -96,7 +98,7 @@
96
  <!-- ── DRAWER ───────────────────────────────────────────────────────────────── -->
97
  <aside id="drawer" class="drawer-closed fixed inset-x-0 bottom-0 glass border-t border-slate-700 p-4 flex flex-col gap-3 max-h-[93vh] overflow-y-auto">
98
  <div class="flex justify-between items-center">
99
- <span class="text-orange-400 font-bold text-sm">ELASTIC MESH LABORATORY</span>
100
  <button onclick="closeDrawer()" class="text-slate-400 text-2xl leading-none">✕</button>
101
  </div>
102
 
@@ -104,10 +106,7 @@
104
 
105
  <!-- MODE -->
106
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-yellow-900/50">
107
- <div class="text-yellow-400 text-[9px] font-bold mb-1">
108
- EXECUTION MODE
109
- <span class="text-slate-600 font-normal ml-1">switches instantly, springs kept</span>
110
- </div>
111
  <div class="flex gap-2">
112
  <button class="tog on flex-1 py-2 rounded text-xs font-bold bg-yellow-700"
113
  onclick="setMode('training',this,'bg-yellow-700')">TRAINING</button>
@@ -116,7 +115,47 @@
116
  </div>
117
  </div>
118
 
119
- <!-- ARCH -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  <div class="bg-slate-900 rounded p-3 border border-blue-900/50">
121
  <div class="text-blue-400 text-[9px] font-bold mb-2">NODE ACTIVATION</div>
122
  <button class="tog on w-full mb-1 py-2 rounded text-[10px] font-bold bg-blue-700"
@@ -125,7 +164,6 @@
125
  onclick="pick('architecture','multiplicative',this,'bg-blue-700')">MULTIPLICATIVE Π</button>
126
  </div>
127
 
128
- <!-- ALPHA -->
129
  <div class="bg-slate-900 rounded p-3 border border-orange-900/50">
130
  <div class="text-orange-400 text-[9px] font-bold mb-1">BACK-TENSION α</div>
131
  <input id="alpha-sl" type="range" min="0" max="100" value="45" step="5"
@@ -134,61 +172,24 @@
134
  <div class="text-center text-orange-300 font-bold text-xl mt-0.5" id="alpha-val">0.45</div>
135
  </div>
136
 
137
- <!-- CROSS CONNECT -->
138
- <div class="col-span-2 bg-slate-900 rounded p-3 border border-amber-900/50">
139
- <div class="text-amber-400 text-[9px] font-bold mb-1">
140
- CROSS-CONNECT
141
- <span class="text-slate-600 font-normal ml-1">passive bridge vertices</span>
142
- </div>
143
- <div class="text-[9px] text-slate-500 mb-2">
144
- OFF N independent parallel hourglasses (default)<br>
145
- ON → a passive bridge vertex is inserted between each adjacent<br>
146
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;dimension pair (upper &amp; lower). Bridge springs are fixed<br>
147
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;and never learned — cross-talk happens through physics only.
148
- </div>
149
- <button id="drawer-cross-btn" onclick="toggleCross()"
150
- class="w-full py-2 rounded text-xs font-bold border border-slate-700 bg-slate-800 text-slate-400">
151
- CROSS-CONNECT: OFF
152
- </button>
153
- <div id="cross-info" class="text-[8px] text-slate-600 mt-1 text-center"></div>
154
- </div>
155
-
156
- <!-- TOPOLOGY -->
157
- <div class="col-span-2 bg-slate-900 rounded p-3 border border-green-900/50">
158
- <div class="text-green-400 text-[9px] font-bold mb-2">
159
- TOPOLOGY
160
- <span class="text-slate-500 font-normal ml-1">A[D]→[D×U]→C[D]←[D×L]←B[D]</span>
161
- </div>
162
- <div class="grid grid-cols-3 gap-2">
163
- <div class="bg-slate-800 rounded p-2 text-center border border-yellow-900/40">
164
- <div class="text-yellow-400 text-[8px] mb-1">DIMENSIONS (D)</div>
165
- <div class="flex items-center justify-center gap-2">
166
- <button class="lbtn text-sm" onclick="chL('inputs',-1)">−</button>
167
- <span id="dial-inputs" class="text-yellow-300 font-bold text-2xl w-8 text-center">1</span>
168
- <button class="lbtn text-sm" onclick="chL('inputs',+1)">+</button>
169
- </div>
170
- <div class="text-[7px] text-slate-600 mt-1">max 8</div>
171
- </div>
172
- <div class="bg-slate-800 rounded p-2 text-center border border-orange-900/40">
173
- <div class="text-orange-400 text-[8px] mb-1">UPPER (A-side)</div>
174
- <div class="flex items-center justify-center gap-2">
175
- <button class="lbtn text-sm" onclick="chL('upper',-1)">−</button>
176
- <span id="dial-upper" class="text-orange-300 font-bold text-2xl w-8 text-center">3</span>
177
- <button class="lbtn text-sm" onclick="chL('upper',+1)">+</button>
178
- </div>
179
- <div class="text-[7px] text-slate-600 mt-1">per dim</div>
180
  </div>
181
- <div class="bg-slate-800 rounded p-2 text-center border border-cyan-900/40">
182
- <div class="text-cyan-400 text-[8px] mb-1">LOWER (B-side)</div>
183
- <div class="flex items-center justify-center gap-2">
184
- <button class="lbtn text-sm" onclick="chL('lower',-1)">−</button>
185
- <span id="dial-lower" class="text-cyan-300 font-bold text-2xl w-8 text-center">3</span>
186
- <button class="lbtn text-sm" onclick="chL('lower',+1)">+</button>
187
- </div>
188
- <div class="text-[7px] text-slate-600 mt-1">per dim</div>
189
  </div>
190
  </div>
191
- <div id="spring-count" class="text-center text-[8px] text-slate-600 mt-2"></div>
192
  </div>
193
  </div>
194
 
@@ -197,129 +198,57 @@
197
  APPLY &amp; REBUILD TOPOLOGY
198
  </button>
199
 
200
- <!-- DATASET -->
201
- <div class="bg-slate-900 rounded p-3 border border-pink-900/50">
202
- <div class="text-pink-400 text-[9px] font-bold mb-2">DATASET
203
- <span class="text-slate-600 font-normal ml-1">(applied per dimension)</span>
204
- </div>
205
- <select id="ds-sel" onchange="refreshDS()"
206
- class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded mb-2">
207
- <option value="housing">Housing 2.5 + B×1.2</option>
208
- <option value="subtraction">Subtraction AB</option>
209
- <option value="multiplication">Multiplication — A × B</option>
210
- <option value="quadratic">Quadratic — A² + B</option>
211
- </select>
212
- <p id="ds-hint" class="text-[9px] text-slate-500 italic mb-2"></p>
213
- <div class="flex gap-2">
214
- <input id="batch-n" type="number" value="30" min="5" max="200"
215
- class="w-16 bg-black border border-slate-700 p-2 text-white text-sm text-center rounded">
216
- <button onclick="startBatch()"
217
- class="flex-1 bg-pink-800 hover:bg-pink-700 py-2 text-xs font-bold rounded">START BATCH</button>
218
  </div>
219
- </div>
220
 
221
- <!-- CUSTOM -->
222
- <div class="bg-slate-900 rounded p-3 border border-cyan-900/50">
223
- <div class="text-cyan-400 text-[9px] font-bold mb-1">CUSTOM INPUT
224
- <span id="custom-dim-hint" class="text-slate-600 font-normal ml-1"></span>
225
- </div>
226
- <div id="ds-examples" class="text-[9px] text-slate-500 mb-2"></div>
227
- <div class="grid grid-cols-3 gap-2 mb-2">
228
- <div>
229
- <div class="text-[8px] text-slate-600 mb-1">A (top)</div>
230
- <input id="ca" type="text" value="5"
231
- class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded"
232
- oninput="updateExpected()">
233
- </div>
234
- <div>
235
- <div class="text-[8px] text-slate-600 mb-1">B (bottom)</div>
236
- <input id="cb" type="text" value="3"
237
- class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded"
238
- oninput="updateExpected()">
239
- </div>
240
- <div>
241
- <div class="text-[8px] text-slate-600 mb-1">C target</div>
242
- <input id="cc" type="text" placeholder="auto"
243
- class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded">
244
- </div>
245
  </div>
246
- <div id="expected-lbl" class="text-[9px] text-yellow-400 mb-2 min-h-[14px]"></div>
247
- <button onclick="runCustom()"
248
- class="w-full bg-cyan-800 hover:bg-cyan-700 py-2 text-xs font-bold rounded">RUN CUSTOM</button>
249
  </div>
250
 
251
- <button onclick="halt()"
252
- class="w-full border border-red-700 text-red-500 py-2 rounded text-xs font-bold">HALT ENGINE</button>
253
  </aside>
254
 
255
  <script>
256
  // ── STATE ──────────────────────────────────────────────────────────────────────
257
  const cfg = { mode: 'training', architecture: 'additive' };
258
- const topo = { inputs: 1, upper: 3, lower: 3 };
259
- let crossConnect = false;
260
- let nBridges = 0;
261
- let bridgeK = 0.20;
262
-
263
- // ── CROSS CONNECT ─────────────────────────────────────────────────────────────
264
- async function toggleCross() {
265
- const res = await fetch('/toggle_cross', { method: 'POST' });
266
- const data = await res.json();
267
- crossConnect = data.cross_connect;
268
- nBridges = data.n_bridges || 0;
269
- bridgeK = data.bridge_k || 0.20;
270
- updateCrossUI();
271
- meshPlotted = false;
272
- }
273
-
274
- function updateCrossUI() {
275
- const btn1 = document.getElementById('b-cross');
276
- const btn2 = document.getElementById('drawer-cross-btn');
277
- const info = document.getElementById('cross-info');
278
- const blbl = document.getElementById('bridge-lbl');
279
- const n = topo.inputs;
280
-
281
- // Estimated bridge count when server hasn't confirmed yet
282
- const nb = nBridges || (crossConnect && n >= 2 ? (n - 1) * 2 : 0);
283
-
284
- if (crossConnect) {
285
- btn1.className = 'px-1.5 py-0.5 rounded border font-bold text-[8px] transition-all bg-amber-800 text-amber-200 border-amber-600';
286
- btn1.innerText = 'CROSS:ON';
287
- btn2.className = 'w-full py-2 rounded text-xs font-bold border border-amber-600 bg-amber-900/60 text-amber-200';
288
- btn2.innerText = 'CROSS-CONNECT: ON (click to disable)';
289
- info.innerText = nb
290
- ? `${nb} bridge ${nb !== 1 ? 'vertices' : 'vertex'} — passive k=${bridgeK}, not learned`
291
- : 'No bridges (need D≥2)';
292
- blbl.innerText = nb ? `⬡${nb}` : '';
293
- blbl.className = 'text-[7px] text-amber-500';
294
  } else {
295
- btn1.className = 'px-1.5 py-0.5 rounded border font-bold text-[8px] transition-all bg-slate-900 text-slate-600 border-slate-700';
296
- btn1.innerText = 'CROSS:OFF';
297
- btn2.className = 'w-full py-2 rounded text-xs font-bold border border-slate-700 bg-slate-800 text-slate-400';
298
- btn2.innerText = 'CROSS-CONNECT: OFF (click to enable)';
299
- info.innerText = `${n} independent parallel hourglass${n !== 1 ? 'es' : ''}`;
300
- blbl.innerText = '';
301
  }
302
  }
303
 
304
- // ── TOPOLOGY ──────────────────────────────────────────────────────────────────
305
- function updateSpringCount() {
306
- const { inputs: n, upper: u, lower: l } = topo;
307
- const nb = crossConnect && n >= 2 ? (n - 1) * 2 : 0; // XU + XL per pair
308
- const learnSprings = n * (2*u + 2*l);
309
- const bridgeSprings = nb * 2; // each bridge has 2 springs
310
- const totalNodes = 3*n + n*u + n*l + nb;
311
- document.getElementById('spring-count').innerText =
312
- `${learnSprings} learned + ${bridgeSprings} bridge springs | ${totalNodes} nodes`;
313
- document.getElementById('custom-dim-hint').innerText =
314
- n === 1 ? '(single value)' : `(${n} values, comma-separated)`;
315
- updateCrossUI();
316
- }
317
-
318
  function chL(w, d) {
319
- const lim = { inputs:[1,8], upper:[1,16], lower:[1,16] };
320
  topo[w] = Math.max(lim[w][0], Math.min(lim[w][1], topo[w]+d));
321
  document.getElementById(`dial-${w}`).innerText = topo[w];
322
- updateSpringCount();
323
  }
324
 
325
  async function quickL(w, d) {
@@ -328,19 +257,21 @@ async function quickL(w, d) {
328
  body: JSON.stringify({ layer: w, delta: d })
329
  });
330
  const data = await res.json();
331
- syncTopoUI(data.n_inputs, data.n_upper, data.n_lower);
332
  meshPlotted = false;
333
  }
334
 
335
- function syncTopoUI(ni, nu, nl) {
336
- topo.inputs = ni; topo.upper = nu; topo.lower = nl;
337
- document.getElementById('b-inputs').innerText = ni;
338
- document.getElementById('b-upper').innerText = nu;
339
- document.getElementById('b-lower').innerText = nl;
340
- document.getElementById('dial-inputs').innerText = ni;
341
- document.getElementById('dial-upper').innerText = nu;
342
- document.getElementById('dial-lower').innerText = nl;
343
- updateSpringCount();
 
 
344
  }
345
 
346
  async function setMode(m, btn, cls) {
@@ -364,21 +295,28 @@ function pick(key, val, btn, cls) {
364
  btn.classList.remove('off'); btn.classList.add('on', cls);
365
  }
366
 
367
- function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); refreshDS(); }
368
  function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
369
 
370
  async function applyConfig() {
371
  const ds = document.getElementById('ds-sel').value;
 
 
 
372
  const alpha = parseFloat(document.getElementById('alpha-sl').value) / 100;
 
373
  await fetch('/config', {
374
  method:'POST', headers:{'Content-Type':'application/json'},
375
  body: JSON.stringify({
376
- ...cfg, dataset: ds, back_alpha: alpha,
377
- n_inputs: topo.inputs, n_upper: topo.upper, n_lower: topo.lower
 
 
378
  })
379
  });
380
  document.getElementById('b-arch').innerText = cfg.architecture.slice(0,5).toUpperCase();
381
  document.getElementById('b-alpha').innerText = `α:${alpha.toFixed(2)}`;
 
382
  document.getElementById('b-data').innerText = ds.slice(0,6).toUpperCase();
383
  meshPlotted = false;
384
  closeDrawer();
@@ -393,12 +331,13 @@ async function startBatch() {
393
  }
394
 
395
  async function runCustom() {
396
- const a = document.getElementById('ca').value;
397
- const b = document.getElementById('cb').value;
398
- const c = document.getElementById('cc').value;
399
  await fetch('/run_custom', {
400
  method:'POST', headers:{'Content-Type':'application/json'},
401
- body: JSON.stringify({ a, b, c: c !== '' ? c : null })
 
 
 
 
402
  });
403
  closeDrawer();
404
  }
@@ -408,52 +347,6 @@ async function halt() {
408
  closeDrawer();
409
  }
410
 
411
- // ── DATASET META ──────────────────────────────────────────────────────────────
412
- const DS = {
413
- housing: { hint:'A×2.5+B×1.2 per dim', fn:(a,b)=>(a*2.5+b*1.2).toFixed(3), ex:[{a:4,b:2},{a:6,b:3}] },
414
- subtraction: { hint:'A−B per dim', fn:(a,b)=>(a-b).toFixed(3), ex:[{a:8,b:3},{a:5,b:5}] },
415
- multiplication:{ hint:'A×B per dim', fn:(a,b)=>(a*b).toFixed(3), ex:[{a:6,b:7},{a:3,b:9}] },
416
- quadratic: { hint:'A²+B per dim', fn:(a,b)=>(a*a+b).toFixed(3), ex:[{a:3,b:2},{a:4,b:5}] },
417
- };
418
-
419
- function refreshDS() {
420
- const ds = document.getElementById('ds-sel').value, m = DS[ds];
421
- document.getElementById('ds-hint').innerText = m.hint;
422
- document.getElementById('ds-examples').innerHTML = 'e.g. ' +
423
- m.ex.map(e => `<span class="cursor-pointer text-cyan-500 underline"
424
- onclick="fillC(${e.a},${e.b})">A=${e.a} B=${e.b}→${m.fn(e.a,e.b)}</span>`).join(' ');
425
- updateExpected();
426
- }
427
-
428
- function fillC(a, b) {
429
- document.getElementById('ca').value = a;
430
- document.getElementById('cb').value = b;
431
- document.getElementById('cc').value = '';
432
- updateExpected();
433
- }
434
-
435
- function parseVals(str) {
436
- return String(str).split(',').map(s => parseFloat(s.trim())).filter(x => !isNaN(x));
437
- }
438
-
439
- function updateExpected() {
440
- const a = document.getElementById('ca').value;
441
- const b = document.getElementById('cb').value;
442
- const ds = document.getElementById('ds-sel').value;
443
- if (!a || !b || !DS[ds]) return;
444
- const fn = DS[ds].fn;
445
- const av = parseVals(a), bv = parseVals(b);
446
- if (!av.length || !bv.length) return;
447
- const n = Math.max(topo.inputs, 1);
448
- const results = Array.from({length:n}, (_,i) => fn(av[i%av.length], bv[i%bv.length]));
449
- document.getElementById('expected-lbl').innerText =
450
- n === 1 ? `Ground truth: ${results[0]}` : `Ground truth: [${results.join(', ')}]`;
451
- }
452
-
453
- refreshDS();
454
- updateSpringCount();
455
-
456
- // ── TABS ──────────────────────────────────────────────────────────────────────
457
  function tab(name) {
458
  ['nodes','springs','logs'].forEach(t => {
459
  document.getElementById(`pane-${t}`).classList.toggle('hidden', t !== name);
@@ -463,71 +356,9 @@ function tab(name) {
463
  });
464
  }
465
 
466
- // ── VISUALIZATION ─────────────────────────────────────────────────────────────
467
-
468
- /**
469
- * Build x,y positions for every node.
470
- * Bridge nodes (XU{d}, XL{d}) sit at the midpoint between their two
471
- * adjacent boundary hidden nodes, on the same Y row.
472
- */
473
- function buildPos(layers, n_inputs, n_upper, n_lower, bridgeNodeIds = []) {
474
- const pos = {};
475
- const Y = [4.4, 2.1, 0.0, -2.1, -4.4];
476
- const COL_W = n_inputs === 1 ? 0 : Math.min(9.0 / (n_inputs - 1), 4.2);
477
- const halfSp = COL_W * (n_inputs - 1) / 2;
478
- const bSprd = n_inputs === 1 ? 3.8 : Math.min(COL_W * 0.55, 1.8);
479
-
480
- layers.forEach((layer, li) => {
481
- const y = Y[li];
482
- layer.forEach(nid => {
483
- const kind = nid[0];
484
- let dim = 1, j = 1, total = 1;
485
- if ('ABC'.includes(kind)) {
486
- dim = parseInt(nid.slice(1));
487
- } else {
488
- const parts = nid.slice(1).split('_');
489
- dim = parseInt(parts[0]);
490
- j = parseInt(parts[1]);
491
- total = kind === 'U' ? n_upper : n_lower;
492
- }
493
- const cx = n_inputs === 1 ? 0 : -halfSp + (dim - 1) * COL_W;
494
- if ('ABC'.includes(kind)) {
495
- pos[nid] = [cx, y];
496
- } else {
497
- const t = total === 1 ? 0 : (2*(j-1)/(total-1) - 1);
498
- pos[nid] = [cx + bSprd * t, y];
499
- }
500
- });
501
- });
502
-
503
- // Bridge nodes: midpoint between right boundary of dim d and left of dim d+1
504
- // x = -halfSp + (d - 0.5) * COL_W (simplifies to the column midpoint)
505
- bridgeNodeIds.forEach(nid => {
506
- const side = nid[1]; // 'U' or 'L'
507
- const d = parseInt(nid.slice(2));
508
- const yVal = side === 'U' ? Y[1] : Y[3]; // same row as upper/lower hidden
509
- const xVal = -halfSp + (d - 0.5) * COL_W;
510
- pos[nid] = [xVal, yVal];
511
- });
512
 
513
- return pos;
514
- }
515
-
516
- function busShapes(pos, n_inputs) {
517
- const sh = [];
518
- const mg = n_inputs === 1 ? 1.4 : 0.8;
519
- const xs = k => Object.entries(pos).filter(([id]) => id[0] === k).map(([,v]) => v[0]);
520
- const rect = (mn, mx, yc, hh, fill, stroke) => sh.push({
521
- type:'rect', xref:'x', yref:'y',
522
- x0:mn-mg, x1:mx+mg, y0:yc-hh, y1:yc+hh,
523
- fillcolor:fill, line:{color:stroke, width:2}
524
- });
525
- const aXs=xs('A'), cXs=xs('C'), bXs=xs('B');
526
- rect(Math.min(...aXs),Math.max(...aXs), 4.4, 0.35,'rgba(251,146,60,0.10)','rgba(251,146,60,0.65)');
527
- rect(Math.min(...cXs),Math.max(...cXs), 0.0, 0.32,'rgba(56,189,248,0.10)','rgba(56,189,248,0.70)');
528
- rect(Math.min(...bXs),Math.max(...bXs),-4.4, 0.35,'rgba(192,132,252,0.10)','rgba(192,132,252,0.65)');
529
- return sh;
530
- }
531
 
532
  function springColor(k) {
533
  const t = Math.min(Math.abs(k) / 6, 1);
@@ -536,99 +367,123 @@ function springColor(k) {
536
  }
537
 
538
  /**
539
- * Build all Plotly traces.
540
- * Learnable springs: solid colour based on spring constant.
541
- * Bridge springs: dashed amber lines.
542
- * Bridge nodes: amber diamonds, distinct from regular hidden nodes.
543
  */
544
- function buildTraces(nodes, springs, bridgeSprings, layers, n_inputs, n_upper, n_lower) {
545
- const bridgeNodeIds = Object.keys(nodes).filter(id => id[0] === 'X');
546
- const pos = buildPos(layers, n_inputs, n_upper, n_lower, bridgeNodeIds);
547
  const traces = [];
548
-
549
- // 1. Learnable spring edges
550
- for (const [key, k] of Object.entries(springs)) {
551
- const [u, v] = key.split('→');
552
- if (!pos[u] || !pos[v]) continue;
553
- const [col, wd] = springColor(k);
554
- traces.push({
555
- type:'scatter', mode:'lines',
556
- x:[pos[u][0], pos[v][0]], y:[pos[u][1], pos[v][1]],
557
- line:{color:col, width:wd},
558
- hoverinfo:'none', showlegend:false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  });
560
- }
561
 
562
- // 2. Bridge spring edges — amber dashed, drawn on top
563
- for (const key of Object.keys(bridgeSprings)) {
564
- const [u, v] = key.split('→');
565
- if (!pos[u] || !pos[v]) continue;
566
- traces.push({
567
- type:'scatter', mode:'lines',
568
- x:[pos[u][0], pos[v][0]], y:[pos[u][1], pos[v][1]],
569
- line:{color:'#f59e0b', width:2.0, dash:'dot'},
570
- hoverinfo:'none', showlegend:false
571
- });
572
- }
 
 
 
 
 
573
 
574
- // 3. All nodes (regular + bridge)
575
- const regularNodes = layers.flat();
576
- const allN = [...regularNodes, ...bridgeNodeIds];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
 
578
- const NCOL = id => {
579
- const k = id[0];
580
- return k==='A'?'#fb923c': k==='B'?'#c084fc': k==='C'?'#38bdf8':
581
- k==='X'?'#f59e0b': k==='U'?'#4ade80': '#67e8f9';
582
- };
583
- const isIO = id => 'ABC'.includes(id[0]);
584
- const isBridge = id => id[0] === 'X';
 
 
 
 
 
 
 
585
 
 
 
586
  traces.push({
587
  type:'scatter', mode:'markers+text',
588
- x: allN.map(id => pos[id]?.[0] ?? 0),
589
- y: allN.map(id => pos[id]?.[1] ?? 0),
590
- text: allN.map(id => {
591
- if (!isIO(id)) return '';
592
- return `${id}\n${Number(nodes[id]?.x ?? 0).toFixed(2)}`;
593
- }),
594
- textposition: allN.map(id => id[0]==='B' ? 'bottom center' : 'top center'),
595
- textfont:{ size:9, color: allN.map(id => NCOL(id)) },
596
  marker:{
597
- size: allN.map(id => {
598
- const v = Math.abs(nodes[id]?.vel ?? 0);
599
- const base = isIO(id) ? 18 : isBridge(id) ? 13 : 10;
600
- return base + Math.min(v*30, 8);
601
- }),
602
- symbol: allN.map(id => isBridge(id) ? 'diamond' : 'circle'),
603
- color: allN.map(id => NCOL(id)),
604
- opacity: allN.map(id => 0.75 + Math.min(Math.abs(nodes[id]?.vel??0)*1.8, 0.25)),
605
- line:{
606
- width: allN.map(id => isBridge(id) ? 3.0 : 2.5),
607
- color: allN.map(id =>
608
- isBridge(id) ? '#d97706'
609
- : nodes[id]?.anchored ? '#ef4444'
610
- : '#22c55e'
611
- )
612
- }
613
  },
614
- hoverinfo:'none', showlegend:false
615
  });
616
 
617
- return traces;
618
- }
619
-
620
- function meshLayout(nodes, layers, n_inputs, n_upper, n_lower) {
621
- const bridgeNodeIds = Object.keys(nodes).filter(id => id[0] === 'X');
622
- const pos = buildPos(layers, n_inputs, n_upper, n_lower, bridgeNodeIds);
623
- const xMax = Math.max(5.5, n_inputs * 2.8);
624
- return {
625
- margin:{l:8,r:8,t:8,b:8},
626
- paper_bgcolor:'transparent', plot_bgcolor:'transparent',
627
- xaxis:{visible:false, range:[-xMax, xMax]},
628
- yaxis:{visible:false, range:[-5.5, 5.5]},
629
- showlegend:false,
630
- shapes: busShapes(pos, n_inputs),
631
- };
632
  }
633
 
634
  const ERR_LAYOUT = {
@@ -647,86 +502,65 @@ setInterval(async () => {
647
  const r = await fetch('/state');
648
  const d = await r.json();
649
 
650
- syncTopoUI(d.n_inputs, d.n_upper, d.n_lower);
651
- crossConnect = d.cross_connect;
652
- nBridges = d.n_bridges || 0;
653
- bridgeK = d.bridge_k || 0.20;
654
- updateCrossUI();
655
- document.getElementById('b-alpha').innerText = `α:${d.back_alpha.toFixed(2)}`;
656
- document.getElementById('b-data').innerText = (d.dataset_type||'').slice(0,6).toUpperCase();
657
 
658
  const mBadge = document.getElementById('b-mode');
659
- mBadge.innerText = d.mode === 'training' ? 'TRAIN' : 'INFER';
660
- mBadge.className = d.mode === 'training'
661
  ? 'px-1.5 py-0.5 rounded bg-yellow-900/60 text-yellow-300 border border-yellow-800/60 text-[8px] font-bold'
662
  : 'px-1.5 py-0.5 rounded bg-green-900/60 text-green-300 border border-green-800/60 text-[8px] font-bold';
663
 
664
- document.getElementById('run-dot').className =
665
- `w-2 h-2 rounded-full ${d.running ? 'bg-green-400' : 'bg-slate-700'}`;
 
 
 
 
 
 
666
  document.getElementById('q-lbl').innerText = `Q:${d.queue_size}`;
667
  document.getElementById('iter-lbl').innerText = `IT:${d.iter}`;
668
 
669
- const e = Math.abs(d.error);
670
  const col = e < 0.02 ? 'text-green-400' : e < 2 ? 'text-yellow-400' : 'text-red-400';
671
  document.getElementById('err-big').className = `text-xl font-bold ${col} leading-none`;
672
- document.getElementById('err-big').innerText = d.error.toFixed(4);
673
-
674
- const preds = d.predictions || [d.prediction];
675
- document.getElementById('pred-val').innerText =
676
- preds.length === 1 ? `P:${preds[0].toFixed(3)}`
677
- : `P:[${preds.map(v=>v.toFixed(2)).join(',')}]`;
678
 
679
- // Nodes pane
680
- const order = d.layers.flat();
681
  let nh = '';
682
- order.forEach(id => {
683
- const n = d.nodes[id]; if (!n || !'ABC'.includes(id[0])) return;
684
- const icon = n.anchored ? '<span class="text-red-400">⊠</span>' : '<span class="text-green-500">◎</span>';
685
- const COLS = {A:'#fb923c',B:'#c084fc',C:'#38bdf8'};
686
- nh += `<div class="flex justify-between items-center py-0.5 border-b border-slate-900">
687
- ${icon}<span class="ml-1 font-bold" style="color:${COLS[id[0]]}">${id}</span>
688
- <span class="text-white font-bold">${Number(n.x).toFixed(4)}</span>
689
- <span class="text-slate-700 text-[8px]">v:${(n.vel||0).toFixed(3)}</span>
690
- </div>`;
691
- });
692
- // Bridge nodes summary
693
- const bridgeIds = Object.keys(d.nodes).filter(id => id[0] === 'X');
694
- if (bridgeIds.length) {
695
- nh += `<div class="text-[8px] text-amber-600 py-0.5 border-b border-slate-900">
696
- ⬡ ${bridgeIds.length} bridge nodes — passive k=${bridgeK}</div>`;
697
- bridgeIds.forEach(id => {
698
- const n = d.nodes[id];
699
  nh += `<div class="flex justify-between items-center py-0.5 border-b border-slate-900/50">
700
- <span class="text-amber-500 text-[9px]">${id}</span>
701
- <span class="text-amber-300 font-bold">${Number(n.x).toFixed(4)}</span>
702
- <span class="text-slate-700 text-[8px]">v:${(n.vel||0).toFixed(3)}</span>
703
  </div>`;
704
  });
705
- }
706
- const hids = order.filter(id => 'UL'.includes(id[0]));
707
- if (hids.length) {
708
- const avgV = hids.reduce((s,id) => s + Math.abs(d.nodes[id]?.vel||0), 0) / hids.length;
709
- nh += `<div class="text-[8px] text-slate-600 py-0.5">${hids.length} hidden — avg|vel|=${avgV.toFixed(3)}</div>`;
710
- }
711
  document.getElementById('pane-nodes').innerHTML = nh;
712
 
713
- // Springs pane — learnable first, then bridge springs in amber
714
  let sh = '';
715
- for (const [key, k] of Object.entries(d.springs)) {
716
- const kc = k < 0 ? 'text-blue-300' : k > 4 ? 'text-yellow-200' : 'text-purple-300';
717
- sh += `<div class="flex justify-between py-0.5 border-b border-slate-900">
718
- <span class="text-slate-500 text-[9px]">${key}</span>
719
- <span class="${kc} font-bold text-[10px]">${k.toFixed(4)}</span></div>`;
720
- }
721
- const bs = d.bridge_springs || {};
722
- if (Object.keys(bs).length) {
723
- sh += `<div class="text-[8px] text-amber-600 py-0.5 border-b border-slate-800 mt-1">⬡ BRIDGE (passive, fixed)</div>`;
724
- for (const [key, k] of Object.entries(bs)) {
725
- sh += `<div class="flex justify-between py-0.5 border-b border-slate-900/50">
726
- <span class="text-amber-700 text-[9px]">${key}</span>
727
- <span class="text-amber-400 font-bold text-[10px]">${k.toFixed(4)}</span></div>`;
728
- }
729
- }
730
  document.getElementById('pane-springs').innerHTML = sh;
731
 
732
  // Logs
@@ -734,10 +568,9 @@ setInterval(async () => {
734
  d.logs.map(l => `<div class="py-0.5 border-b border-slate-900/50">${l}</div>`).join('');
735
 
736
  // Mesh plot
737
- const layerKey = JSON.stringify(d.layers) + d.cross_connect + d.n_bridges;
738
- const bridgeSprings = d.bridge_springs || {};
739
- const traces = buildTraces(d.nodes, d.springs, bridgeSprings, d.layers, d.n_inputs, d.n_upper, d.n_lower);
740
- const layout = meshLayout(d.nodes, d.layers, d.n_inputs, d.n_upper, d.n_lower);
741
  if (!meshPlotted || layerKey !== lastLayerKey) {
742
  Plotly.newPlot('mesh-plot', traces, layout, {displayModeBar:false, responsive:true});
743
  meshPlotted = true; lastLayerKey = layerKey;
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Stacked Elastic Mesh Lab</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
9
  <style>
 
30
  <span id="b-mode" class="px-1.5 py-0.5 rounded bg-yellow-900/60 text-yellow-300 border border-yellow-800/60">TRAIN</span>
31
  <span id="b-arch" class="px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-800/60">ADDIT</span>
32
  <span id="b-alpha" class="px-1.5 py-0.5 rounded bg-orange-900/60 text-orange-300 border border-orange-800/60">α:0.45</span>
33
+ <span id="b-stack" class="px-1.5 py-0.5 rounded bg-emerald-900/60 text-emerald-300 border border-emerald-800/60">STACK:0</span>
34
+ <span id="b-cred" class="px-1.5 py-0.5 rounded bg-fuchsia-900/60 text-fuchsia-300 border border-fuchsia-800/60">CREDIT</span>
35
+ <span id="b-data" class="px-1.5 py-0.5 rounded bg-pink-900/60 text-pink-300 border border-pink-800/60">HOUSNG</span>
36
 
37
+ <!-- D · U · L quick buttons -->
38
  <span class="px-1.5 py-0.5 rounded bg-green-900/60 text-green-300 border border-green-800/60 flex items-center gap-0.5">
39
  <span class="text-yellow-300 mr-0.5">D</span>
40
  <button class="lbtn" onclick="quickL('inputs',-1)">−</button>
 
50
  <button class="lbtn" onclick="quickL('lower',+1)">+</button>
51
  </span>
52
 
53
+ <!-- STIFFNESS INDICATOR -->
54
+ <div class="flex items-center gap-1.5 px-2 border-l border-slate-700 ml-2" title="Realtime active LMS adjustments">
55
+ <span class="text-[7px] text-slate-500 uppercase tracking-wider">Learning</span>
56
+ <div class="w-16 bg-slate-900 rounded-sm h-2 overflow-hidden border border-slate-700">
57
+ <div id="stiff-bar" class="bg-amber-400 h-full transition-all duration-500 ease-out" style="width:0%"></div>
58
+ </div>
59
+ <span id="stiff-lbl" class="text-[8px] text-amber-500 w-5 text-right font-bold">0%</span>
60
+ </div>
61
  </div>
62
+
63
  <div class="flex items-center gap-2 ml-1">
 
 
64
  <span id="q-lbl" class="text-[8px] text-slate-600">Q:0</span>
65
  <div id="run-dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
66
  <button onclick="openDrawer()" class="text-[10px] bg-blue-700 hover:bg-blue-600 px-2 py-1 rounded font-bold">⚙ DIALS</button>
 
76
  <!-- ── BOTTOM PANEL ─────────────────────────────────────────────────────────── -->
77
  <div class="glass flex-shrink-0 border-t border-slate-800" style="height:174px">
78
  <div class="flex border-b border-slate-800 text-[10px]">
79
+ <button onclick="tab('nodes')" id="tab-nodes" class="flex-1 py-1.5 bg-blue-900/40 text-blue-300 font-bold">UNITS</button>
80
  <button onclick="tab('springs')" id="tab-springs" class="flex-1 py-1.5 text-slate-500">SPRINGS</button>
81
  <button onclick="tab('logs')" id="tab-logs" class="flex-1 py-1.5 text-slate-500">LOGS</button>
82
  </div>
 
84
  <div class="w-20 flex-shrink-0 border-r border-slate-800 flex flex-col items-center justify-center p-1 gap-0.5">
85
  <div class="text-[7px] text-slate-600 uppercase tracking-widest">Tension</div>
86
  <div id="err-big" class="text-xl font-bold text-red-400 leading-none">0.00</div>
87
+ <div id="pred-val" class="text-[8px] text-slate-500 text-center w-full truncate">P:—</div>
88
  <div id="iter-lbl" class="text-[8px] text-slate-700">IT:0</div>
89
  </div>
90
  <div class="flex-grow overflow-y-auto p-1.5 text-[10px]">
 
98
  <!-- ── DRAWER ───────────────────────────────────────────────────────────────── -->
99
  <aside id="drawer" class="drawer-closed fixed inset-x-0 bottom-0 glass border-t border-slate-700 p-4 flex flex-col gap-3 max-h-[93vh] overflow-y-auto">
100
  <div class="flex justify-between items-center">
101
+ <span class="text-emerald-400 font-bold text-sm">STACKED ELASTIC MESH (TRE)</span>
102
  <button onclick="closeDrawer()" class="text-slate-400 text-2xl leading-none">✕</button>
103
  </div>
104
 
 
106
 
107
  <!-- MODE -->
108
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-yellow-900/50">
109
+ <div class="text-yellow-400 text-[9px] font-bold mb-1">EXECUTION MODE</div>
 
 
 
110
  <div class="flex gap-2">
111
  <button class="tog on flex-1 py-2 rounded text-xs font-bold bg-yellow-700"
112
  onclick="setMode('training',this,'bg-yellow-700')">TRAINING</button>
 
115
  </div>
116
  </div>
117
 
118
+ <!-- STACK TOPOLOGY -->
119
+ <div class="col-span-2 bg-slate-900 rounded p-3 border border-emerald-900/50">
120
+ <div class="text-emerald-400 text-[9px] font-bold mb-2">STACKED ARCHITECTURE (Binary Tree)</div>
121
+ <div class="flex gap-2 items-center mb-2">
122
+ <select id="cfg-stack" onchange="updateTopoUI()" class="flex-1 bg-black border border-slate-700 p-2 text-white text-xs rounded">
123
+ <option value="0">Flat (Level 0 Only) - Independent HGs</option>
124
+ <option value="1">Stack Level 1 (2 inputs → 1 final HG)</option>
125
+ <option value="2">Stack Level 2 (4 inputs → 2 HGs → 1 final HG)</option>
126
+ <option value="3">Stack Level 3 (8 inputs → 4 HGs → 2 HGs → 1 final)</option>
127
+ <option value="4">Stack Level 4 (16 inputs → 8 → 4 → 2 → 1 final)</option>
128
+ </select>
129
+ <div class="bg-slate-800 p-2 rounded text-center border border-slate-700 w-24">
130
+ <div class="text-slate-400 text-[8px] mb-1">RAW DIMS</div>
131
+ <input id="cfg-inputs" type="number" value="1" min="1" max="16" class="w-full bg-black border border-slate-700 text-center text-white rounded text-sm p-0.5">
132
+ </div>
133
+ </div>
134
+ <div class="flex gap-4">
135
+ <label class="flex items-center gap-1.5 cursor-pointer">
136
+ <input type="checkbox" id="cfg-rev" class="accent-emerald-500 w-3 h-3">
137
+ <span class="text-[9px] text-slate-400">Reverse Mode (C → A/B Decoder)</span>
138
+ </label>
139
+ <label class="flex items-center gap-1.5 cursor-pointer">
140
+ <input type="checkbox" id="cfg-ind" class="accent-emerald-500 w-3 h-3">
141
+ <span class="text-[9px] text-slate-400">Train Leaves on local GT</span>
142
+ </label>
143
+ </div>
144
+ </div>
145
+
146
+ <!-- CREDIT ASSIGNMENT -->
147
+ <div class="col-span-2 bg-slate-900 rounded p-3 border border-fuchsia-900/50">
148
+ <div class="text-fuchsia-400 text-[9px] font-bold mb-2">CREDIT ASSIGNMENT (Training)</div>
149
+ <select id="cfg-cred" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded">
150
+ <option value="elastic_backprop">Elastic Backprop (Sensitivity Chain Rule)</option>
151
+ <option value="greedy">Greedy Depth Scaling</option>
152
+ <option value="param_hg">Parametric HG (Full Error Shared)</option>
153
+ <option value="independent">Independent Layer Training</option>
154
+ </select>
155
+ <div class="text-[8px] text-slate-500 mt-1 italic">Determines how final C error flows backwards to intermediate springs.</div>
156
+ </div>
157
+
158
+ <!-- ARCH & ALPHA -->
159
  <div class="bg-slate-900 rounded p-3 border border-blue-900/50">
160
  <div class="text-blue-400 text-[9px] font-bold mb-2">NODE ACTIVATION</div>
161
  <button class="tog on w-full mb-1 py-2 rounded text-[10px] font-bold bg-blue-700"
 
164
  onclick="pick('architecture','multiplicative',this,'bg-blue-700')">MULTIPLICATIVE Π</button>
165
  </div>
166
 
 
167
  <div class="bg-slate-900 rounded p-3 border border-orange-900/50">
168
  <div class="text-orange-400 text-[9px] font-bold mb-1">BACK-TENSION α</div>
169
  <input id="alpha-sl" type="range" min="0" max="100" value="45" step="5"
 
172
  <div class="text-center text-orange-300 font-bold text-xl mt-0.5" id="alpha-val">0.45</div>
173
  </div>
174
 
175
+ <!-- H-BULGES -->
176
+ <div class="col-span-2 bg-slate-900 rounded p-2 text-center border border-slate-700 grid grid-cols-2 gap-2">
177
+ <div class="bg-slate-800 rounded p-2 text-center border border-orange-900/40">
178
+ <div class="text-orange-400 text-[8px] mb-1">UPPER ROW (U)</div>
179
+ <div class="flex items-center justify-center gap-2">
180
+ <button class="lbtn text-sm" onclick="chL('upper',-1)">−</button>
181
+ <span id="dial-upper" class="text-orange-300 font-bold text-2xl w-8 text-center">3</span>
182
+ <button class="lbtn text-sm" onclick="chL('upper',+1)">+</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  </div>
184
+ </div>
185
+ <div class="bg-slate-800 rounded p-2 text-center border border-cyan-900/40">
186
+ <div class="text-cyan-400 text-[8px] mb-1">LOWER ROW (L)</div>
187
+ <div class="flex items-center justify-center gap-2">
188
+ <button class="lbtn text-sm" onclick="chL('lower',-1)"></button>
189
+ <span id="dial-lower" class="text-cyan-300 font-bold text-2xl w-8 text-center">3</span>
190
+ <button class="lbtn text-sm" onclick="chL('lower',+1)">+</button>
 
191
  </div>
192
  </div>
 
193
  </div>
194
  </div>
195
 
 
198
  APPLY &amp; REBUILD TOPOLOGY
199
  </button>
200
 
201
+ <!-- DATASET & CUSTOM -->
202
+ <div class="grid grid-cols-2 gap-2">
203
+ <div class="bg-slate-900 rounded p-3 border border-pink-900/50">
204
+ <div class="text-pink-400 text-[9px] font-bold mb-2">DATASET</div>
205
+ <select id="ds-sel" onchange="refreshDS()" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded mb-2">
206
+ <option value="housing">Housing (A×2.5+B×1.2)</option>
207
+ <option value="subtraction">Subtraction (A−B)</option>
208
+ <option value="multiplication">Multiplication (A×B)</option>
209
+ <option value="quadratic">Quadratic (A²+B)</option>
210
+ </select>
211
+ <div class="flex gap-2">
212
+ <input id="batch-n" type="number" value="30" class="w-16 bg-black border border-slate-700 p-2 text-white text-sm text-center rounded">
213
+ <button onclick="startBatch()" class="flex-1 bg-pink-800 hover:bg-pink-700 py-2 text-xs font-bold rounded">START BATCH</button>
214
+ </div>
 
 
 
 
215
  </div>
 
216
 
217
+ <div class="bg-slate-900 rounded p-3 border border-cyan-900/50">
218
+ <div class="text-cyan-400 text-[9px] font-bold mb-1">CUSTOM INPUT</div>
219
+ <div class="text-[8px] text-slate-500 mb-1">Comma-separated if D>1</div>
220
+ <input id="ca" type="text" value="5" placeholder="A vals" class="w-full bg-black border border-slate-700 p-1.5 text-white text-xs text-center rounded mb-1">
221
+ <input id="cb" type="text" value="3" placeholder="B vals" class="w-full bg-black border border-slate-700 p-1.5 text-white text-xs text-center rounded mb-1">
222
+ <input id="cc" type="text" placeholder="C target (auto)" class="w-full bg-black border border-slate-700 p-1.5 text-white text-xs text-center rounded mb-2">
223
+ <button onclick="runCustom()" class="w-full bg-cyan-800 hover:bg-cyan-700 py-2 text-xs font-bold rounded">RUN CUSTOM</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  </div>
 
 
 
225
  </div>
226
 
227
+ <button onclick="halt()" class="w-full border border-red-700 text-red-500 py-2 rounded text-xs font-bold">HALT ENGINE</button>
 
228
  </aside>
229
 
230
  <script>
231
  // ── STATE ──────────────────────────────────────────────────────────────────────
232
  const cfg = { mode: 'training', architecture: 'additive' };
233
+ const topo = { inputs: 1, upper: 3, lower: 3, stack: 0 };
234
+
235
+ function updateTopoUI() {
236
+ const stack = parseInt(document.getElementById('cfg-stack').value);
237
+ const dimInput = document.getElementById('cfg-inputs');
238
+ if (stack > 0) {
239
+ dimInput.value = Math.pow(2, stack);
240
+ dimInput.disabled = true;
241
+ dimInput.className = "w-full bg-slate-800 border border-slate-700 text-center text-slate-500 rounded text-sm p-0.5 cursor-not-allowed";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  } else {
243
+ dimInput.disabled = false;
244
+ dimInput.className = "w-full bg-black border border-slate-700 text-center text-white rounded text-sm p-0.5";
 
 
 
 
245
  }
246
  }
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  function chL(w, d) {
249
+ const lim = { upper:[1,16], lower:[1,16] };
250
  topo[w] = Math.max(lim[w][0], Math.min(lim[w][1], topo[w]+d));
251
  document.getElementById(`dial-${w}`).innerText = topo[w];
 
252
  }
253
 
254
  async function quickL(w, d) {
 
257
  body: JSON.stringify({ layer: w, delta: d })
258
  });
259
  const data = await res.json();
260
+ syncTopoUI(data);
261
  meshPlotted = false;
262
  }
263
 
264
+ function syncTopoUI(d) {
265
+ topo.inputs = d.n_inputs; topo.upper = d.n_upper; topo.lower = d.n_lower; topo.stack = d.stack_levels;
266
+ document.getElementById('b-inputs').innerText = d.n_inputs;
267
+ document.getElementById('b-upper').innerText = d.n_upper;
268
+ document.getElementById('b-lower').innerText = d.n_lower;
269
+ document.getElementById('dial-upper').innerText = d.n_upper;
270
+ document.getElementById('dial-lower').innerText = d.n_lower;
271
+ document.getElementById('cfg-inputs').value = d.n_inputs;
272
+ document.getElementById('cfg-stack').value = d.stack_levels;
273
+ document.getElementById('b-stack').innerText = `STACK:${d.stack_levels}`;
274
+ updateTopoUI();
275
  }
276
 
277
  async function setMode(m, btn, cls) {
 
295
  btn.classList.remove('off'); btn.classList.add('on', cls);
296
  }
297
 
298
+ function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); }
299
  function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
300
 
301
  async function applyConfig() {
302
  const ds = document.getElementById('ds-sel').value;
303
+ const cred = document.getElementById('cfg-cred').value;
304
+ const rev = document.getElementById('cfg-rev').checked;
305
+ const ind = document.getElementById('cfg-ind').checked;
306
  const alpha = parseFloat(document.getElementById('alpha-sl').value) / 100;
307
+
308
  await fetch('/config', {
309
  method:'POST', headers:{'Content-Type':'application/json'},
310
  body: JSON.stringify({
311
+ ...cfg, dataset: ds, back_alpha: alpha, credit_mode: cred, reverse_mode: rev, individual_train: ind,
312
+ n_inputs: parseInt(document.getElementById('cfg-inputs').value),
313
+ n_upper: topo.upper, n_lower: topo.lower,
314
+ stack_levels: parseInt(document.getElementById('cfg-stack').value)
315
  })
316
  });
317
  document.getElementById('b-arch').innerText = cfg.architecture.slice(0,5).toUpperCase();
318
  document.getElementById('b-alpha').innerText = `α:${alpha.toFixed(2)}`;
319
+ document.getElementById('b-cred').innerText = cred.replace('_','').slice(0,7).toUpperCase();
320
  document.getElementById('b-data').innerText = ds.slice(0,6).toUpperCase();
321
  meshPlotted = false;
322
  closeDrawer();
 
331
  }
332
 
333
  async function runCustom() {
 
 
 
334
  await fetch('/run_custom', {
335
  method:'POST', headers:{'Content-Type':'application/json'},
336
+ body: JSON.stringify({
337
+ a: document.getElementById('ca').value,
338
+ b: document.getElementById('cb').value,
339
+ c: document.getElementById('cc').value || null
340
+ })
341
  });
342
  closeDrawer();
343
  }
 
347
  closeDrawer();
348
  }
349
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  function tab(name) {
351
  ['nodes','springs','logs'].forEach(t => {
352
  document.getElementById(`pane-${t}`).classList.toggle('hidden', t !== name);
 
356
  });
357
  }
358
 
359
+ function refreshDS() {} // stub
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
+ // ── VISUALIZATION (Horizontal Hierarchy) ───────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
  function springColor(k) {
364
  const t = Math.min(Math.abs(k) / 6, 1);
 
367
  }
368
 
369
  /**
370
+ * Renders the stacked architecture side-to-side (Data flows Left -> Right).
371
+ * Each unit draws its A (top-left), B (bottom-left), U/L (middle), and C (right).
 
 
372
  */
373
+ function buildTraces(d) {
374
+ const pos = {};
 
375
  const traces = [];
376
+
377
+ // Layout spacing configurations
378
+ const W_SPREAD = 5.5; // X-distance between hierarchy levels
379
+ const H_SPREAD = 4.0; // Y-distance between adjacent units in same level
380
+ const HG_W = 2.5; // Internal width of a single Hourglass (A to C)
381
+ const HG_H = 1.5; // Internal height (A to B offset)
382
+
383
+ // 1. Calculate positions for every unit
384
+ d.topology.forEach((level, lv) => {
385
+ const baseX = lv * W_SPREAD;
386
+ const N = level.length;
387
+
388
+ level.forEach((uid, i) => {
389
+ // Center Y vertically
390
+ const baseY = (i - (N - 1) / 2) * -H_SPREAD;
391
+ const u = d.units[uid];
392
+
393
+ // Standard Hourglass Nodes >
394
+ pos[`${uid}_A`] = [baseX - (HG_W/2), baseY + HG_H];
395
+ pos[`${uid}_B`] = [baseX - (HG_W/2), baseY - HG_H];
396
+ pos[`${uid}_C`] = [baseX + (HG_W/2), baseY];
397
+
398
+ // Bulge / Hidden nodes
399
+ for(let j=1; j<=u.n_upper; j++) {
400
+ const ty = u.n_upper === 1 ? 0 : (j-1)/(u.n_upper-1) - 0.5;
401
+ pos[`${uid}_U${j}`] = [baseX, baseY + (HG_H*0.7) - ty * 1.5];
402
+ }
403
+ for(let j=1; j<=u.n_lower; j++) {
404
+ const ty = u.n_lower === 1 ? 0 : (j-1)/(u.n_lower-1) - 0.5;
405
+ pos[`${uid}_L${j}`] = [baseX, baseY - (HG_H*0.7) - ty * 1.5];
406
+ }
407
  });
408
+ });
409
 
410
+ // 2. Macro-connections (Inter-Unit wiring)
411
+ d.connections.forEach(conn => {
412
+ const p1 = pos[`${conn.from_uid}_C`];
413
+ const p2 = pos[`${conn.to_uid}_${conn.to_port}`];
414
+ if(p1 && p2) {
415
+ // Draw bezier curves for beautiful flowing tree
416
+ const midX = (p1[0] + p2[0]) / 2;
417
+ traces.push({
418
+ type:'scatter', mode:'lines',
419
+ x: [p1[0], midX, midX, p2[0]],
420
+ y: [p1[1], p1[1], p2[1], p2[1]],
421
+ line:{color:'rgba(255,255,255,0.15)', width:2, shape:'spline'},
422
+ hoverinfo:'none'
423
+ });
424
+ }
425
+ });
426
 
427
+ // 3. Intra-Unit Springs
428
+ const allN = [];
429
+ Object.keys(d.units).forEach(uid => {
430
+ const u = d.units[uid];
431
+
432
+ // Spring Lines
433
+ Object.entries(u.springs).forEach(([key, k]) => {
434
+ const [n1, n2] = key.split('→');
435
+ const p1 = pos[`${uid}_${n1}`], p2 = pos[`${uid}_${n2}`];
436
+ if(p1 && p2) {
437
+ const [col, wd] = springColor(k);
438
+ traces.push({
439
+ type:'scatter', mode:'lines', x:[p1[0], p2[0]], y:[p1[1], p2[1]],
440
+ line:{color:col, width:wd}, hoverinfo:'none'
441
+ });
442
+ }
443
+ });
444
 
445
+ // Collect nodes for scatter
446
+ allN.push({ id:`${uid}_A`, x:pos[`${uid}_A`][0], y:pos[`${uid}_A`][1], v:u.a_val, t:'A' });
447
+ allN.push({ id:`${uid}_B`, x:pos[`${uid}_B`][0], y:pos[`${uid}_B`][1], v:u.b_val, t:'B' });
448
+ allN.push({ id:`${uid}_C`, x:pos[`${uid}_C`][0], y:pos[`${uid}_C`][1], v:u.c_val, t:'C' });
449
+
450
+ for(let j=1; j<=u.n_upper; j++) {
451
+ const nd = u.nodes[`U${j}`];
452
+ allN.push({ id:`${uid}_U${j}`, x:pos[`${uid}_U${j}`][0], y:pos[`${uid}_U${j}`][1], vel:nd.vel, t:'U' });
453
+ }
454
+ for(let j=1; j<=u.n_lower; j++) {
455
+ const nd = u.nodes[`L${j}`];
456
+ allN.push({ id:`${uid}_L${j}`, x:pos[`${uid}_L${j}`][0], y:pos[`${uid}_L${j}`][1], vel:nd.vel, t:'L' });
457
+ }
458
+ });
459
 
460
+ // 4. Node Markers
461
+ const NCOL = t => t==='A'?'#fb923c': t==='B'?'#c084fc': t==='C'?'#38bdf8': t==='U'?'#4ade80':'#67e8f9';
462
  traces.push({
463
  type:'scatter', mode:'markers+text',
464
+ x: allN.map(n => n.x), y: allN.map(n => n.y),
465
+ text: allN.map(n => 'ABC'.includes(n.t) ? Number(n.v).toFixed(2) : ''),
466
+ textposition: 'top center',
467
+ textfont:{ size:9, color: allN.map(n => NCOL(n.t)) },
 
 
 
 
468
  marker:{
469
+ size: allN.map(n => 'ABC'.includes(n.t) ? 14 : 10 + Math.min(Math.abs(n.vel||0)*30, 8)),
470
+ color: allN.map(n => NCOL(n.t)),
471
+ line:{ width:2, color:'#22c55e' }
 
 
 
 
 
 
 
 
 
 
 
 
 
472
  },
473
+ hoverinfo:'none'
474
  });
475
 
476
+ const N_Lvs = d.topology.length;
477
+ const N_Max = d.topology[0].length;
478
+ const xMax = (N_Lvs - 1) * W_SPREAD + (HG_W*1.5);
479
+ const xMin = -(HG_W*1.5);
480
+ const yMax = Math.max(5, (N_Max/2) * H_SPREAD + 1);
481
+
482
+ return { traces, layout: {
483
+ margin:{l:8,r:8,t:8,b:8}, paper_bgcolor:'transparent', plot_bgcolor:'transparent',
484
+ xaxis:{visible:false, range:[xMin, xMax]}, yaxis:{visible:false, range:[-yMax, yMax]},
485
+ showlegend:false
486
+ }};
 
 
 
 
487
  }
488
 
489
  const ERR_LAYOUT = {
 
502
  const r = await fetch('/state');
503
  const d = await r.json();
504
 
505
+ // Sync UI Top Bar variables
506
+ syncTopoUI(d);
507
+ document.getElementById('b-cred').innerText = d.credit_mode.replace('_','').slice(0,7).toUpperCase();
508
+ document.getElementById('b-alpha').innerText = `α:${d.back_alpha.toFixed(2)}`;
509
+ document.getElementById('b-data').innerText = (d.dataset_type||'').slice(0,6).toUpperCase();
 
 
510
 
511
  const mBadge = document.getElementById('b-mode');
512
+ mBadge.innerText = d.mode === 'training' ? 'TRAIN' : 'INFER';
513
+ mBadge.className = d.mode === 'training'
514
  ? 'px-1.5 py-0.5 rounded bg-yellow-900/60 text-yellow-300 border border-yellow-800/60 text-[8px] font-bold'
515
  : 'px-1.5 py-0.5 rounded bg-green-900/60 text-green-300 border border-green-800/60 text-[8px] font-bold';
516
 
517
+ // Stiffness Bar Animation
518
+ const stiffAct = d.stiffness_active || 0;
519
+ const sBar = document.getElementById('stiff-bar');
520
+ sBar.style.width = `${stiffAct}%`;
521
+ sBar.className = `h-full transition-all duration-500 ease-out ${stiffAct > 0.1 ? 'bg-amber-400' : 'bg-slate-700'}`;
522
+ document.getElementById('stiff-lbl').innerText = `${stiffAct.toFixed(1)}%`;
523
+
524
+ document.getElementById('run-dot').className = `w-2 h-2 rounded-full ${d.running ? 'bg-green-400' : 'bg-slate-700'}`;
525
  document.getElementById('q-lbl').innerText = `Q:${d.queue_size}`;
526
  document.getElementById('iter-lbl').innerText = `IT:${d.iter}`;
527
 
528
+ const e = Math.abs(d.error);
529
  const col = e < 0.02 ? 'text-green-400' : e < 2 ? 'text-yellow-400' : 'text-red-400';
530
  document.getElementById('err-big').className = `text-xl font-bold ${col} leading-none`;
531
+ document.getElementById('err-big').innerText = d.error.toFixed(4);
532
+ document.getElementById('pred-val').innerText = `P: ${d.prediction.toFixed(4)}`;
 
 
 
 
533
 
534
+ // Nodes/Units pane
 
535
  let nh = '';
536
+ d.topology.forEach((level, lv) => {
537
+ nh += `<div class="text-[8px] text-emerald-600 py-1 font-bold border-b border-slate-800 mt-1">LEVEL ${lv} UNITS</div>`;
538
+ level.forEach(uid => {
539
+ const u = d.units[uid];
540
+ const kCol = u.stiffness_delta > 0 ? 'text-amber-400' : 'text-slate-600';
 
 
 
 
 
 
 
 
 
 
 
 
541
  nh += `<div class="flex justify-between items-center py-0.5 border-b border-slate-900/50">
542
+ <span class="text-white font-bold text-[9px] w-12">${uid.replace('HG_','')}</span>
543
+ <span class="text-slate-500 text-[8px] font-mono">I:[<span class="text-orange-300">${u.a_val.toFixed(2)}</span>,<span class="text-purple-300">${u.b_val.toFixed(2)}</span>] → O:<span class="text-cyan-300">${u.c_val.toFixed(3)}</span></span>
544
+ <span class="${kCol} text-[8px] w-10 text-right">Δk:${u.stiffness_delta.toFixed(0)}%</span>
545
  </div>`;
546
  });
547
+ });
 
 
 
 
 
548
  document.getElementById('pane-nodes').innerHTML = nh;
549
 
550
+ // Springs pane
551
  let sh = '';
552
+ d.topology.forEach((level, lv) => {
553
+ level.forEach(uid => {
554
+ const u = d.units[uid];
555
+ sh += `<div class="text-[8px] text-emerald-700 py-1 font-bold border-b border-slate-800 mt-1">${uid.replace('HG_','')}</div>`;
556
+ Object.entries(u.springs).forEach(([key, k]) => {
557
+ const kc = k < 0 ? 'text-blue-300' : k > 4 ? 'text-yellow-200' : 'text-purple-300';
558
+ sh += `<div class="flex justify-between py-0.5 border-b border-slate-900/50">
559
+ <span class="text-slate-500 text-[9px]">${key}</span>
560
+ <span class="${kc} font-bold text-[10px]">${k.toFixed(4)}</span></div>`;
561
+ });
562
+ });
563
+ });
 
 
 
564
  document.getElementById('pane-springs').innerHTML = sh;
565
 
566
  // Logs
 
568
  d.logs.map(l => `<div class="py-0.5 border-b border-slate-900/50">${l}</div>`).join('');
569
 
570
  // Mesh plot
571
+ const layerKey = JSON.stringify(d.topology) + d.n_upper + d.n_lower;
572
+ const { traces, layout } = buildTraces(d);
573
+
 
574
  if (!meshPlotted || layerKey !== lastLayerKey) {
575
  Plotly.newPlot('mesh-plot', traces, layout, {displayModeBar:false, responsive:true});
576
  meshPlotted = true; lastLayerKey = layerKey;