Spaces:
Runtime error
Runtime error
Update index.html
Browse files- index.html +59 -111
index.html
CHANGED
|
@@ -24,7 +24,6 @@
|
|
| 24 |
</head>
|
| 25 |
<body class="flex flex-col h-screen overflow-hidden">
|
| 26 |
|
| 27 |
-
<!-- ── HEADER ──────────────────────────────────────────────────────────────── -->
|
| 28 |
<header class="glass flex-shrink-0 px-2 py-1.5 flex justify-between items-center">
|
| 29 |
<div class="flex flex-wrap gap-1 text-[8px] font-bold items-center">
|
| 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>
|
|
@@ -34,7 +33,6 @@
|
|
| 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,7 +48,6 @@
|
|
| 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="Percentage of springs actively changing">
|
| 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">
|
|
@@ -67,13 +64,11 @@
|
|
| 67 |
</div>
|
| 68 |
</header>
|
| 69 |
|
| 70 |
-
<!-- ── PLOTS ────────────────────────────────────────────────────────────────── -->
|
| 71 |
<div class="flex-grow flex flex-col min-h-0 overflow-hidden">
|
| 72 |
<div id="mesh-plot" class="flex-grow min-h-0"></div>
|
| 73 |
<div id="err-plot" style="height:58px" class="flex-shrink-0 border-t border-slate-900"></div>
|
| 74 |
</div>
|
| 75 |
|
| 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>
|
|
@@ -104,18 +99,14 @@
|
|
| 104 |
|
| 105 |
<div class="grid grid-cols-2 gap-2">
|
| 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 |
-
|
| 113 |
-
<button class="tog off flex-1 py-2 rounded text-xs font-bold"
|
| 114 |
-
onclick="setMode('inference',this,'bg-yellow-700')">INFERENCE</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">ARCHITECTURE TOPOLOGY (Dimensionality)</div>
|
| 121 |
<div class="flex gap-2 items-center mb-1">
|
|
@@ -133,7 +124,6 @@
|
|
| 133 |
<div id="topo-hint" class="text-[8px] text-emerald-500 italic mt-1 text-center">Flat mode: D independent variables.</div>
|
| 134 |
</div>
|
| 135 |
|
| 136 |
-
<!-- CREDIT ASSIGNMENT -->
|
| 137 |
<div class="col-span-2 bg-slate-900 rounded p-3 border border-fuchsia-900/50">
|
| 138 |
<div class="text-fuchsia-400 text-[9px] font-bold mb-2">TRAINING BEHAVIOR (How errors fix springs)</div>
|
| 139 |
<select id="cfg-cred" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded">
|
|
@@ -144,24 +134,18 @@
|
|
| 144 |
<div class="text-[8px] text-slate-500 mt-1 italic text-center">Controls how the model learns when stacking multiple hourglasses.</div>
|
| 145 |
</div>
|
| 146 |
|
| 147 |
-
<!-- ARCH & ALPHA -->
|
| 148 |
<div class="bg-slate-900 rounded p-3 border border-blue-900/50">
|
| 149 |
<div class="text-blue-400 text-[9px] font-bold mb-2">NODE ACTIVATION</div>
|
| 150 |
-
<button class="tog on w-full mb-1 py-2 rounded text-[10px] font-bold bg-blue-700"
|
| 151 |
-
|
| 152 |
-
<button class="tog off w-full py-2 rounded text-[10px] font-bold"
|
| 153 |
-
onclick="pick('architecture','multiplicative',this,'bg-blue-700')">MULTIPLICATIVE Π</button>
|
| 154 |
</div>
|
| 155 |
|
| 156 |
<div class="bg-slate-900 rounded p-3 border border-orange-900/50">
|
| 157 |
<div class="text-orange-400 text-[9px] font-bold mb-1">BACK-TENSION α</div>
|
| 158 |
-
<input id="alpha-sl" type="range" min="0" max="100" value="45" step="5"
|
| 159 |
-
class="w-full accent-orange-500 mt-1"
|
| 160 |
-
oninput="document.getElementById('alpha-val').innerText=(this.value/100).toFixed(2)">
|
| 161 |
<div class="text-center text-orange-300 font-bold text-xl mt-0.5" id="alpha-val">0.45</div>
|
| 162 |
</div>
|
| 163 |
|
| 164 |
-
<!-- H-BULGES -->
|
| 165 |
<div class="col-span-2 bg-slate-900 rounded p-2 text-center border border-slate-700 grid grid-cols-2 gap-2">
|
| 166 |
<div class="bg-slate-800 rounded p-2 text-center border border-orange-900/40">
|
| 167 |
<div class="text-orange-400 text-[8px] mb-1">UPPER ROW (U)</div>
|
|
@@ -182,14 +166,11 @@
|
|
| 182 |
</div>
|
| 183 |
</div>
|
| 184 |
|
| 185 |
-
<button onclick="applyConfig()"
|
| 186 |
-
class="w-full bg-white text-black py-3 rounded font-bold text-sm hover:bg-slate-200">
|
| 187 |
APPLY & REBUILD TOPOLOGY
|
| 188 |
</button>
|
| 189 |
|
| 190 |
-
<!-- DATASET & CUSTOM -->
|
| 191 |
<div class="grid grid-cols-2 gap-2 mt-2">
|
| 192 |
-
<!-- BATCH GEN -->
|
| 193 |
<div class="bg-slate-900 rounded p-3 border border-pink-900/50">
|
| 194 |
<div class="text-pink-400 text-[9px] font-bold mb-2">DATASET</div>
|
| 195 |
<select id="ds-sel" onchange="refreshDS()" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded mb-2">
|
|
@@ -204,7 +185,6 @@
|
|
| 204 |
</div>
|
| 205 |
</div>
|
| 206 |
|
| 207 |
-
<!-- CUSTOM INPUT VALIDATOR -->
|
| 208 |
<div class="bg-slate-900 rounded p-3 border border-cyan-900/50 flex flex-col">
|
| 209 |
<div class="text-cyan-400 text-[9px] font-bold mb-1">CUSTOM INPUT PREVIEW</div>
|
| 210 |
<div id="ds-examples" class="text-[9px] text-slate-500 mb-2">e.g. click me</div>
|
|
@@ -215,7 +195,6 @@
|
|
| 215 |
<input id="cc" type="text" placeholder="auto" class="w-full bg-black border border-slate-700 p-1.5 text-white text-xs text-center rounded" title="Optional Target C">
|
| 216 |
</div>
|
| 217 |
|
| 218 |
-
<!-- Crucial Realtime Validator Text -->
|
| 219 |
<div id="expected-lbl" class="text-[9px] text-yellow-400 font-bold mb-2 min-h-[14px] text-center flex-grow flex items-center justify-center">
|
| 220 |
Ground truth: waiting...
|
| 221 |
</div>
|
|
@@ -228,11 +207,9 @@
|
|
| 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 |
-
// ── DATASET LOGIC FOR PREVIEW ────────────────────────────────────────────────
|
| 236 |
const DS = {
|
| 237 |
housing: { hint:'A×2.5+B×1.2', fn:(a,b)=>(a*2.5+b*1.2).toFixed(3), ex:[{a:4,b:2},{a:6,b:3}] },
|
| 238 |
subtraction: { hint:'A−B', fn:(a,b)=>(a-b).toFixed(3), ex:[{a:8,b:3},{a:5,b:5}] },
|
|
@@ -240,21 +217,26 @@ const DS = {
|
|
| 240 |
quadratic: { hint:'A²+B', fn:(a,b)=>(a*a+b).toFixed(3), ex:[{a:3,b:2},{a:4,b:5}] },
|
| 241 |
};
|
| 242 |
|
| 243 |
-
function refreshDS() {
|
| 244 |
const ds = document.getElementById('ds-sel').value;
|
| 245 |
const m = DS[ds];
|
| 246 |
document.getElementById('ds-examples').innerHTML = 'e.g. ' +
|
| 247 |
m.ex.map(e => `<span class="cursor-pointer text-cyan-500 underline"
|
| 248 |
onclick="fillC('${e.a}','${e.b}')">A=${e.a} B=${e.b}</span>`).join(' ');
|
| 249 |
updateExpected();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
}
|
| 251 |
|
| 252 |
function fillC(a, b) {
|
| 253 |
-
const
|
| 254 |
-
const n =
|
| 255 |
|
| 256 |
if (n > 1) {
|
| 257 |
-
// Fill with comma separated values if n > 1
|
| 258 |
document.getElementById('ca').value = Array(n).fill(a).join(',');
|
| 259 |
document.getElementById('cb').value = Array(n).fill(b).join(',');
|
| 260 |
} else {
|
|
@@ -276,46 +258,39 @@ function updateExpected() {
|
|
| 276 |
const lbl = document.getElementById('expected-lbl');
|
| 277 |
|
| 278 |
if (!a || !b || !DS[ds]) {
|
| 279 |
-
lbl.innerText = "Enter A and B values";
|
| 280 |
-
return;
|
| 281 |
}
|
| 282 |
|
| 283 |
const fn = DS[ds].fn;
|
| 284 |
const av = parseVals(a), bv = parseVals(b);
|
| 285 |
if (!av.length || !bv.length) {
|
| 286 |
-
lbl.innerText = "Invalid numbers";
|
| 287 |
-
return;
|
| 288 |
}
|
| 289 |
|
| 290 |
const stackLv = parseInt(document.getElementById('cfg-stack').value);
|
| 291 |
const isStacked = stackLv > 0;
|
| 292 |
-
// If stacked, D is fixed to 2^stack. Otherwise use D dial.
|
| 293 |
const n = isStacked ? Math.pow(2, stackLv) : parseInt(document.getElementById('cfg-inputs').value);
|
| 294 |
|
| 295 |
if (isStacked) {
|
| 296 |
-
// For stacked, the entire system converges to a single output target
|
| 297 |
-
// which in your python is evaluated on the first item in the list
|
| 298 |
const res = fn(av[0], bv[0]);
|
| 299 |
-
lbl.innerText = `Final
|
| 300 |
} else {
|
| 301 |
-
// For flat, it evaluates N independent outputs
|
| 302 |
const results = Array.from({length:n}, (_,i) => fn(av[i%av.length], bv[i%bv.length]));
|
| 303 |
-
lbl.innerText = n === 1 ? `Target
|
| 304 |
}
|
| 305 |
}
|
| 306 |
|
| 307 |
-
// ── UI TOPOLOGY TOGGLES ──────────────────────────────────────────────────────
|
| 308 |
function updateTopoUI() {
|
| 309 |
const stack = parseInt(document.getElementById('cfg-stack').value);
|
| 310 |
const dimInput = document.getElementById('cfg-inputs');
|
| 311 |
const hint = document.getElementById('topo-hint');
|
| 312 |
|
| 313 |
if (stack > 0) {
|
| 314 |
-
const
|
| 315 |
-
dimInput.value =
|
| 316 |
dimInput.disabled = true;
|
| 317 |
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";
|
| 318 |
-
hint.innerText = `Stacked mode requires
|
| 319 |
} else {
|
| 320 |
dimInput.disabled = false;
|
| 321 |
dimInput.className = "w-full bg-black border border-slate-700 text-center text-white rounded text-sm p-0.5";
|
|
@@ -360,10 +335,7 @@ async function setMode(m, btn, cls) {
|
|
| 360 |
b.classList.add('off'); b.classList.remove('on', cls);
|
| 361 |
});
|
| 362 |
btn.classList.remove('off'); btn.classList.add('on', cls);
|
| 363 |
-
await fetch('/set_mode', {
|
| 364 |
-
method:'POST', headers:{'Content-Type':'application/json'},
|
| 365 |
-
body: JSON.stringify({ mode: m })
|
| 366 |
-
});
|
| 367 |
document.getElementById('b-mode').innerText = m === 'training' ? 'TRAIN' : 'INFER';
|
| 368 |
}
|
| 369 |
|
|
@@ -378,7 +350,6 @@ function pick(key, val, btn, cls) {
|
|
| 378 |
function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); refreshDS(); }
|
| 379 |
function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
|
| 380 |
|
| 381 |
-
// Initialize defaults on load
|
| 382 |
refreshDS();
|
| 383 |
|
| 384 |
async function applyConfig() {
|
|
@@ -386,9 +357,7 @@ async function applyConfig() {
|
|
| 386 |
let cred = document.getElementById('cfg-cred').value;
|
| 387 |
const alpha = parseFloat(document.getElementById('alpha-sl').value) / 100;
|
| 388 |
|
| 389 |
-
|
| 390 |
-
let rev = false;
|
| 391 |
-
let ind = false;
|
| 392 |
if (cred === 'reverse') { rev = true; cred = 'elastic_backprop'; }
|
| 393 |
if (cred === 'independent') { ind = true; cred = 'independent'; }
|
| 394 |
|
|
@@ -424,13 +393,29 @@ async function startBatch() {
|
|
| 424 |
}
|
| 425 |
|
| 426 |
async function runCustom() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
await fetch('/run_custom', {
|
| 428 |
method:'POST', headers:{'Content-Type':'application/json'},
|
| 429 |
-
body: JSON.stringify({
|
| 430 |
-
a: document.getElementById('ca').value,
|
| 431 |
-
b: document.getElementById('cb').value,
|
| 432 |
-
c: document.getElementById('cc').value || null
|
| 433 |
-
})
|
| 434 |
});
|
| 435 |
closeDrawer();
|
| 436 |
}
|
|
@@ -449,7 +434,7 @@ function tab(name) {
|
|
| 449 |
});
|
| 450 |
}
|
| 451 |
|
| 452 |
-
// ── VISUALIZATION
|
| 453 |
|
| 454 |
function springColor(k) {
|
| 455 |
const t = Math.min(Math.abs(k) / 6, 1);
|
|
@@ -460,24 +445,17 @@ function springColor(k) {
|
|
| 460 |
function buildTraces(d) {
|
| 461 |
const pos = {};
|
| 462 |
const traces = [];
|
| 463 |
-
|
| 464 |
-
const W_SPREAD = 5.5;
|
| 465 |
-
const H_SPREAD = 4.0;
|
| 466 |
-
const HG_W = 2.0;
|
| 467 |
-
const HG_H = 1.5;
|
| 468 |
|
| 469 |
d.topology.forEach((level, lv) => {
|
| 470 |
const baseX = lv * W_SPREAD;
|
| 471 |
const N = level.length;
|
| 472 |
-
|
| 473 |
level.forEach((uid, i) => {
|
| 474 |
const baseY = (i - (N - 1) / 2) * -H_SPREAD;
|
| 475 |
const u = d.units[uid];
|
| 476 |
-
|
| 477 |
pos[`${uid}_A`] = [baseX - (HG_W/2), baseY + HG_H];
|
| 478 |
pos[`${uid}_B`] = [baseX - (HG_W/2), baseY - HG_H];
|
| 479 |
pos[`${uid}_C`] = [baseX + (HG_W/2), baseY];
|
| 480 |
-
|
| 481 |
for(let j=1; j<=u.n_upper; j++) {
|
| 482 |
const ty = u.n_upper === 1 ? 0 : (j-1)/(u.n_upper-1) - 0.5;
|
| 483 |
pos[`${uid}_U${j}`] = [baseX, baseY + (HG_H*0.7) - ty * 1.5];
|
|
@@ -489,18 +467,14 @@ function buildTraces(d) {
|
|
| 489 |
});
|
| 490 |
});
|
| 491 |
|
| 492 |
-
// Wiring between levels
|
| 493 |
d.connections.forEach(conn => {
|
| 494 |
const p1 = pos[`${conn.from_uid}_C`];
|
| 495 |
const p2 = pos[`${conn.to_uid}_${conn.to_port}`];
|
| 496 |
if(p1 && p2) {
|
| 497 |
const midX = (p1[0] + p2[0]) / 2;
|
| 498 |
traces.push({
|
| 499 |
-
type:'scatter', mode:'lines',
|
| 500 |
-
|
| 501 |
-
y: [p1[1], p1[1], p2[1], p2[1]],
|
| 502 |
-
line:{color:'rgba(255,255,255,0.15)', width:2, shape:'spline'},
|
| 503 |
-
hoverinfo:'none'
|
| 504 |
});
|
| 505 |
}
|
| 506 |
});
|
|
@@ -513,24 +487,17 @@ function buildTraces(d) {
|
|
| 513 |
const p1 = pos[`${uid}_${n1}`], p2 = pos[`${uid}_${n2}`];
|
| 514 |
if(p1 && p2) {
|
| 515 |
const [col, wd] = springColor(k);
|
| 516 |
-
traces.push({
|
| 517 |
-
type:'scatter', mode:'lines', x:[p1[0], p2[0]], y:[p1[1], p2[1]],
|
| 518 |
-
line:{color:col, width:wd}, hoverinfo:'none'
|
| 519 |
-
});
|
| 520 |
}
|
| 521 |
});
|
| 522 |
-
|
| 523 |
allN.push({ id:`${uid}_A`, x:pos[`${uid}_A`][0], y:pos[`${uid}_A`][1], v:u.a_val, t:'A' });
|
| 524 |
allN.push({ id:`${uid}_B`, x:pos[`${uid}_B`][0], y:pos[`${uid}_B`][1], v:u.b_val, t:'B' });
|
| 525 |
allN.push({ id:`${uid}_C`, x:pos[`${uid}_C`][0], y:pos[`${uid}_C`][1], v:u.c_val, t:'C' });
|
| 526 |
-
|
| 527 |
for(let j=1; j<=u.n_upper; j++) {
|
| 528 |
-
|
| 529 |
-
allN.push({ id:`${uid}_U${j}`, x:pos[`${uid}_U${j}`][0], y:pos[`${uid}_U${j}`][1], vel:nd.vel, t:'U' });
|
| 530 |
}
|
| 531 |
for(let j=1; j<=u.n_lower; j++) {
|
| 532 |
-
|
| 533 |
-
allN.push({ id:`${uid}_L${j}`, x:pos[`${uid}_L${j}`][0], y:pos[`${uid}_L${j}`][1], vel:nd.vel, t:'L' });
|
| 534 |
}
|
| 535 |
});
|
| 536 |
|
|
@@ -549,27 +516,15 @@ function buildTraces(d) {
|
|
| 549 |
hoverinfo:'none'
|
| 550 |
});
|
| 551 |
|
| 552 |
-
const
|
| 553 |
-
const
|
| 554 |
-
const xMax = (N_Lvs - 1) * W_SPREAD + (HG_W*1.5);
|
| 555 |
-
const xMin = -(HG_W*1.5);
|
| 556 |
-
const yMax = Math.max(5, (N_Max/2) * H_SPREAD + 1);
|
| 557 |
|
| 558 |
return { traces, layout: {
|
| 559 |
margin:{l:8,r:8,t:8,b:8}, paper_bgcolor:'transparent', plot_bgcolor:'transparent',
|
| 560 |
-
xaxis:{visible:false, range:[
|
| 561 |
-
showlegend:false
|
| 562 |
}};
|
| 563 |
}
|
| 564 |
|
| 565 |
-
const ERR_LAYOUT = {
|
| 566 |
-
margin:{l:30,r:6,t:3,b:12},
|
| 567 |
-
paper_bgcolor:'transparent', plot_bgcolor:'transparent',
|
| 568 |
-
xaxis:{visible:false},
|
| 569 |
-
yaxis:{color:'#334155', gridcolor:'#0f172a', zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1},
|
| 570 |
-
showlegend:false,
|
| 571 |
-
};
|
| 572 |
-
|
| 573 |
let meshPlotted = false, errPlotted = false, lastLayerKey = '';
|
| 574 |
|
| 575 |
// ── POLL ──────────────────────────────────────────────────────────────────────
|
|
@@ -579,7 +534,6 @@ setInterval(async () => {
|
|
| 579 |
const d = await r.json();
|
| 580 |
|
| 581 |
syncTopoUI(d);
|
| 582 |
-
|
| 583 |
let credBadge = d.credit_mode.replace('_','').slice(0,7).toUpperCase();
|
| 584 |
if (d.reverse_mode) credBadge = 'REVERSE';
|
| 585 |
if (d.individual_train) credBadge = 'INDEPND';
|
|
@@ -639,8 +593,7 @@ setInterval(async () => {
|
|
| 639 |
});
|
| 640 |
document.getElementById('pane-springs').innerHTML = sh;
|
| 641 |
|
| 642 |
-
document.getElementById('pane-logs').innerHTML =
|
| 643 |
-
d.logs.map(l => `<div class="py-0.5 border-b border-slate-900/50">${l}</div>`).join('');
|
| 644 |
|
| 645 |
const layerKey = JSON.stringify(d.topology) + d.n_upper + d.n_lower;
|
| 646 |
const { traces, layout } = buildTraces(d);
|
|
@@ -653,17 +606,12 @@ setInterval(async () => {
|
|
| 653 |
}
|
| 654 |
|
| 655 |
const hist = d.history;
|
| 656 |
-
const eTrace = {
|
| 657 |
-
type:'scatter', mode:'lines',
|
| 658 |
-
x: hist.map((_,i)=>i), y: hist,
|
| 659 |
-
line:{color:'#f97316',width:1.5},
|
| 660 |
-
fill:'tozeroy', fillcolor:'rgba(249,115,22,0.07)'
|
| 661 |
-
};
|
| 662 |
if (!errPlotted) {
|
| 663 |
-
Plotly.newPlot('err-plot',[eTrace],
|
| 664 |
errPlotted = true;
|
| 665 |
} else {
|
| 666 |
-
Plotly.react('err-plot',[eTrace],
|
| 667 |
}
|
| 668 |
|
| 669 |
} catch(e) { }
|
|
|
|
| 24 |
</head>
|
| 25 |
<body class="flex flex-col h-screen overflow-hidden">
|
| 26 |
|
|
|
|
| 27 |
<header class="glass flex-shrink-0 px-2 py-1.5 flex justify-between items-center">
|
| 28 |
<div class="flex flex-wrap gap-1 text-[8px] font-bold items-center">
|
| 29 |
<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>
|
|
|
|
| 33 |
<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>
|
| 34 |
<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>
|
| 35 |
|
|
|
|
| 36 |
<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">
|
| 37 |
<span class="text-yellow-300 mr-0.5">D</span>
|
| 38 |
<button class="lbtn" onclick="quickL('inputs',-1)">−</button>
|
|
|
|
| 48 |
<button class="lbtn" onclick="quickL('lower',+1)">+</button>
|
| 49 |
</span>
|
| 50 |
|
|
|
|
| 51 |
<div class="flex items-center gap-1.5 px-2 border-l border-slate-700 ml-2" title="Percentage of springs actively changing">
|
| 52 |
<span class="text-[7px] text-slate-500 uppercase tracking-wider">Learning</span>
|
| 53 |
<div class="w-16 bg-slate-900 rounded-sm h-2 overflow-hidden border border-slate-700">
|
|
|
|
| 64 |
</div>
|
| 65 |
</header>
|
| 66 |
|
|
|
|
| 67 |
<div class="flex-grow flex flex-col min-h-0 overflow-hidden">
|
| 68 |
<div id="mesh-plot" class="flex-grow min-h-0"></div>
|
| 69 |
<div id="err-plot" style="height:58px" class="flex-shrink-0 border-t border-slate-900"></div>
|
| 70 |
</div>
|
| 71 |
|
|
|
|
| 72 |
<div class="glass flex-shrink-0 border-t border-slate-800" style="height:174px">
|
| 73 |
<div class="flex border-b border-slate-800 text-[10px]">
|
| 74 |
<button onclick="tab('nodes')" id="tab-nodes" class="flex-1 py-1.5 bg-blue-900/40 text-blue-300 font-bold">UNITS</button>
|
|
|
|
| 99 |
|
| 100 |
<div class="grid grid-cols-2 gap-2">
|
| 101 |
|
|
|
|
| 102 |
<div class="col-span-2 bg-slate-900 rounded p-3 border border-yellow-900/50">
|
| 103 |
<div class="text-yellow-400 text-[9px] font-bold mb-1">EXECUTION MODE</div>
|
| 104 |
<div class="flex gap-2">
|
| 105 |
+
<button class="tog on flex-1 py-2 rounded text-xs font-bold bg-yellow-700" onclick="setMode('training',this,'bg-yellow-700')">TRAINING</button>
|
| 106 |
+
<button class="tog off flex-1 py-2 rounded text-xs font-bold" onclick="setMode('inference',this,'bg-yellow-700')">INFERENCE</button>
|
|
|
|
|
|
|
| 107 |
</div>
|
| 108 |
</div>
|
| 109 |
|
|
|
|
| 110 |
<div class="col-span-2 bg-slate-900 rounded p-3 border border-emerald-900/50">
|
| 111 |
<div class="text-emerald-400 text-[9px] font-bold mb-2">ARCHITECTURE TOPOLOGY (Dimensionality)</div>
|
| 112 |
<div class="flex gap-2 items-center mb-1">
|
|
|
|
| 124 |
<div id="topo-hint" class="text-[8px] text-emerald-500 italic mt-1 text-center">Flat mode: D independent variables.</div>
|
| 125 |
</div>
|
| 126 |
|
|
|
|
| 127 |
<div class="col-span-2 bg-slate-900 rounded p-3 border border-fuchsia-900/50">
|
| 128 |
<div class="text-fuchsia-400 text-[9px] font-bold mb-2">TRAINING BEHAVIOR (How errors fix springs)</div>
|
| 129 |
<select id="cfg-cred" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded">
|
|
|
|
| 134 |
<div class="text-[8px] text-slate-500 mt-1 italic text-center">Controls how the model learns when stacking multiple hourglasses.</div>
|
| 135 |
</div>
|
| 136 |
|
|
|
|
| 137 |
<div class="bg-slate-900 rounded p-3 border border-blue-900/50">
|
| 138 |
<div class="text-blue-400 text-[9px] font-bold mb-2">NODE ACTIVATION</div>
|
| 139 |
+
<button class="tog on w-full mb-1 py-2 rounded text-[10px] font-bold bg-blue-700" onclick="pick('architecture','additive',this,'bg-blue-700')">ADDITIVE Σ</button>
|
| 140 |
+
<button class="tog off w-full py-2 rounded text-[10px] font-bold" onclick="pick('architecture','multiplicative',this,'bg-blue-700')">MULTIPLICATIVE Π</button>
|
|
|
|
|
|
|
| 141 |
</div>
|
| 142 |
|
| 143 |
<div class="bg-slate-900 rounded p-3 border border-orange-900/50">
|
| 144 |
<div class="text-orange-400 text-[9px] font-bold mb-1">BACK-TENSION α</div>
|
| 145 |
+
<input id="alpha-sl" type="range" min="0" max="100" value="45" step="5" class="w-full accent-orange-500 mt-1" oninput="document.getElementById('alpha-val').innerText=(this.value/100).toFixed(2)">
|
|
|
|
|
|
|
| 146 |
<div class="text-center text-orange-300 font-bold text-xl mt-0.5" id="alpha-val">0.45</div>
|
| 147 |
</div>
|
| 148 |
|
|
|
|
| 149 |
<div class="col-span-2 bg-slate-900 rounded p-2 text-center border border-slate-700 grid grid-cols-2 gap-2">
|
| 150 |
<div class="bg-slate-800 rounded p-2 text-center border border-orange-900/40">
|
| 151 |
<div class="text-orange-400 text-[8px] mb-1">UPPER ROW (U)</div>
|
|
|
|
| 166 |
</div>
|
| 167 |
</div>
|
| 168 |
|
| 169 |
+
<button onclick="applyConfig()" class="w-full bg-white text-black py-3 rounded font-bold text-sm hover:bg-slate-200 mt-2">
|
|
|
|
| 170 |
APPLY & REBUILD TOPOLOGY
|
| 171 |
</button>
|
| 172 |
|
|
|
|
| 173 |
<div class="grid grid-cols-2 gap-2 mt-2">
|
|
|
|
| 174 |
<div class="bg-slate-900 rounded p-3 border border-pink-900/50">
|
| 175 |
<div class="text-pink-400 text-[9px] font-bold mb-2">DATASET</div>
|
| 176 |
<select id="ds-sel" onchange="refreshDS()" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded mb-2">
|
|
|
|
| 185 |
</div>
|
| 186 |
</div>
|
| 187 |
|
|
|
|
| 188 |
<div class="bg-slate-900 rounded p-3 border border-cyan-900/50 flex flex-col">
|
| 189 |
<div class="text-cyan-400 text-[9px] font-bold mb-1">CUSTOM INPUT PREVIEW</div>
|
| 190 |
<div id="ds-examples" class="text-[9px] text-slate-500 mb-2">e.g. click me</div>
|
|
|
|
| 195 |
<input id="cc" type="text" placeholder="auto" class="w-full bg-black border border-slate-700 p-1.5 text-white text-xs text-center rounded" title="Optional Target C">
|
| 196 |
</div>
|
| 197 |
|
|
|
|
| 198 |
<div id="expected-lbl" class="text-[9px] text-yellow-400 font-bold mb-2 min-h-[14px] text-center flex-grow flex items-center justify-center">
|
| 199 |
Ground truth: waiting...
|
| 200 |
</div>
|
|
|
|
| 207 |
</aside>
|
| 208 |
|
| 209 |
<script>
|
|
|
|
| 210 |
const cfg = { mode: 'training', architecture: 'additive' };
|
| 211 |
const topo = { inputs: 1, upper: 3, lower: 3, stack: 0 };
|
| 212 |
|
|
|
|
| 213 |
const DS = {
|
| 214 |
housing: { hint:'A×2.5+B×1.2', fn:(a,b)=>(a*2.5+b*1.2).toFixed(3), ex:[{a:4,b:2},{a:6,b:3}] },
|
| 215 |
subtraction: { hint:'A−B', fn:(a,b)=>(a-b).toFixed(3), ex:[{a:8,b:3},{a:5,b:5}] },
|
|
|
|
| 217 |
quadratic: { hint:'A²+B', fn:(a,b)=>(a*a+b).toFixed(3), ex:[{a:3,b:2},{a:4,b:5}] },
|
| 218 |
};
|
| 219 |
|
| 220 |
+
async function refreshDS() {
|
| 221 |
const ds = document.getElementById('ds-sel').value;
|
| 222 |
const m = DS[ds];
|
| 223 |
document.getElementById('ds-examples').innerHTML = 'e.g. ' +
|
| 224 |
m.ex.map(e => `<span class="cursor-pointer text-cyan-500 underline"
|
| 225 |
onclick="fillC('${e.a}','${e.b}')">A=${e.a} B=${e.b}</span>`).join(' ');
|
| 226 |
updateExpected();
|
| 227 |
+
|
| 228 |
+
// Quietly sync backend dataset immediately so custom runs perfectly match UI
|
| 229 |
+
await fetch('/config', {
|
| 230 |
+
method:'POST', headers:{'Content-Type':'application/json'},
|
| 231 |
+
body: JSON.stringify({ dataset: ds })
|
| 232 |
+
});
|
| 233 |
}
|
| 234 |
|
| 235 |
function fillC(a, b) {
|
| 236 |
+
const stackLv = parseInt(document.getElementById('cfg-stack').value);
|
| 237 |
+
const n = stackLv > 0 ? Math.pow(2, stackLv) : parseInt(document.getElementById('cfg-inputs').value);
|
| 238 |
|
| 239 |
if (n > 1) {
|
|
|
|
| 240 |
document.getElementById('ca').value = Array(n).fill(a).join(',');
|
| 241 |
document.getElementById('cb').value = Array(n).fill(b).join(',');
|
| 242 |
} else {
|
|
|
|
| 258 |
const lbl = document.getElementById('expected-lbl');
|
| 259 |
|
| 260 |
if (!a || !b || !DS[ds]) {
|
| 261 |
+
lbl.innerText = "Enter A and B values"; return;
|
|
|
|
| 262 |
}
|
| 263 |
|
| 264 |
const fn = DS[ds].fn;
|
| 265 |
const av = parseVals(a), bv = parseVals(b);
|
| 266 |
if (!av.length || !bv.length) {
|
| 267 |
+
lbl.innerText = "Invalid numbers"; return;
|
|
|
|
| 268 |
}
|
| 269 |
|
| 270 |
const stackLv = parseInt(document.getElementById('cfg-stack').value);
|
| 271 |
const isStacked = stackLv > 0;
|
|
|
|
| 272 |
const n = isStacked ? Math.pow(2, stackLv) : parseInt(document.getElementById('cfg-inputs').value);
|
| 273 |
|
| 274 |
if (isStacked) {
|
|
|
|
|
|
|
| 275 |
const res = fn(av[0], bv[0]);
|
| 276 |
+
lbl.innerText = `Final Target: ${res}`;
|
| 277 |
} else {
|
|
|
|
| 278 |
const results = Array.from({length:n}, (_,i) => fn(av[i%av.length], bv[i%bv.length]));
|
| 279 |
+
lbl.innerText = n === 1 ? `Target: ${results[0]}` : `Targets: [${results.join(', ')}]`;
|
| 280 |
}
|
| 281 |
}
|
| 282 |
|
|
|
|
| 283 |
function updateTopoUI() {
|
| 284 |
const stack = parseInt(document.getElementById('cfg-stack').value);
|
| 285 |
const dimInput = document.getElementById('cfg-inputs');
|
| 286 |
const hint = document.getElementById('topo-hint');
|
| 287 |
|
| 288 |
if (stack > 0) {
|
| 289 |
+
const req = Math.pow(2, stack);
|
| 290 |
+
dimInput.value = req;
|
| 291 |
dimInput.disabled = true;
|
| 292 |
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";
|
| 293 |
+
hint.innerText = `Stacked mode requires ${req} comma-separated inputs per batch.`;
|
| 294 |
} else {
|
| 295 |
dimInput.disabled = false;
|
| 296 |
dimInput.className = "w-full bg-black border border-slate-700 text-center text-white rounded text-sm p-0.5";
|
|
|
|
| 335 |
b.classList.add('off'); b.classList.remove('on', cls);
|
| 336 |
});
|
| 337 |
btn.classList.remove('off'); btn.classList.add('on', cls);
|
| 338 |
+
await fetch('/set_mode', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ mode: m }) });
|
|
|
|
|
|
|
|
|
|
| 339 |
document.getElementById('b-mode').innerText = m === 'training' ? 'TRAIN' : 'INFER';
|
| 340 |
}
|
| 341 |
|
|
|
|
| 350 |
function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); refreshDS(); }
|
| 351 |
function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
|
| 352 |
|
|
|
|
| 353 |
refreshDS();
|
| 354 |
|
| 355 |
async function applyConfig() {
|
|
|
|
| 357 |
let cred = document.getElementById('cfg-cred').value;
|
| 358 |
const alpha = parseFloat(document.getElementById('alpha-sl').value) / 100;
|
| 359 |
|
| 360 |
+
let rev = false, ind = false;
|
|
|
|
|
|
|
| 361 |
if (cred === 'reverse') { rev = true; cred = 'elastic_backprop'; }
|
| 362 |
if (cred === 'independent') { ind = true; cred = 'independent'; }
|
| 363 |
|
|
|
|
| 393 |
}
|
| 394 |
|
| 395 |
async function runCustom() {
|
| 396 |
+
const a = document.getElementById('ca').value;
|
| 397 |
+
const b = document.getElementById('cb').value;
|
| 398 |
+
let c = document.getElementById('cc').value;
|
| 399 |
+
|
| 400 |
+
// STRICT UI SYNC: Calculate exact C array so Backend math is strictly overriden to match UI.
|
| 401 |
+
if (!c) {
|
| 402 |
+
const ds = document.getElementById('ds-sel').value;
|
| 403 |
+
const fn = DS[ds].fn;
|
| 404 |
+
const av = parseVals(a), bv = parseVals(b);
|
| 405 |
+
if (av.length && bv.length) {
|
| 406 |
+
const stackLv = parseInt(document.getElementById('cfg-stack').value);
|
| 407 |
+
if (stackLv > 0) {
|
| 408 |
+
c = fn(av[0], bv[0]);
|
| 409 |
+
} else {
|
| 410 |
+
const n = parseInt(document.getElementById('cfg-inputs').value);
|
| 411 |
+
c = Array.from({length:n}, (_,i) => fn(av[i%av.length], bv[i%bv.length])).join(',');
|
| 412 |
+
}
|
| 413 |
+
}
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
await fetch('/run_custom', {
|
| 417 |
method:'POST', headers:{'Content-Type':'application/json'},
|
| 418 |
+
body: JSON.stringify({ a, b, c })
|
|
|
|
|
|
|
|
|
|
|
|
|
| 419 |
});
|
| 420 |
closeDrawer();
|
| 421 |
}
|
|
|
|
| 434 |
});
|
| 435 |
}
|
| 436 |
|
| 437 |
+
// ── VISUALIZATION ────────────────────────────────────────────────────────────
|
| 438 |
|
| 439 |
function springColor(k) {
|
| 440 |
const t = Math.min(Math.abs(k) / 6, 1);
|
|
|
|
| 445 |
function buildTraces(d) {
|
| 446 |
const pos = {};
|
| 447 |
const traces = [];
|
| 448 |
+
const W_SPREAD = 5.5, H_SPREAD = 4.0, HG_W = 2.0, HG_H = 1.5;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
|
| 450 |
d.topology.forEach((level, lv) => {
|
| 451 |
const baseX = lv * W_SPREAD;
|
| 452 |
const N = level.length;
|
|
|
|
| 453 |
level.forEach((uid, i) => {
|
| 454 |
const baseY = (i - (N - 1) / 2) * -H_SPREAD;
|
| 455 |
const u = d.units[uid];
|
|
|
|
| 456 |
pos[`${uid}_A`] = [baseX - (HG_W/2), baseY + HG_H];
|
| 457 |
pos[`${uid}_B`] = [baseX - (HG_W/2), baseY - HG_H];
|
| 458 |
pos[`${uid}_C`] = [baseX + (HG_W/2), baseY];
|
|
|
|
| 459 |
for(let j=1; j<=u.n_upper; j++) {
|
| 460 |
const ty = u.n_upper === 1 ? 0 : (j-1)/(u.n_upper-1) - 0.5;
|
| 461 |
pos[`${uid}_U${j}`] = [baseX, baseY + (HG_H*0.7) - ty * 1.5];
|
|
|
|
| 467 |
});
|
| 468 |
});
|
| 469 |
|
|
|
|
| 470 |
d.connections.forEach(conn => {
|
| 471 |
const p1 = pos[`${conn.from_uid}_C`];
|
| 472 |
const p2 = pos[`${conn.to_uid}_${conn.to_port}`];
|
| 473 |
if(p1 && p2) {
|
| 474 |
const midX = (p1[0] + p2[0]) / 2;
|
| 475 |
traces.push({
|
| 476 |
+
type:'scatter', mode:'lines', x: [p1[0], midX, midX, p2[0]], y: [p1[1], p1[1], p2[1], p2[1]],
|
| 477 |
+
line:{color:'rgba(255,255,255,0.15)', width:2, shape:'spline'}, hoverinfo:'none'
|
|
|
|
|
|
|
|
|
|
| 478 |
});
|
| 479 |
}
|
| 480 |
});
|
|
|
|
| 487 |
const p1 = pos[`${uid}_${n1}`], p2 = pos[`${uid}_${n2}`];
|
| 488 |
if(p1 && p2) {
|
| 489 |
const [col, wd] = springColor(k);
|
| 490 |
+
traces.push({ type:'scatter', mode:'lines', x:[p1[0], p2[0]], y:[p1[1], p2[1]], line:{color:col, width:wd}, hoverinfo:'none' });
|
|
|
|
|
|
|
|
|
|
| 491 |
}
|
| 492 |
});
|
|
|
|
| 493 |
allN.push({ id:`${uid}_A`, x:pos[`${uid}_A`][0], y:pos[`${uid}_A`][1], v:u.a_val, t:'A' });
|
| 494 |
allN.push({ id:`${uid}_B`, x:pos[`${uid}_B`][0], y:pos[`${uid}_B`][1], v:u.b_val, t:'B' });
|
| 495 |
allN.push({ id:`${uid}_C`, x:pos[`${uid}_C`][0], y:pos[`${uid}_C`][1], v:u.c_val, t:'C' });
|
|
|
|
| 496 |
for(let j=1; j<=u.n_upper; j++) {
|
| 497 |
+
allN.push({ id:`${uid}_U${j}`, x:pos[`${uid}_U${j}`][0], y:pos[`${uid}_U${j}`][1], vel:u.nodes[`U${j}`].vel, t:'U' });
|
|
|
|
| 498 |
}
|
| 499 |
for(let j=1; j<=u.n_lower; j++) {
|
| 500 |
+
allN.push({ id:`${uid}_L${j}`, x:pos[`${uid}_L${j}`][0], y:pos[`${uid}_L${j}`][1], vel:u.nodes[`L${j}`].vel, t:'L' });
|
|
|
|
| 501 |
}
|
| 502 |
});
|
| 503 |
|
|
|
|
| 516 |
hoverinfo:'none'
|
| 517 |
});
|
| 518 |
|
| 519 |
+
const xMax = (d.topology.length - 1) * W_SPREAD + (HG_W*1.5);
|
| 520 |
+
const yMax = Math.max(5, (d.topology[0].length/2) * H_SPREAD + 1);
|
|
|
|
|
|
|
|
|
|
| 521 |
|
| 522 |
return { traces, layout: {
|
| 523 |
margin:{l:8,r:8,t:8,b:8}, paper_bgcolor:'transparent', plot_bgcolor:'transparent',
|
| 524 |
+
xaxis:{visible:false, range:[-(HG_W*1.5), xMax]}, yaxis:{visible:false, range:[-yMax, yMax]}, showlegend:false
|
|
|
|
| 525 |
}};
|
| 526 |
}
|
| 527 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
let meshPlotted = false, errPlotted = false, lastLayerKey = '';
|
| 529 |
|
| 530 |
// ── POLL ──────────────────────────────────────────────────────────────────────
|
|
|
|
| 534 |
const d = await r.json();
|
| 535 |
|
| 536 |
syncTopoUI(d);
|
|
|
|
| 537 |
let credBadge = d.credit_mode.replace('_','').slice(0,7).toUpperCase();
|
| 538 |
if (d.reverse_mode) credBadge = 'REVERSE';
|
| 539 |
if (d.individual_train) credBadge = 'INDEPND';
|
|
|
|
| 593 |
});
|
| 594 |
document.getElementById('pane-springs').innerHTML = sh;
|
| 595 |
|
| 596 |
+
document.getElementById('pane-logs').innerHTML = d.logs.map(l => `<div class="py-0.5 border-b border-slate-900/50">${l}</div>`).join('');
|
|
|
|
| 597 |
|
| 598 |
const layerKey = JSON.stringify(d.topology) + d.n_upper + d.n_lower;
|
| 599 |
const { traces, layout } = buildTraces(d);
|
|
|
|
| 606 |
}
|
| 607 |
|
| 608 |
const hist = d.history;
|
| 609 |
+
const eTrace = { type:'scatter', mode:'lines', x: hist.map((_,i)=>i), y: hist, line:{color:'#f97316',width:1.5}, fill:'tozeroy', fillcolor:'rgba(249,115,22,0.07)' };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 610 |
if (!errPlotted) {
|
| 611 |
+
Plotly.newPlot('err-plot',[eTrace],{margin:{l:30,r:6,t:3,b:12}, paper_bgcolor:'transparent', plot_bgcolor:'transparent', xaxis:{visible:false}, yaxis:{color:'#334155', gridcolor:'#0f172a', zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1}, showlegend:false},{displayModeBar:false,responsive:true});
|
| 612 |
errPlotted = true;
|
| 613 |
} else {
|
| 614 |
+
Plotly.react('err-plot',[eTrace],{margin:{l:30,r:6,t:3,b:12}, paper_bgcolor:'transparent', plot_bgcolor:'transparent', xaxis:{visible:false}, yaxis:{color:'#334155', gridcolor:'#0f172a', zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1}, showlegend:false});
|
| 615 |
}
|
| 616 |
|
| 617 |
} catch(e) { }
|