Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Topological Engine Lab</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script> | |
| <style> | |
| body { background: #06090e; color: #cbd5e1; font-family: monospace; overflow: hidden; } | |
| .glass { background: rgba(15,23,42,0.96); border: 1px solid #334155; } | |
| #drawer { transition: transform 0.3s ease-in-out; z-index: 100; } | |
| .drawer-hidden { transform: translateY(100%); } | |
| .panel-title { letter-spacing: 0.18em; text-transform: uppercase; } | |
| </style> | |
| </head> | |
| <body class="flex flex-col h-screen overflow-hidden"> | |
| <header class="glass p-3 flex justify-between items-center z-10"> | |
| <div> | |
| <h1 id="logic-label" class="text-blue-400 font-bold text-xs uppercase">TOPOLOGICAL ENGINE</h1> | |
| <p id="sub-label" class="text-[10px] text-slate-500 mt-1">Ready</p> | |
| </div> | |
| <button onclick="toggleDrawer()" class="bg-blue-600 px-4 py-1 rounded text-white text-xs font-bold"> | |
| ⚙️ SYSTEM DIALS | |
| </button> | |
| </header> | |
| <aside id="drawer" class="fixed inset-x-0 bottom-0 drawer-hidden glass p-6 flex flex-col gap-5 z-50 max-h-[92vh] overflow-y-auto"> | |
| <div class="flex justify-between items-center border-b border-slate-700 pb-2"> | |
| <span class="text-orange-400 font-bold panel-title text-xs">LABORATORY CONTROLS</span> | |
| <button onclick="toggleDrawer()" class="text-slate-400 text-2xl">✕</button> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <section class="bg-slate-900 border border-blue-900 p-3 rounded"> | |
| <h3 class="text-blue-400 font-bold text-[10px] mb-2 panel-title">1. ARCHITECTURE</h3> | |
| <select id="architecture" class="w-full bg-black border border-slate-700 p-2 text-white rounded"> | |
| <option value="additive">Additive</option> | |
| <option value="multiplicative">Multiplicative</option> | |
| <option value="affine">Affine</option> | |
| <option value="bilinear">Bilinear</option> | |
| <option value="gated">Gated</option> | |
| </select> | |
| <select id="coeff_mode" class="w-full mt-2 bg-black border border-slate-700 p-2 text-white rounded"> | |
| <option value="single_k">Single K</option> | |
| <option value="triple_k">Triple K</option> | |
| <option value="per_edge_k">Per Edge K</option> | |
| </select> | |
| <select id="topology" class="w-full mt-2 bg-black border border-slate-700 p-2 text-white rounded"> | |
| <option value="single_cell">Single Cell</option> | |
| <option value="chain">Chain</option> | |
| <option value="mesh">Mesh</option> | |
| </select> | |
| <select id="mode" class="w-full mt-2 bg-black border border-slate-700 p-2 text-white rounded"> | |
| <option value="training">Training</option> | |
| <option value="inference">Inference</option> | |
| </select> | |
| <div class="grid grid-cols-2 gap-2 mt-2"> | |
| <input id="num_cells" type="number" min="1" value="4" class="bg-black border border-slate-700 p-2 text-white rounded" /> | |
| <input id="batch_count" type="number" min="1" value="24" class="bg-black border border-slate-700 p-2 text-white rounded" /> | |
| </div> | |
| <div class="grid grid-cols-3 gap-2 mt-2"> | |
| <input id="learning_rate" type="number" step="0.001" value="0.01" class="bg-black border border-slate-700 p-2 text-white rounded" /> | |
| <input id="coupling" type="number" step="0.001" value="0.05" class="bg-black border border-slate-700 p-2 text-white rounded" /> | |
| <input id="damping" type="number" step="0.001" value="0.12" class="bg-black border border-slate-700 p-2 text-white rounded" /> | |
| </div> | |
| <button onclick="applyConfig()" class="w-full bg-blue-700 mt-3 py-2 font-bold rounded text-white">APPLY PHYSICS</button> | |
| </section> | |
| <section class="bg-slate-900 border border-purple-900 p-3 rounded"> | |
| <h3 class="text-purple-400 font-bold text-[10px] mb-2 panel-title">2. DATASET</h3> | |
| <select id="dataset_family" class="w-full bg-black border border-slate-700 p-2 text-white rounded"> | |
| <option value="housing">Housing</option> | |
| <option value="subtraction">Subtraction</option> | |
| <option value="multiplication">Multiplication</option> | |
| <option value="symbolic">Symbolic</option> | |
| <option value="mixed">Mixed</option> | |
| </select> | |
| <button onclick="previewExample()" class="w-full bg-purple-700 mt-3 py-2 font-bold rounded text-white">GENERATE EXAMPLE</button> | |
| <button onclick="startBatch()" class="w-full bg-indigo-700 mt-2 py-2 font-bold rounded text-white">START BATCH</button> | |
| <div class="mt-3 p-3 rounded border border-slate-700 bg-black/40 text-[11px] space-y-1"> | |
| <div class="text-slate-400 panel-title text-[10px]">EXAMPLE PREVIEW</div> | |
| <div id="example-view" class="text-slate-300">No sample loaded.</div> | |
| </div> | |
| </section> | |
| <section class="bg-slate-900 border border-cyan-900 p-3 rounded"> | |
| <h3 class="text-cyan-400 font-bold text-[10px] mb-2 panel-title">3. CUSTOM INPUTS</h3> | |
| <div class="grid grid-cols-3 gap-2"> | |
| <input id="custom_a" type="number" step="0.01" value="7" class="bg-black border border-slate-700 p-2 text-white rounded" placeholder="A" /> | |
| <input id="custom_b" type="number" step="0.01" value="8" class="bg-black border border-slate-700 p-2 text-white rounded" placeholder="B" /> | |
| <input id="custom_c" type="number" step="0.01" class="bg-black border border-slate-700 p-2 text-white rounded" placeholder="C target" /> | |
| </div> | |
| <button onclick="runCustom()" class="w-full bg-cyan-700 mt-3 py-2 font-bold rounded text-white">RUN CUSTOM</button> | |
| </section> | |
| <section class="bg-slate-900 border border-red-900 p-3 rounded flex flex-col justify-between"> | |
| <h3 class="text-red-400 font-bold text-[10px] mb-2 panel-title">4. CONTROL</h3> | |
| <button onclick="halt()" class="w-full border border-red-500 text-red-400 py-3 rounded font-bold">HALT ENGINE</button> | |
| <div class="mt-3 text-[11px] text-slate-500"> | |
| Batch count and configuration are independent. The same data family can be tested under different physics. | |
| </div> | |
| </section> | |
| </div> | |
| </aside> | |
| <main class="flex-grow flex flex-col min-h-0"> | |
| <div class="flex flex-col xl:flex-row flex-grow min-h-0"> | |
| <div class="flex-1 min-h-[320px]" id="plot"></div> | |
| <div class="xl:w-[34rem] w-full glass border-l border-slate-800 p-4 overflow-y-auto"> | |
| <div class="flex justify-between items-center mb-3 border-b border-slate-800 pb-2"> | |
| <div> | |
| <div class="text-blue-300 text-[10px] font-bold panel-title">LIVE STATUS</div> | |
| <div id="status-line" class="text-[11px] text-slate-400 mt-1">Waiting...</div> | |
| </div> | |
| <div id="error-val" class="text-sm font-bold text-red-400">ERR: 0.000</div> | |
| </div> | |
| <div id="summary-box" class="grid grid-cols-2 gap-2 text-[11px] mb-4"></div> | |
| <div class="mb-4"> | |
| <div class="text-[10px] text-slate-500 panel-title mb-2">CELL STATE</div> | |
| <div id="cells-ui" class="space-y-2 text-[11px] font-mono"></div> | |
| </div> | |
| <div class="mb-4"> | |
| <div class="text-[10px] text-slate-500 panel-title mb-2">LOSS HISTORY</div> | |
| <div id="loss-plot" class="h-56"></div> | |
| </div> | |
| <div> | |
| <div class="text-[10px] text-slate-500 panel-title mb-2">LOGS</div> | |
| <div id="logs" class="mt-auto pt-2 border-t border-slate-800 text-[10px] text-slate-500 h-44 overflow-y-auto space-y-1"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| const state = { | |
| plotted: false, | |
| lossPlotted: false, | |
| example: null, | |
| historyLoss: [], | |
| historyError: [] | |
| }; | |
| function toggleDrawer() { | |
| document.getElementById('drawer').classList.toggle('drawer-hidden'); | |
| } | |
| function readConfig() { | |
| return { | |
| architecture: document.getElementById('architecture').value, | |
| coeff_mode: document.getElementById('coeff_mode').value, | |
| topology: document.getElementById('topology').value, | |
| dataset_family: document.getElementById('dataset_family').value, | |
| mode: document.getElementById('mode').value, | |
| num_cells: parseInt(document.getElementById('num_cells').value || '1', 10), | |
| batch_size: parseInt(document.getElementById('batch_count').value || '24', 10), | |
| learning_rate: parseFloat(document.getElementById('learning_rate').value || '0.01'), | |
| coupling: parseFloat(document.getElementById('coupling').value || '0.05'), | |
| damping: parseFloat(document.getElementById('damping').value || '0.12'), | |
| }; | |
| } | |
| function setHeaderFromConfig(cfg) { | |
| document.getElementById('logic-label').innerText = | |
| `TOPOLOGICAL ENGINE | ${cfg.architecture.toUpperCase()} | ${cfg.coeff_mode.toUpperCase()} | ${cfg.topology.toUpperCase()}`; | |
| document.getElementById('sub-label').innerText = | |
| `${cfg.dataset_family.toUpperCase()} / ${cfg.mode.toUpperCase()}`; | |
| } | |
| async function applyConfig() { | |
| const cfg = readConfig(); | |
| setHeaderFromConfig(cfg); | |
| await fetch('/config', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify(cfg) | |
| }); | |
| await previewExample(false); | |
| toggleDrawer(); | |
| } | |
| async function previewExample(fillInputs = true) { | |
| const cfg = readConfig(); | |
| const res = await fetch('/example', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify(cfg) | |
| }); | |
| const sample = await res.json(); | |
| state.example = sample; | |
| const ex = document.getElementById('example-view'); | |
| ex.innerHTML = ` | |
| <div class="text-slate-200">a: <b>${sample.a.toFixed(3)}</b></div> | |
| <div class="text-slate-200">b: <b>${sample.b.toFixed(3)}</b></div> | |
| <div class="text-slate-200">c: <b>${sample.c.toFixed(3)}</b></div> | |
| <div class="text-slate-400">label: ${sample.label}</div> | |
| `; | |
| if (fillInputs) { | |
| document.getElementById('custom_a').value = sample.a.toFixed(3); | |
| document.getElementById('custom_b').value = sample.b.toFixed(3); | |
| document.getElementById('custom_c').value = sample.c.toFixed(3); | |
| } | |
| } | |
| async function startBatch() { | |
| const cfg = readConfig(); | |
| await fetch('/config', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify(cfg) | |
| }); | |
| await fetch('/generate_batch', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ count: cfg.batch_size }) | |
| }); | |
| toggleDrawer(); | |
| } | |
| async function runCustom() { | |
| const a = document.getElementById('custom_a').value; | |
| const b = document.getElementById('custom_b').value; | |
| const c = document.getElementById('custom_c').value; | |
| await fetch('/test_custom', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ a, b, c: c === '' ? null : c }) | |
| }); | |
| toggleDrawer(); | |
| } | |
| async function halt() { | |
| await fetch('/halt', { method: 'POST' }); | |
| toggleDrawer(); | |
| } | |
| function fmt(n) { | |
| if (n === null || n === undefined || Number.isNaN(n)) return '—'; | |
| return Number(n).toFixed(4); | |
| } | |
| function renderSummary(d) { | |
| const cfg = d.config; | |
| const sample = d.current_sample || {}; | |
| const last = d.last_sample || sample; | |
| const items = [ | |
| ['Mode', cfg.mode], | |
| ['Arch', cfg.architecture], | |
| ['Coeffs', cfg.coeff_mode], | |
| ['Topology', cfg.topology], | |
| ['Dataset', cfg.dataset_family], | |
| ['Batch left', d.batch_remaining], | |
| ['Iteration', d.iteration], | |
| ['Sample', last.label || '—'], | |
| ['Error', fmt(d.current_error)], | |
| ['Loss', fmt(d.current_loss)], | |
| ['A', sample.a !== undefined ? fmt(sample.a) : '—'], | |
| ['B', sample.b !== undefined ? fmt(sample.b) : '—'], | |
| ]; | |
| document.getElementById('summary-box').innerHTML = items.map(([k, v]) => ` | |
| <div class="bg-black/40 border border-slate-800 rounded p-2"> | |
| <div class="text-[9px] text-slate-500 panel-title">${k}</div> | |
| <div class="text-[12px] text-slate-100 mt-1">${v}</div> | |
| </div> | |
| `).join(''); | |
| } | |
| function renderCells(d) { | |
| const cells = d.cells || []; | |
| document.getElementById('cells-ui').innerHTML = cells.map(cell => ` | |
| <div class="border border-slate-800 rounded p-3 bg-black/30"> | |
| <div class="flex justify-between items-center mb-1"> | |
| <div class="text-sky-300 font-bold">CELL ${cell.id} ${cell.anchored ? '[ANCHORED]' : '[DRIFTING]'}</div> | |
| <div class="text-slate-400">label: ${cell.label || '—'}</div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-x-3 gap-y-1"> | |
| <div>A: <span class="text-white">${fmt(cell.a)}</span></div> | |
| <div>B: <span class="text-white">${fmt(cell.b)}</span></div> | |
| <div>C: <span class="text-white">${fmt(cell.c)}</span></div> | |
| <div>T: <span class="text-white">${cell.target !== null ? fmt(cell.target) : '—'}</span></div> | |
| <div>P: <span class="text-white">${fmt(cell.prediction)}</span></div> | |
| <div>E: <span class="text-white">${fmt(cell.error)}</span></div> | |
| <div>Energy: <span class="text-white">${fmt(cell.energy)}</span></div> | |
| <div>Force: <span class="text-white">${fmt(cell.force)}</span></div> | |
| </div> | |
| <div class="mt-2 flex gap-3 text-[10px] text-purple-300"> | |
| <div>k: ${fmt(cell.k)}</div> | |
| <div>ka: ${fmt(cell.ka)}</div> | |
| <div>kb: ${fmt(cell.kb)}</div> | |
| <div>kc: ${fmt(cell.kc)}</div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| function renderLogs(d) { | |
| document.getElementById('logs').innerHTML = (d.logs || []).map(l => `<div>${l}</div>`).join(''); | |
| } | |
| function renderTopPlot(d) { | |
| const cells = d.cells || []; | |
| const xsPred = cells.map(c => c.prediction); | |
| const ys = cells.map(c => c.id); | |
| const zsErr = cells.map(c => c.error); | |
| const labels = cells.map(c => `cell ${c.id}`); | |
| const xsTarget = cells.map(c => c.target !== null ? c.target : null).filter(v => v !== null); | |
| const ysTarget = cells.map(c => c.target !== null ? c.id : null).filter(v => v !== null); | |
| const zsTarget = cells.map(() => 0).slice(0, xsTarget.length); | |
| const trace1 = { | |
| type: 'scatter3d', | |
| mode: 'lines+markers+text', | |
| x: xsPred, | |
| y: ys, | |
| z: zsErr, | |
| text: labels, | |
| textposition: 'top center', | |
| marker: { size: 7 }, | |
| line: { width: 3 } | |
| }; | |
| const trace2 = { | |
| type: 'scatter3d', | |
| mode: 'markers', | |
| x: xsTarget, | |
| y: ysTarget, | |
| z: zsTarget, | |
| marker: { size: 5, symbol: 'circle' } | |
| }; | |
| const layout = { | |
| margin: { l: 0, r: 0, t: 0, b: 0 }, | |
| paper_bgcolor: 'transparent', | |
| plot_bgcolor: 'transparent', | |
| scene: { | |
| xaxis: { title: 'Prediction / C', color: '#475569', gridcolor: '#1e293b' }, | |
| yaxis: { title: 'Cell index', color: '#475569', gridcolor: '#1e293b' }, | |
| zaxis: { title: 'Error', color: '#475569', gridcolor: '#1e293b' }, | |
| camera: { eye: { x: 0.9, y: -1.8, z: 0.8 } } | |
| }, | |
| showlegend: false | |
| }; | |
| if (!window.topPlotted) { | |
| Plotly.newPlot('plot', [trace1, trace2], layout, { displayModeBar: false, responsive: true }); | |
| window.topPlotted = true; | |
| } else { | |
| Plotly.react('plot', [trace1, trace2], layout, { displayModeBar: false, responsive: true }); | |
| } | |
| } | |
| function renderLossPlot(d) { | |
| const loss = d.loss_history || []; | |
| const err = d.error_history || []; | |
| const x = loss.map((_, i) => i); | |
| const lossTrace = { | |
| type: 'scatter', | |
| mode: 'lines', | |
| x: x, | |
| y: loss, | |
| name: 'loss' | |
| }; | |
| const errTrace = { | |
| type: 'scatter', | |
| mode: 'lines', | |
| x: x, | |
| y: err, | |
| name: 'error' | |
| }; | |
| const layout = { | |
| margin: { l: 35, r: 10, t: 10, b: 30 }, | |
| paper_bgcolor: 'transparent', | |
| plot_bgcolor: 'transparent', | |
| xaxis: { color: '#94a3b8', gridcolor: '#1e293b' }, | |
| yaxis: { color: '#94a3b8', gridcolor: '#1e293b' }, | |
| showlegend: true, | |
| legend: { orientation: 'h' } | |
| }; | |
| if (!window.lossPlotted) { | |
| Plotly.newPlot('loss-plot', [lossTrace, errTrace], layout, { displayModeBar: false, responsive: true }); | |
| window.lossPlotted = true; | |
| } else { | |
| Plotly.react('loss-plot', [lossTrace, errTrace], layout, { displayModeBar: false, responsive: true }); | |
| } | |
| } | |
| async function tick() { | |
| try { | |
| const res = await fetch('/state'); | |
| const d = await res.json(); | |
| document.getElementById('error-val').innerText = 'ERR: ' + fmt(d.current_error); | |
| document.getElementById('error-val').className = | |
| Math.abs(d.current_error) < 0.05 ? 'text-green-400 font-bold' : 'text-red-400 font-bold'; | |
| document.getElementById('status-line').innerText = | |
| d.running ? 'Engine running...' : 'Engine idle.'; | |
| renderSummary(d); | |
| renderCells(d); | |
| renderLogs(d); | |
| renderTopPlot(d); | |
| renderLossPlot(d); | |
| } catch (e) { | |
| document.getElementById('status-line').innerText = 'Disconnected.'; | |
| } | |
| } | |
| // initial example + first config sync | |
| (async () => { | |
| const cfg = readConfig(); | |
| setHeaderFromConfig(cfg); | |
| await previewExample(false); | |
| setInterval(tick, 180); | |
| })(); | |
| </script> | |
| </body> | |
| </html> |