a11oy / static /3d /surfaces /kan.js
betterwithage's picture
deploy(hf): sync szl-holdings/a11oy@main derived COPY set
953184a verified
Raw History Blame
19.4 kB
// SPDX-License-Identifier: Apache-2.0
// © 2026 Lutar, Stephen P. — SZL Holdings · ORCID 0009-0001-0110-4173 · Doctrine v11
//
// surfaces/kan.js — KOLMOGOROV-ARNOLD NETWORK (KAN) organ for the holographic
// frontier ring. Renders the tiny fitted KAN as a node-edge lattice where each
// EDGE is drawn as its own learned spline curve (not a flat scalar-weight
// line) — the defining visual difference from an MLP. A HUD shows param
// counts (KAN vs MLP baseline), final fit error, and the symbolic-distillation
// snap for the selected edge. Honesty label "MODELED" is read VERBATIM from
// the JSON and displayed as-is; it is never upgraded.
//
// Surface export shape (mirrors specdecode.js / testtime.js exactly):
// export default { id, title, endpoints, mount(ctx), unmount() }
// ctx = { stage, container, live, label, THREE, szl3d }
//
// DATA SHOWN (all from live endpoint):
// task.formula, kan.n_params, kan.final_mse, kan.loss_curve[],
// mlp_baseline.n_params, mlp_baseline.final_mse, comparison.kan_fewer_params,
// edge_activation_shapes[] ({edge, curve:[[u,phi(u)]...]}),
// symbolic_distillation[] ({edge, symbolic_form, coefficient, residual_sse})
//
// LEADERS ADOPTED & CITED (clean-room; NOT claimed as SZL's own):
// KAN: Kolmogorov-Arnold Networks:
// Liu, Wang, Vaidya, Ruehle, Halverson, Soljacic, Hou & Tegmark 2024, arXiv:2404.19756
// https://arxiv.org/abs/2404.19756
// pykan reference implementation (MIT, REFERENCE ONLY — no code copied):
// https://github.com/KindXiaoming/pykan
//
// HONESTY LABELS: MODELED (small, from-scratch, deterministic illustrative KAN;
// NOT pykan; NEVER-CLAIMED-AS a production/large-scale KAN). Read verbatim
// from JSON; never upgraded here.
// COLOURS: lattice-blue 0x5b8dee (KAN edges/lattice), violet-blue 0x8a6bff
// (hidden-layer nodes), proof-teal 0x3af4c8 (accepted/best-fit accents, HUD).
// Greys for degraded/MLP-baseline contrast. Purple BANNED as UI/background.
// 0 RUNTIME CDN. Vendored three.js r170 via ctx.THREE (page importmap).
// DOCTRINE v11: degrades gracefully (grey) on 404/error; honesty label still shown.
// Nothing here is in the locked-8. Lambda stays Conjecture 1. Trust never 100%.
import { createShowcase } from "./_showcase.js";
const ID = "kan";
const TITLE = "Kolmogorov-Arnold Network · Per-Edge Splines (live)";
// Endpoint is served SAME-ORIGIN by the flagship (szl_kan.py), a deterministic
// from-scratch KAN fit. Same-origin avoids CORS and cross-Space fault coupling.
const EP = "/api/a11oy/v1/kan/fit?seed=42&hidden=3&knots=7&epochs=120";
// data-viz hues — purple BANNED
const C_EDGE = 0x5b8dee; // lattice-blue (KAN edge-spline curves)
const C_NODE = 0x8a6bff; // violet-blue (hidden-layer nodes)
const C_ACCENT = 0x3af4c8; // proof-teal (best-fit / output node / HUD accent)
const C_MLP = 0x5a6570; // grey (MLP-baseline contrast lattice)
const C_DIM = 0x42505d; // grey (degraded / no-live-data)
const C_GRID = 0x1b3a44; // floor / link colour
// lattice layout geometry: 2 inputs -> H hidden -> 1 output, laid out along X
const LAYER_GAP = 4.2; // world-units between layers along X
const NODE_GAP = 1.6; // world-units between nodes within a layer (Y)
const MAX_HIDDEN = 8; // pre-allocated hidden-node capacity (perf cap)
const CURVE_SEGS = 23; // spline sample segments per edge (matches server's 24 samples)
let _stage = null, _THREE = null, _ctx = null, _group = null, _show = null;
let _frameReg = false, _polls = [], _el = {}, _badge = null;
let _markerBaseScale = 1.0;
// geometry handles
let _floor = null;
let _inputNodes = []; // Array<THREE.Mesh> — x, y input nodes
let _hiddenNodes = []; // Array<THREE.Mesh> — hidden-layer nodes
let _outputNode = null; // THREE.Mesh — single output node
let _edgeCurves1 = []; // Array<Array<THREE.Line>> — [inputIdx][hiddenIdx] spline curve lines
let _edgeCurves2 = []; // Array<THREE.Line> — [hiddenIdx] -> output spline curve lines
let _marker = null; // THREE.Mesh — HUD pulsing "fit quality" marker
// live state
const S = {
label: null,
formula: null, // task.formula
kanParams: null, // kan.n_params
kanMse: null, // kan.final_mse
kanLoss: null, // kan.loss_curve[]
mlpParams: null, // mlp_baseline.n_params
mlpMse: null, // mlp_baseline.final_mse
fewerParams: null, // comparison.kan_fewer_params (bool)
edgeShapes: null, // edge_activation_shapes[]
distilled: null, // symbolic_distillation[]
state: "init",
};
// =============================================================================
// mount(ctx)
// =============================================================================
export function mount(ctx) {
_ctx = ctx; _stage = ctx.stage; _THREE = ctx.THREE;
_group = new _THREE.Group();
_stage.scene.add(_group);
_stage.camera.position.set(3, 6, 16);
try { if (_stage.controls && _stage.controls.target) { _stage.controls.target.set(4, 1.5, 0); _stage.controls.update(); } } catch (_) {}
try { _stage.setBloom(true); } catch (_) {}
_buildFloor();
_buildLattice();
_buildMarker();
if (!_frameReg) { _stage.onFrame(_onFrame); _frameReg = true; }
_badge = ctx.live.createBadge();
_polls.push(ctx.live.poll(EP, 6000, _onKan, { badge: _badge, onState: (m) => { S.state = m.state; _paintOverlay(); } }));
_buildOverlay();
return { id: ID, started: true };
}
// =============================================================================
// builders
// =============================================================================
function _buildFloor() {
const THREE = _THREE;
const grid = new THREE.GridHelper(40, 40, C_GRID, 0x0f2027);
grid.material.opacity = 0.18; grid.material.transparent = true; grid.position.y = -0.01;
_group.add(grid);
_floor = grid;
}
// Pre-allocate input/hidden/output node meshes and edge-spline curve lines.
// We toggle visibility / geometry points / color in-place as live data
// arrives (no per-poll geometry churn beyond BufferGeometry point updates).
function _buildLattice() {
const THREE = _THREE;
const nodeGeo = new THREE.SphereGeometry(0.22, 16, 16);
// input nodes (x, y) at layer x=0
for (let i = 0; i < 2; i++) {
const mesh = new THREE.Mesh(
nodeGeo,
new THREE.MeshStandardMaterial({ color: C_NODE, emissive: C_NODE, emissiveIntensity: 0.3, transparent: true, opacity: 0.9 }),
);
mesh.position.set(0, (i - 0.5) * NODE_GAP * 2, 0);
_group.add(mesh);
_inputNodes.push(mesh);
}
// hidden nodes at layer x=LAYER_GAP (pre-allocated to MAX_HIDDEN, toggled visible)
for (let h = 0; h < MAX_HIDDEN; h++) {
const mesh = new THREE.Mesh(
nodeGeo,
new THREE.MeshStandardMaterial({ color: C_NODE, emissive: C_NODE, emissiveIntensity: 0.3, transparent: true, opacity: 0.9 }),
);
mesh.position.set(LAYER_GAP, (h - (MAX_HIDDEN - 1) / 2) * NODE_GAP, 0);
mesh.visible = false;
_group.add(mesh);
_hiddenNodes.push(mesh);
}
// output node at layer x=2*LAYER_GAP
_outputNode = new THREE.Mesh(
nodeGeo,
new THREE.MeshStandardMaterial({ color: C_ACCENT, emissive: C_ACCENT, emissiveIntensity: 0.4, transparent: true, opacity: 0.95 }),
);
_outputNode.position.set(2 * LAYER_GAP, 0, 0);
_group.add(_outputNode);
// layer-1 edge-spline curves: [inputIdx][hiddenIdx], each a THREE.Line whose
// points trace phi(u) as a curve from the input node to the hidden node,
// bowed in Z by the spline's own shape (this is the KAN-vs-MLP visual tell:
// a curved, individually-shaped line per edge instead of a straight one).
const curveMat = () => new THREE.LineBasicMaterial({ color: C_EDGE, transparent: true, opacity: 0.55 });
for (let ii = 0; ii < 2; ii++) {
const row = [];
for (let h = 0; h < MAX_HIDDEN; h++) {
const pts = new Array(CURVE_SEGS + 1).fill(0).map(() => new THREE.Vector3(0, 0, 0));
const geo = new THREE.BufferGeometry().setFromPoints(pts);
const line = new THREE.Line(geo, curveMat());
line.visible = false;
_group.add(line);
row.push(line);
}
_edgeCurves1.push(row);
}
// layer-2 edge-spline curves: [hiddenIdx] -> output
for (let h = 0; h < MAX_HIDDEN; h++) {
const pts = new Array(CURVE_SEGS + 1).fill(0).map(() => new THREE.Vector3(0, 0, 0));
const geo = new THREE.BufferGeometry().setFromPoints(pts);
const line = new THREE.Line(geo, curveMat());
line.visible = false;
_group.add(line);
_edgeCurves2.push(line);
}
}
function _buildMarker() {
const THREE = _THREE;
_marker = new THREE.Mesh(
new THREE.IcosahedronGeometry(0.26, 1),
new THREE.MeshStandardMaterial({ color: C_ACCENT, emissive: C_ACCENT, emissiveIntensity: 0.5, wireframe: true, transparent: true, opacity: 0.85 }),
);
_marker.position.set(2 * LAYER_GAP, -1.4, 0);
_group.add(_marker);
}
// =============================================================================
// live data handler
// =============================================================================
function _onKan(j) {
const body = (j && j.payload) ? j.payload : (j || {});
// read honesty label VERBATIM — never upgrade
S.label = (body.label || "MODELED").toUpperCase();
S.formula = (body.task && body.task.formula) || null;
S.kanParams = (body.kan && typeof body.kan.n_params === "number") ? body.kan.n_params : null;
S.kanMse = (body.kan && typeof body.kan.final_mse === "number") ? body.kan.final_mse : null;
S.kanLoss = (body.kan && Array.isArray(body.kan.loss_curve)) ? body.kan.loss_curve : null;
S.mlpParams = (body.mlp_baseline && typeof body.mlp_baseline.n_params === "number") ? body.mlp_baseline.n_params : null;
S.mlpMse = (body.mlp_baseline && typeof body.mlp_baseline.final_mse === "number") ? body.mlp_baseline.final_mse : null;
S.fewerParams = (body.comparison && typeof body.comparison.kan_fewer_params === "boolean") ? body.comparison.kan_fewer_params : null;
S.edgeShapes = Array.isArray(body.edge_activation_shapes) ? body.edge_activation_shapes : null;
S.distilled = Array.isArray(body.symbolic_distillation) ? body.symbolic_distillation : null;
_updateLattice();
_paintOverlay();
}
// =============================================================================
// geometry updater — drives the lattice curves from live edge-shape data
// =============================================================================
function _updateLattice() {
const THREE = _THREE;
const live = S.state === "live";
const shapes = live && S.edgeShapes ? S.edgeShapes : [];
// index shapes by edge name for quick lookup ("x->h0", "y->h2", "h1->out")
const byName = {};
for (const s of shapes) if (s && s.edge) byName[s.edge] = s.curve;
// Hidden-count derivation: count distinct "hN->out" entries.
let hCount = 0;
for (const s of shapes) { if (s && /^h\d+->out$/.test(s.edge)) hCount++; }
hCount = live ? Math.min(MAX_HIDDEN, Math.max(hCount, 0)) : 0;
for (let h = 0; h < MAX_HIDDEN; h++) {
_hiddenNodes[h].visible = h < hCount;
}
const inputNames = ["x", "y"];
for (let ii = 0; ii < 2; ii++) {
for (let h = 0; h < MAX_HIDDEN; h++) {
const line = _edgeCurves1[ii][h];
const curve = h < hCount ? byName[`${inputNames[ii]}->h${h}`] : null;
if (!curve) { line.visible = false; continue; }
line.visible = true;
_paintCurve(line, curve, _inputNodes[ii].position, _hiddenNodes[h].position, C_EDGE);
}
}
for (let h = 0; h < MAX_HIDDEN; h++) {
const line = _edgeCurves2[h];
const curve = h < hCount ? byName[`h${h}->out`] : null;
if (!curve) { line.visible = false; continue; }
line.visible = true;
_paintCurve(line, curve, _hiddenNodes[h].position, _outputNode.position, C_ACCENT);
}
// node/marker degrade to grey when not live
const nodeColor = live ? C_NODE : C_DIM;
for (const n of _inputNodes) { n.material.color.setHex(nodeColor); n.material.emissive.setHex(nodeColor); }
for (const n of _hiddenNodes) { n.material.color.setHex(nodeColor); n.material.emissive.setHex(nodeColor); }
const outColor = live ? C_ACCENT : C_DIM;
_outputNode.material.color.setHex(outColor);
_outputNode.material.emissive.setHex(outColor);
if (_marker) {
if (live && S.kanMse != null) {
_marker.material.color.setHex(C_ACCENT);
_marker.material.emissive.setHex(C_ACCENT);
_marker.material.opacity = 0.85;
// marker size inversely proportional to fit error (tighter fit -> bigger pulse)
_markerBaseScale = Math.max(0.6, Math.min(1.8, 1.0 / (0.15 + S.kanMse)));
} else {
_marker.material.color.setHex(C_DIM);
_marker.material.emissive.setHex(C_DIM);
_marker.material.opacity = 0.3;
_markerBaseScale = 1.0;
}
}
}
// Paint a spline curve's sampled (u, phi(u)) points as a 3D line running from
// nodeFrom to nodeTo, bowing in Z proportional to phi(u) (this is what makes
// each KAN edge visually distinct — an MLP's edges would all be straight).
function _paintCurve(line, curveData, fromPos, toPos, colorHex) {
const THREE = _THREE;
const n = curveData.length;
const pos = line.geometry.attributes.position;
const count = Math.min(pos.count, n);
// normalize phi(u) range for a bounded visual bow
let maxAbs = 1e-6;
for (let i = 0; i < n; i++) maxAbs = Math.max(maxAbs, Math.abs(curveData[i][1]));
for (let i = 0; i < count; i++) {
const t = i / (count - 1);
const x = fromPos.x + (toPos.x - fromPos.x) * t;
const y = fromPos.y + (toPos.y - fromPos.y) * t;
const phi = curveData[Math.min(i, n - 1)][1];
const z = (phi / maxAbs) * 0.9; // bow amplitude capped at ~0.9 world units
pos.setXYZ(i, x, y, z);
}
pos.needsUpdate = true;
line.geometry.computeBoundingSphere();
line.material.color.setHex(colorHex);
}
// =============================================================================
// per-frame animation
// =============================================================================
function _onFrame() {
const t = performance.now();
if (_group) _group.rotation.y = Math.sin(t * 0.00008) * 0.1;
if (_marker) {
_marker.rotation.y += 0.02;
_marker.rotation.x += 0.01;
const pulse = 1.0 + 0.12 * Math.sin(t * 0.0035);
_marker.scale.setScalar(_markerBaseScale * pulse);
}
}
// =============================================================================
// overlay
// =============================================================================
function _buildOverlay() {
_show = createShowcase(_ctx, {
id: ID, title: TITLE, accent: "#5b8dee",
badge: _badge,
chips: [{ label: "MODELED", text: "kolmogorov-arnold network", name: "hl" }],
legend: ["MODELED"],
description:
'A <b>Kolmogorov-Arnold Network</b> puts a learnable curve (spline) on every ' +
'<b>edge</b> instead of a single scalar weight; nodes just sum their inputs. ' +
'Fitted here on the toy task <code>f(x,y)=exp(sin(\u03c0x)+y\u00b2)</code>, each curved ' +
'line below is one edge\u2019s own learned shape. Honesty label <b>MODELED</b> ' +
'(small from-scratch fit; NOT pykan). 0 runtime CDN.',
citations:
"Liu et al. 2024 arXiv:2404.19756 (KAN) \u00b7 pykan github.com/KindXiaoming/pykan (reference only). MODELED \u00b7 not claimed-as.",
plain: { html: _plainHtml },
});
_el["kan-formula"] = _show.addField("target f(x,y)");
_el["kan-params"] = _show.addField("KAN params \u2014 MODELED");
_el["kan-mse"] = _show.addField("KAN final MSE");
_el["kan-mlpparams"] = _show.addField("MLP-baseline params");
_el["kan-mlpmse"] = _show.addField("MLP-baseline final MSE");
_el["kan-fewer"] = _show.addField("KAN fewer params?");
_el["kan-label"] = _show.addField("honesty label");
_paintOverlay();
}
function _plainHtml() {
const kp = S.kanParams != null ? String(S.kanParams) : "loading\u2026";
const mp = S.mlpParams != null ? String(S.mlpParams) : "loading\u2026";
const km = S.kanMse != null ? S.kanMse.toFixed(4) : "loading\u2026";
return (
"<b>What this means:</b> Most neural networks learn a number (a \u201cweight\u201d) for " +
"each connection. A Kolmogorov-Arnold Network instead learns a whole <b>curve</b> for " +
"each connection \u2014 you can see the exact shape of every one of those curves above. " +
"That makes the network easier to read (you can often turn a curve back into a simple " +
"formula \u2014 the \u201csymbolic distillation\u201d step) and, per the original paper, can " +
"match accuracy with fewer connections at larger scale. Here it has <b>" + kp + "</b> " +
"learned numbers vs a same-size ordinary network's <b>" + mp + "</b>, reaching a fit " +
"error of <b>" + km + "</b> on the toy formula shown. This view is a small, from-scratch, " +
"<b>MODELED</b> demonstration \u2014 not the original pykan library or a large-scale KAN.");
}
function _tok(s) {
if (s === "live") return null;
if (s === "missing") return "NO-LIVE-DATA";
if (s === "degraded") return "DEGRADED";
if (s === "error") return "OFFLINE";
return "\u2026";
}
function fx(v, d) { return typeof v === "number" ? v.toFixed(d) : "\u2014"; }
function _set(id, v) { if (_el[id]) _el[id].textContent = v; }
function _paintOverlay() {
const t = _tok(S.state);
_set("kan-formula", t || (S.formula || "\u2014"));
_set("kan-params", t || (S.kanParams != null ? String(S.kanParams) : "\u2014"));
_set("kan-mse", t || fx(S.kanMse, 5));
_set("kan-mlpparams", t || (S.mlpParams != null ? String(S.mlpParams) : "\u2014"));
_set("kan-mlpmse", t || fx(S.mlpMse, 5));
_set("kan-fewer", t || (S.fewerParams === true ? "yes" : S.fewerParams === false ? "no" : "\u2014"));
// honesty label verbatim — never upgraded
_set("kan-label", t || (S.label || "MODELED"));
if (_show) { _show.setChip("hl", S.label || "MODELED", { text: "kolmogorov-arnold network" }); _show.refreshPlain(); }
}
// =============================================================================
// unmount — clean up everything; must not affect other organs
// =============================================================================
export function unmount() {
_polls.forEach((p) => { try { p.stop(); } catch (_) {} }); _polls = [];
try { if (_show) _show.destroy(); } catch (_) {}
try {
if (_group && _stage) {
_group.traverse((o) => {
if (o.geometry && o.geometry.dispose) o.geometry.dispose();
if (o.material) {
const ms = Array.isArray(o.material) ? o.material : [o.material];
ms.forEach((m) => { if (m.dispose) m.dispose(); });
}
});
_stage.scene.remove(_group);
}
} catch (_) {}
_group = _show = null;
_floor = null; _inputNodes = []; _hiddenNodes = []; _outputNode = null;
_edgeCurves1 = []; _edgeCurves2 = []; _marker = null;
_el = {}; _badge = null; _frameReg = false; _markerBaseScale = 1.0;
_stage = _THREE = _ctx = null;
S.label = S.formula = S.kanParams = S.kanMse = S.kanLoss = null;
S.mlpParams = S.mlpMse = S.fewerParams = S.edgeShapes = S.distilled = null;
S.state = "init";
}
export default { id: ID, title: TITLE, endpoints: [EP], mount, unmount };