feat(framework): add Periodic Table page with YAML source of truth

- periodic-table/table.yml is now the canonical source for 90 elements
  and 53 compounds, validated against table.schema.json on every build.
- build-html.mjs regenerates the standalone index.html idempotently;
  sentinel comments mark the data sections so CSS and render JS are
  preserved across edits.
- sync-periodic-table.mjs regenerates the StaffML React data file as a
  prebuild/predev hook in interviews/staffml; the generated TS file
  carries an @generated header.
- validate.mjs catches cell collisions, broken bonds, undeclared symbol
  collisions, and unresolved formula references at edit time.
- new /framework route in StaffML renders the table interactively with
  search, block filter, element detail modal, and bidirectional cross-
  references between elements and Molecular ML compounds.
- dark mode is the site-wide default; CSP allows 'unsafe-eval' in dev so
  Next.js HMR can run without blocking client JS hydration.
- Makefile + package.json scripts let you run \`make all\` (or just
  \`npm run dev\`) to keep both consumers in sync from the YAML.
This commit is contained in:
Vijay Janapa Reddi
2026-04-07 08:52:43 -04:00
parent 48ab1b58d1
commit c5f90022b4
20 changed files with 4229 additions and 53 deletions

View File

@@ -22,11 +22,13 @@
"devDependencies": {
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.27",
"js-yaml": "^4.1.1",
"jsdom": "^29.0.1",
"postcss": "^8.5.8",
"tailwindcss": "^3.4.19",
@@ -1513,6 +1515,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.19.37",
"dev": true,
@@ -1734,6 +1743,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0"
},
"node_modules/aria-query": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
@@ -2372,6 +2388,19 @@
"license": "MIT",
"peer": true
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsdom": {
"version": "29.0.1",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz",

View File

@@ -3,12 +3,15 @@
"version": "0.0.1-dev",
"private": true,
"scripts": {
"predev": "node scripts/sync-periodic-table.mjs",
"dev": "next dev",
"prebuild": "node scripts/sync-periodic-table.mjs",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "vitest run",
"test:watch": "vitest"
"test:watch": "vitest",
"sync:periodic-table": "node scripts/sync-periodic-table.mjs"
},
"dependencies": {
"@react-sigma/core": "^5.0.6",
@@ -28,11 +31,13 @@
"devDependencies": {
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.27",
"js-yaml": "^4.1.1",
"jsdom": "^29.0.1",
"postcss": "^8.5.8",
"tailwindcss": "^3.4.19",

View File

@@ -0,0 +1,286 @@
#!/usr/bin/env node
/**
* Sync periodic-table/table.yml -> interviews/staffml/src/data/periodicTable.ts
*
* This is a one-way generator. Edit the YAML, never edit the .ts file
* (it has a @generated header to remind you). The script is wired into
* the StaffML package.json as a `predev` and `prebuild` hook so the
* data file is always fresh before the dev server or production build runs.
*
* Usage: npm run sync:periodic-table (from interviews/staffml)
* node interviews/staffml/scripts/sync-periodic-table.mjs (from anywhere)
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import yaml from "js-yaml";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO = path.resolve(__dirname, "..", "..", "..");
const YAML_PATH = path.join(REPO, "periodic-table", "table.yml");
const TS_PATH = path.join(REPO, "interviews", "staffml", "src", "data", "periodicTable.ts");
// ── 1. Load and validate ────────────────────────────────────────────────
const doc = yaml.load(fs.readFileSync(YAML_PATH, "utf8"));
validate(doc);
const KNOWN_SYMS = new Set(doc.elements.map((e) => e.sym));
// ── 2. Parse every compound formula into a token list ──────────────────
// This happens at sync time so the runtime React renderer stays simple
// (it just iterates the pre-parsed tokens, no parser at runtime).
const parsedCompounds = doc.compounds.map((section) => ({
...section,
items: section.items.map((item) => ({
name: item.name,
formula: parseFormula(item.formula, KNOWN_SYMS),
})),
}));
// ── 3. Emit the TypeScript file ─────────────────────────────────────────
const out = renderTs(doc, parsedCompounds);
fs.writeFileSync(TS_PATH, out);
console.log(`Wrote ${TS_PATH}`);
console.log(` ${doc.elements.length} elements`);
console.log(` ${parsedCompounds.reduce((n, s) => n + s.items.length, 0)} compounds across ${parsedCompounds.length} sections`);
// ════════════════════════════════════════════════════════════════════════
// Validation (mirrors build-html.mjs)
// ════════════════════════════════════════════════════════════════════════
function validate(doc) {
if (!doc || typeof doc !== "object") throw new Error("table.yml is empty or malformed");
if (!Array.isArray(doc.elements) || doc.elements.length === 0) throw new Error("No elements defined");
const issues = [];
const knownSyms = new Set(doc.elements.map((e) => e.sym));
const cellSeen = new Map();
for (const e of doc.elements) {
const k = `${e.row},${e.col}`;
if (cellSeen.has(k)) {
issues.push(`Cell collision at (${k}): #${cellSeen.get(k)} and #${e.id}`);
} else {
cellSeen.set(k, e.id);
}
}
for (const e of doc.elements) {
for (const b of e.bonds || []) {
if (!knownSyms.has(b)) issues.push(`Element #${e.id} ${e.sym}: unresolved bond '${b}'`);
}
}
const symCount = {};
for (const e of doc.elements) symCount[e.sym] = (symCount[e.sym] || 0) + 1;
const declared = new Set((doc.known_collisions || []).map((c) => c.sym));
for (const [sym, count] of Object.entries(symCount)) {
if (count > 1 && !declared.has(sym)) {
issues.push(`Undocumented symbol collision: ${sym} appears ${count} times — add to known_collisions`);
}
}
for (const section of doc.compounds || []) {
for (const item of section.items || []) {
const refs = extractFormulaSymbols(item.formula);
for (const ref of refs) {
if (!knownSyms.has(ref)) {
issues.push(`Compound "${item.name}" references unknown symbol '${ref}': ${item.formula}`);
}
}
}
}
if (issues.length > 0) {
console.error("VALIDATION FAILED:");
issues.forEach((i) => console.error(" " + i));
process.exit(1);
}
}
function extractFormulaSymbols(formula) {
const cleaned = formula.replace(/_[A-Za-z]+/g, "");
const re = /(?<![A-Za-z])([A-Z][a-z])(?![A-Za-z])/g;
const out = [];
let m;
while ((m = re.exec(cleaned)) !== null) out.push(m[1]);
return out;
}
// ════════════════════════════════════════════════════════════════════════
// Formula parser: string -> FormulaToken[]
// ════════════════════════════════════════════════════════════════════════
/**
* Walks the formula left to right. A "symbol token" is a two-character
* substring [A-Z][a-z] that (a) appears in `knownSyms`, (b) is bordered
* by non-letters on both sides, and (c) optionally has a `_subscript`
* trailing it. Anything else accumulates into op tokens (sequential
* connectives, brackets, parens, the ᴺ marker, etc.).
*
* Bracket/paren subscripts (`]ᴺ_enc`) become an op token containing the
* literal underscore + word — the React renderer treats them as inline
* text. (The standalone HTML emitter promotes them to <sub> tags; the
* React renderer doesn't, but the visual difference is minor and we can
* upgrade later.)
*/
function parseFormula(formula, knownSyms) {
const tokens = [];
let pendingOp = "";
let i = 0;
while (i < formula.length) {
const two = formula.slice(i, i + 2);
const isSym =
two.length === 2 &&
/^[A-Z][a-z]$/.test(two) &&
knownSyms.has(two) &&
(i === 0 || !/[A-Za-z]/.test(formula[i - 1])) &&
(i + 2 >= formula.length || !/[A-Za-z]/.test(formula[i + 2]));
if (isSym) {
if (pendingOp) {
tokens.push({ kind: "op", text: pendingOp });
pendingOp = "";
}
let sub;
if (formula[i + 2] === "_") {
const subMatch = formula.slice(i + 3).match(/^[A-Za-z]+/);
if (subMatch) {
sub = subMatch[0];
tokens.push({ kind: "sym", sym: two, sub });
i += 3 + sub.length;
continue;
}
}
tokens.push({ kind: "sym", sym: two });
i += 2;
} else {
pendingOp += formula[i];
i += 1;
}
}
if (pendingOp) tokens.push({ kind: "op", text: pendingOp });
return tokens;
}
// ════════════════════════════════════════════════════════════════════════
// TypeScript code generation
// ════════════════════════════════════════════════════════════════════════
function renderTs(doc, parsedCompounds) {
let out = "";
out += "// @generated — DO NOT EDIT BY HAND\n";
out += "// Source of truth: periodic-table/table.yml\n";
out += "// Regenerate: npm run sync:periodic-table (from interviews/staffml)\n";
out += "//\n";
out += "// Edits to this file will be overwritten on the next sync. To change\n";
out += "// any element, compound, color, or label, edit table.yml and re-run\n";
out += "// the sync script (or just run `npm run dev` — predev hook handles it).\n";
out += "\n";
// Types
out += `export type BlockKey = ${doc.blocks.map((b) => `"${b.key}"`).join(" | ")};\n\n`;
out += "export interface Block {\n";
out += " key: BlockKey;\n";
out += " name: string;\n";
out += " sub: string;\n";
out += " color: string;\n";
out += " cols: number[];\n";
out += "}\n\n";
// blocks (as a Record so the existing React code keeps working — it does
// `blocks[el.block]` lookups by key)
out += "export const blocks: Record<BlockKey, Block> = {\n";
for (const b of doc.blocks) {
out += ` ${b.key}: { key: "${b.key}", name: ${tsStr(b.name)}, sub: ${tsStr(b.sub)}, color: ${tsStr(b.color)}, cols: [${b.cols.join(", ")}] },\n`;
}
out += "};\n\n";
// rowLabels (a flat string array — what the React page imports)
out += "export const rowLabels = [\n";
for (const r of doc.rows) {
out += ` ${tsStr(r.name)},\n`;
}
out += "];\n\n";
// Element type
out += "export interface Element {\n";
out += " num: number;\n";
out += " sym: string;\n";
out += " name: string;\n";
out += " block: BlockKey;\n";
out += " row: number;\n";
out += " col: number;\n";
out += " year: string;\n";
out += " desc: string;\n";
out += " bonds: string[];\n";
out += " whyHere: string;\n";
out += "}\n\n";
// elements
out += "export const elements: Element[] = [\n";
for (const e of doc.elements) {
const year = e.year === null || e.year === undefined ? '"—"' : tsStr(e.year);
const bonds = e.bonds.length === 0 ? "[]" : `[${e.bonds.map(tsStr).join(", ")}]`;
out += ` { num: ${e.id}, sym: ${tsStr(e.sym)}, name: ${tsStr(e.name)}, block: "${e.block}", row: ${e.row}, col: ${e.col}, year: ${year}, desc: ${tsStr(e.desc)}, bonds: ${bonds}, whyHere: ${tsStr(e.why)} },\n`;
}
out += "];\n\n";
// elMap — last-write-wins lookup, matching the original behavior
out += "// Lookup map by symbol — last write wins (matches the original\n";
out += "// behavior for documented symbol collisions like Sm/Sp/Ro/En).\n";
out += "export const elMap: Record<string, Element> = {};\n";
out += "elements.forEach((e) => { elMap[e.sym] = e; });\n\n";
// Compound types
out += "// ── Compounds ─────────────────────────────────────────────────────────────\n";
out += "// Each formula is a list of typed tokens parsed from the YAML's formula\n";
out += "// string at sync time, so the React renderer never has to parse anything.\n";
out += "// sym tokens reference an element by symbol, with optional subscript.\n";
out += "// op tokens are literal connective text (→ ∥ ⇌ ↺ [ ] ( ) ?).\n";
out += "export type FormulaToken =\n";
out += ' | { kind: "sym"; sym: string; sub?: string }\n';
out += ' | { kind: "op"; text: string };\n\n';
out += "export interface Compound {\n";
out += " name: string;\n";
out += " formula: FormulaToken[];\n";
out += "}\n\n";
out += "export interface CompoundSection {\n";
out += " title: string;\n";
out += " hint?: string;\n";
out += " items: Compound[];\n";
out += "}\n\n";
// compounds (parsed)
out += "export const compounds: CompoundSection[] = [\n";
for (const section of parsedCompounds) {
out += ` {\n`;
out += ` title: ${tsStr(section.section)},\n`;
if (section.hint) out += ` hint: ${tsStr(section.hint)},\n`;
out += ` items: [\n`;
for (const item of section.items) {
out += ` { name: ${tsStr(item.name)}, formula: ${renderTokenList(item.formula)} },\n`;
}
out += ` ],\n`;
out += ` },\n`;
}
out += "];\n";
return out;
}
function renderTokenList(tokens) {
if (tokens.length === 0) return "[]";
const parts = tokens.map((t) => {
if (t.kind === "sym") {
return t.sub
? `{ kind: "sym", sym: ${tsStr(t.sym)}, sub: ${tsStr(t.sub)} }`
: `{ kind: "sym", sym: ${tsStr(t.sym)} }`;
}
return `{ kind: "op", text: ${tsStr(t.text)} }`;
});
return "[" + parts.join(", ") + "]";
}
function tsStr(s) {
if (s === null || s === undefined) return '""';
// Use double quotes; escape backslash, double quote, and special chars.
return '"' + String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n") + '"';
}

View File

@@ -0,0 +1,151 @@
/* Periodic-table grid — kept as a CSS module because the dense 15×9 grid
plus per-element CSS variable theming would be very noisy in Tailwind. */
.tableWrap {
display: flex;
justify-content: center;
overflow-x: auto;
padding: 0 0.5rem;
}
.tableOuter {
display: flex;
gap: 0;
}
.yLabels {
display: flex;
flex-direction: column;
gap: 3px;
margin-right: 6px;
padding-top: 26px;
}
.yLbl {
height: 58px;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 6px;
font-size: 0.55rem;
color: var(--text-tertiary);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
white-space: nowrap;
}
.yLblFrontier {
margin-top: 8px;
padding-top: 8px;
padding-right: 6px;
height: 24px;
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 0.55rem;
color: var(--text-muted);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
border-top: 1px dashed var(--border);
white-space: nowrap;
font-style: italic;
}
.blockHeaders {
display: flex;
gap: 3px;
margin-bottom: 3px;
}
.bh {
text-align: center;
font-size: 0.55rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
padding: 4px 0;
border-radius: 4px 4px 0 0;
}
.grid {
display: grid;
grid-template-columns: repeat(15, 58px);
grid-template-rows: repeat(8, 58px);
gap: 3px;
}
.el {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
padding: 2px;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s, opacity 0.2s;
overflow: hidden;
}
.el::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: var(--el-c);
opacity: 0.75;
}
.el:hover {
transform: scale(1.18);
z-index: 10;
border-color: var(--el-c);
box-shadow: 0 0 18px color-mix(in srgb, var(--el-c) 30%, transparent);
}
.dimmed {
opacity: 0.12;
pointer-events: none;
}
.elNum {
font-size: 0.5rem;
color: var(--text-muted);
position: absolute;
top: 2px;
left: 4px;
}
.elSym {
font-size: 1.05rem;
font-weight: 700;
color: var(--el-c);
line-height: 1.1;
}
.elName {
font-size: 0.42rem;
color: var(--text-tertiary);
text-align: center;
line-height: 1.15;
margin-top: 1px;
padding: 0 1px;
}
.xref {
border-color: var(--el-c) !important;
box-shadow: 0 0 16px color-mix(in srgb, var(--el-c) 40%, transparent);
animation: xpulse 0.6s ease;
}
@keyframes xpulse {
0% { transform: scale(1); }
50% { transform: scale(1.12); }
100% { transform: scale(1); }
}

View File

@@ -0,0 +1,620 @@
"use client";
import { useMemo, useState, useEffect, useCallback } from "react";
import Link from "next/link";
import { ArrowLeft, Search, X } from "lucide-react";
import {
blocks,
rowLabels,
elements,
elMap,
compounds,
type Block,
type BlockKey,
type Element,
type Compound,
type FormulaToken,
} from "@/data/periodicTable";
import styles from "./PeriodicTable.module.css";
// ── Helpers ──────────────────────────────────────────────────────────────
const blockEntries = Object.entries(blocks) as Array<[BlockKey, Block]>;
function compoundSyms(compound: Compound): Set<string> {
const set = new Set<string>();
compound.formula.forEach((t) => {
if (t.kind === "sym" && elMap[t.sym]) set.add(t.sym);
});
return set;
}
// Pre-compute compound ↔ symbol maps once.
const compoundSymCache: Map<Compound, Set<string>> = new Map();
const symToCompounds: Record<string, Compound[]> = {};
let TOTAL_COMPOUNDS = 0;
compounds.forEach((section) =>
section.items.forEach((c) => {
TOTAL_COMPOUNDS += 1;
const syms = compoundSyms(c);
compoundSymCache.set(c, syms);
syms.forEach((sym) => {
if (!symToCompounds[sym]) symToCompounds[sym] = [];
symToCompounds[sym].push(c);
});
}),
);
// ── Page ─────────────────────────────────────────────────────────────────
export default function FrameworkPage() {
const [activeBlock, setActiveBlock] = useState<BlockKey | null>(null);
const [query, setQuery] = useState("");
const [selected, setSelected] = useState<Element | null>(null);
// Cross-ref state: clicking element highlights its compounds; clicking
// a compound highlights its elements.
const [highlightedCompounds, setHighlightedCompounds] = useState<Set<Compound> | null>(null);
const [highlightedSyms, setHighlightedSyms] = useState<Set<string> | null>(null);
const dimmedSet = useMemo(() => {
const q = query.toLowerCase().trim();
const dimmed = new Set<string>();
elements.forEach((e) => {
const blockMatch = !activeBlock || e.block === activeBlock;
const searchMatch =
!q || e.name.toLowerCase().includes(q) || e.sym.toLowerCase().includes(q);
if (!(blockMatch && searchMatch)) dimmed.add(e.sym);
});
return dimmed;
}, [activeBlock, query]);
const clearXref = useCallback(() => {
setHighlightedCompounds(null);
setHighlightedSyms(null);
}, []);
const handleElementClick = useCallback((e: Element) => {
// Match the original: highlight matching compound cards AND scroll the
// first match into view (scroll happens behind the modal so the page is
// already positioned correctly when the user closes the panel).
const cards = symToCompounds[e.sym] || [];
if (cards.length > 0) {
setHighlightedCompounds(new Set(cards));
setHighlightedSyms(null);
const firstName = cards[0].name;
requestAnimationFrame(() => {
try {
document
.querySelector<HTMLElement>(`[data-pt-compound="${CSS.escape(firstName)}"]`)
?.scrollIntoView({ behavior: "smooth", block: "nearest" });
} catch {
/* scroll is best-effort */
}
});
}
setSelected(e);
}, []);
const handleCompoundClick = useCallback(
(c: Compound) => {
const syms = compoundSymCache.get(c);
if (!syms || syms.size === 0) return;
setHighlightedCompounds(new Set([c]));
setHighlightedSyms(syms);
requestAnimationFrame(() => {
document.getElementById("table-anchor")?.scrollIntoView({
behavior: "smooth",
block: "start",
});
});
},
[],
);
// Esc closes overlay / clears xref.
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") {
setSelected(null);
clearXref();
}
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [clearXref]);
// Click anywhere outside an element / compound card / panel clears the
// cross-reference highlight, matching the original page's behavior.
const xrefIsActive = highlightedCompounds !== null || highlightedSyms !== null;
useEffect(() => {
if (!xrefIsActive) return;
const handler = (e: MouseEvent) => {
const target = e.target as HTMLElement | null;
if (!target) return;
if (target.closest("[data-pt-element]")) return;
if (target.closest("[data-pt-compound]")) return;
if (target.closest("[data-pt-panel]")) return;
clearXref();
};
document.addEventListener("click", handler);
return () => document.removeEventListener("click", handler);
}, [xrefIsActive, clearXref]);
return (
<div className="flex-1 overflow-auto">
<div className="max-w-6xl mx-auto px-4 sm:px-6 py-10">
{/* Back link */}
<Link
href="/"
className="inline-flex items-center gap-1.5 text-sm text-textTertiary hover:text-textSecondary transition-colors mb-8"
>
<ArrowLeft className="w-3.5 h-3.5" /> Back to Vault
</Link>
{/* Hero */}
<div className="text-center mb-10">
<span className="inline-block text-[10px] uppercase tracking-[0.14em] font-semibold text-accentBlue px-2.5 py-1 rounded-full border border-accentBlue/30 bg-accentBlue/5 mb-4">
v0.1 · {elements.length} Elements · {TOTAL_COMPOUNDS} Compounds
</span>
<h1 className="text-3xl sm:text-4xl font-extrabold text-textPrimary tracking-tight mb-3">
The <span className="text-accentBlue italic">Periodic Table</span> of Machine Learning Systems
</h1>
<p className="text-[15px] text-textSecondary leading-relaxed max-w-2xl mx-auto">
Two fundamental axes abstraction layer and information-processing role organize ML
concepts the way electron shells and valence organize chemistry.
</p>
</div>
{/* Two axes explainer */}
<section className="mb-6">
<h2 className="text-base font-bold text-textPrimary mb-3">The Two Axes</h2>
<div className="grid sm:grid-cols-2 gap-3">
<div className="rounded-xl border border-borderSubtle bg-surface/60 p-4">
<h3 className="text-sm font-bold text-textPrimary mb-1.5 flex items-center gap-2">
<span className="text-accentBlue"></span> Rows: Abstraction Layer
</h3>
<p className="text-[12.5px] text-textSecondary mb-2 leading-relaxed">
Like electron shells, each layer <strong className="text-textPrimary">builds on and contains</strong> the
ones above. You can&apos;t have optimization without a model, or deployment without hardware.
</p>
<ol className="text-[11.5px] text-textTertiary leading-relaxed space-y-0.5">
{rowLabels.map((label, i) => (
<li key={label}>
<span className="text-textPrimary font-semibold">
{i + 1}. {label}
</span>
</li>
))}
</ol>
</div>
<div className="rounded-xl border border-borderSubtle bg-surface/60 p-4">
<h3 className="text-sm font-bold text-textPrimary mb-1.5 flex items-center gap-2">
<span className="text-accentBlue"></span> Columns: Information-Processing Role
</h3>
<p className="text-[12.5px] text-textSecondary mb-2 leading-relaxed">
From computer architecture and systems theory five roles that exist in{" "}
<strong className="text-textPrimary">any</strong> information-processing system.
</p>
<ul className="text-[11.5px] text-textTertiary leading-relaxed space-y-0.5">
{blockEntries.map(([key, b]) => (
<li key={key}>
<span className="font-semibold" style={{ color: b.color }}>
{b.name}
</span>
<span className="text-textTertiary"> {b.sub}</span>
</li>
))}
</ul>
</div>
</div>
</section>
{/* "Same column test" proof box */}
<section className="mb-10 rounded-xl border-l-[3px] border-l-accentBlue border border-borderSubtle bg-surface/60 px-4 py-3.5">
<p className="text-[12.5px] text-textSecondary mb-2 leading-relaxed">
<strong className="text-textPrimary">The same-column test:</strong> Concepts in the
same column genuinely behave alike.
</p>
{([
["Represent", blocks.R.color, "Tensor, Probability, Parameter, Embedding, Topology, Hidden State, Optimizer State, Caching, Checkpointing, SRAM, DRAM, Artifact Store — all hold and structure information across all 8 layers."],
["Compute", blocks.C.color, "Operator, Activation, Dense Dot, Convolution, Pooling, Attention, Routing, Quantization, Fusion, Tiling, Compilation — all transform inputs to outputs."],
["Communicate", blocks.X.color, "Chain Rule, Backprop, Tokenization, Skip/Res, Distillation, Weight Averaging, Pipelining, Sync, Prefetching, Interconnect, RPC Protocol — all move information between components without deciding what to do with it."],
["Control", blocks.K.color, "Objective, Constraint, Grad Descent, Search, Initialization, Masking, Scheduling, Regularization, Allocation, Arbiter, Load Balancer, Telemetry — all make decisions that govern system behavior."],
["Measure", blocks.M.color, "Entropy, Loss Function, Receptive Field, Info Density, Throughput, Energy, Latency — all observe without changing. Noble gases."],
] as const).map(([label, color, text]) => (
<p key={label} className="text-[12px] text-textSecondary mb-1 last:mb-0 leading-relaxed">
<strong style={{ color }}>{label}: </strong>
<span className="text-textTertiary">{text}</span>
</p>
))}
</section>
{/* Controls */}
<div id="table-anchor" className="flex flex-col items-center gap-3 mb-4">
<div className="relative w-full max-w-sm">
<Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-textTertiary pointer-events-none" />
<input
type="text"
placeholder="Search elements…"
value={query}
onChange={(ev) => setQuery(ev.target.value)}
className="w-full pl-8 pr-3 py-2 text-[13px] bg-surface border border-border rounded-lg text-textPrimary placeholder:text-textTertiary focus:outline-none focus:border-accentBlue transition-colors"
/>
</div>
<div className="flex flex-wrap justify-center gap-1.5">
{blockEntries.map(([key, b]) => {
const isActive = activeBlock === key;
return (
<button
key={key}
onClick={() => setActiveBlock(isActive ? null : key)}
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-full text-[11px] font-medium transition-colors border ${
isActive
? "border-borderHighlight bg-surfaceHover text-textPrimary"
: "border-borderSubtle bg-surface/60 text-textTertiary hover:text-textSecondary"
}`}
>
<span
className="w-2.5 h-2.5 rounded-sm shrink-0"
style={{ background: b.color }}
/>
<span style={{ color: isActive ? b.color : undefined }}>{b.name}</span>
<span className="hidden sm:inline text-textMuted text-[10px]"> {b.sub}</span>
</button>
);
})}
{(activeBlock || query) && (
<button
onClick={() => {
setActiveBlock(null);
setQuery("");
}}
className="flex items-center gap-1 px-2.5 py-1 rounded-full text-[11px] font-medium text-textTertiary hover:text-textPrimary transition-colors"
>
<X className="w-3 h-3" /> Clear
</button>
)}
</div>
</div>
{/* Periodic grid — framed in a card so the empty cells feel intentional */}
<div className="rounded-2xl border border-borderSubtle bg-surface/40 px-2 sm:px-4 py-5 mb-2 overflow-x-auto">
<PeriodicGrid
dimmed={dimmedSet}
highlightedSyms={highlightedSyms}
onElementClick={handleElementClick}
/>
</div>
<p className="text-center text-[11px] text-textTertiary mt-2">
Click any element for details · click a compound below to highlight its primitives
</p>
{/* Compounds */}
<section id="compounds-anchor" className="mt-12">
<h2 className="text-base font-bold text-textPrimary mb-2">Molecular ML (Compounds)</h2>
<p className="text-[13px] text-textSecondary mb-4 leading-relaxed">
Just as HO = hydrogen + oxygen, every ML system decomposes into primitives from the
table above. Read each formula left to right symbols are element codes, operators
show how they bond.
</p>
<CompoundLegend />
{compounds.map((section) => (
<div key={section.title} className="mt-6">
<h3 className="text-[11px] uppercase tracking-[0.08em] font-bold text-textTertiary border-b border-borderSubtle pb-1.5 mb-3">
{section.title}
{section.hint && (
<span className="ml-2 text-accentBlue normal-case font-normal tracking-normal text-[11px]">
{section.hint}
</span>
)}
</h3>
<div className="grid gap-2.5 grid-cols-[repeat(auto-fill,minmax(280px,1fr))]">
{section.items.map((c) => {
const isHighlighted = highlightedCompounds?.has(c);
const isDimmed = highlightedCompounds && !isHighlighted;
return (
<button
key={c.name}
data-pt-compound={c.name}
onClick={() => handleCompoundClick(c)}
className={`text-left rounded-xl border bg-surface/60 p-3 transition-all ${
isHighlighted
? "border-accentBlue shadow-[0_0_14px_color-mix(in_srgb,var(--accent-blue)_25%,transparent)]"
: "border-borderSubtle hover:border-border"
} ${isDimmed ? "opacity-20" : ""}`}
>
<div className="text-[13px] font-bold text-accentBlue mb-1.5">{c.name}</div>
<FormulaRender tokens={c.formula} />
</button>
);
})}
</div>
</div>
))}
</section>
<footer className="text-center mt-16 pt-6 border-t border-borderSubtle text-[11px] text-textTertiary">
A project of{" "}
<a href="https://mlsysbook.ai" className="text-accentBlue hover:underline">
Machine Learning Systems
</a>{" "}
Harvard CS249r · Vijay Janapa Reddi
</footer>
</div>
{/* Detail overlay */}
{selected && (
<ElementDetail element={selected} onClose={() => setSelected(null)} onBondClick={(sym) => {
const target = elMap[sym];
if (target) setSelected(target);
}} />
)}
</div>
);
}
// ── Periodic grid ────────────────────────────────────────────────────────
function PeriodicGrid({
dimmed,
highlightedSyms,
onElementClick,
}: {
dimmed: Set<string>;
highlightedSyms: Set<string> | null;
onElementClick: (e: Element) => void;
}) {
return (
<div className={styles.tableWrap}>
<div className={styles.tableOuter}>
{/* Y-axis labels */}
<div className={styles.yLabels}>
{rowLabels.map((label, i) => (
<div key={label} className={styles.yLbl}>
{i + 1}. {label}
</div>
))}
</div>
<div>
{/* Block headers */}
<div className={styles.blockHeaders}>
{blockEntries.map(([key, b]) => {
const widthPx = b.cols.length * 58 + (b.cols.length - 1) * 3;
return (
<div
key={key}
className={styles.bh}
style={{
width: `${widthPx}px`,
background: `color-mix(in srgb, ${b.color} 18%, transparent)`,
color: b.color,
}}
>
{b.name}
</div>
);
})}
</div>
{/* Grid */}
<div className={styles.grid}>
{elements.map((e) => {
const block = blocks[e.block];
const isDimmed = dimmed.has(e.sym);
const isXref = highlightedSyms?.has(e.sym);
return (
<button
key={`${e.row}-${e.col}-${e.sym}`}
type="button"
data-pt-element=""
className={`${styles.el} ${isDimmed ? styles.dimmed : ""} ${
isXref ? styles.xref : ""
}`}
style={
{
"--el-c": block.color,
gridRow: e.row,
gridColumn: e.col,
} as React.CSSProperties
}
onClick={() => onElementClick(e)}
title={`${e.name}${block.name}`}
>
<span className={styles.elNum}>{e.num}</span>
<span className={styles.elSym}>{e.sym}</span>
<span className={styles.elName}>{e.name}</span>
</button>
);
})}
</div>
</div>
</div>
</div>
);
}
// ── Compound legend ──────────────────────────────────────────────────────
function CompoundLegend() {
const items = [
["→", "Sequential"],
["∥", "Parallel"],
["?", "Conditional"],
["⇌", "Adversarial"],
["↺", "Feedback Loop"],
["[ ]ᴺ", "Repeated Block"],
];
return (
<div className="flex flex-wrap justify-center gap-x-5 gap-y-1.5 px-3 py-2 rounded-lg border border-borderSubtle bg-surface/40">
{items.map(([sym, label]) => (
<div key={label} className="text-[11px] text-textTertiary flex items-center gap-1.5">
<span className="font-mono font-bold text-accentBlue text-[13px]">{sym}</span>
{label}
</div>
))}
</div>
);
}
// ── Formula renderer ─────────────────────────────────────────────────────
function FormulaRender({ tokens }: { tokens: FormulaToken[] }) {
return (
<div className="text-[12px] font-mono leading-relaxed bg-background/60 px-2.5 py-2 rounded-md text-textTertiary break-words">
{tokens.map((t, i) => {
if (t.kind === "op") return <span key={i}>{t.text}</span>;
const el = elMap[t.sym];
const color = el ? blocks[el.block].color : undefined;
return (
<span key={i} className={`relative inline-block group/sym ${el ? "cursor-help" : ""}`}>
<span
className="font-semibold"
style={{
color: color || "var(--text-primary)",
borderBottom: el
? `1px dotted color-mix(in srgb, ${color} 50%, transparent)`
: undefined,
}}
>
{t.sym}
{t.sub && <sub className="text-[9px] opacity-80">{t.sub}</sub>}
</span>
{el && (
<span className="pointer-events-none absolute left-1/2 -translate-x-1/2 bottom-full mb-1.5 whitespace-nowrap rounded-md border border-border bg-surfaceElevated px-2 py-1 text-[10px] font-sans font-medium text-textPrimary shadow-lg opacity-0 group-hover/sym:opacity-100 transition-opacity z-20">
{el.name}
</span>
)}
</span>
);
})}
</div>
);
}
// ── Element detail overlay ───────────────────────────────────────────────
// Pixel-port of the original .panel CSS from periodic-table/index.html.
// Sizes use arbitrary Tailwind values to match the original's rems/px
// exactly rather than rounding to nearest preset.
function ElementDetail({
element,
onClose,
onBondClick,
}: {
element: Element;
onClose: () => void;
onBondClick: (sym: string) => void;
}) {
const block = blocks[element.block];
const color = block.color;
const layerName = rowLabels[element.row - 1];
return (
<div
className="fixed inset-0 z-[100] bg-black/60 flex items-center justify-center p-4"
onClick={onClose}
>
<div
data-pt-panel=""
className="relative w-full max-w-[500px] bg-surface border border-border rounded-[14px] p-[1.8rem]"
onClick={(e) => e.stopPropagation()}
style={{ animation: "ptPop 0.2s ease" }}
>
<button
onClick={onClose}
className="absolute top-[0.7rem] right-[1rem] bg-transparent border-0 text-textTertiary hover:text-textPrimary text-[1.4rem] leading-none cursor-pointer"
aria-label="Close"
>
×
</button>
{/* Header */}
<div className="flex items-end gap-[0.9rem] mb-4 pb-4 border-b border-border">
<div
className="w-[70px] h-[70px] rounded-[9px] flex flex-col items-center justify-center shrink-0 bg-background border-2"
style={{ borderColor: color }}
>
<span className="text-[0.6rem] text-textTertiary">#{element.num}</span>
<span className="text-[1.7rem] font-bold leading-none" style={{ color }}>
{element.sym}
</span>
</div>
<div className="min-w-0">
<div className="text-[1.15rem] font-bold text-textPrimary leading-tight">
{element.name}
</div>
<div
className="text-[0.65rem] uppercase tracking-[0.08em] font-semibold mt-1"
style={{ color }}
>
{block.name} · {layerName}
</div>
</div>
</div>
{/* Meta */}
<div className="grid grid-cols-3 gap-[0.5rem] mb-[0.9rem]">
<MetaCell label="Introduced" value={element.year} />
<MetaCell label="Role" value={block.name} valueColor={color} />
<MetaCell label="Layer" value={layerName} />
</div>
{/* Description */}
<p className="text-[0.8rem] leading-[1.6] text-textTertiary mb-[0.9rem]">
{element.desc}
</p>
{/* Why here */}
<div className="bg-background rounded-[7px] px-[0.8rem] py-[0.6rem] text-[0.73rem] leading-[1.5] text-textTertiary mb-[0.9rem]">
<strong className="text-textPrimary font-bold">Why this position: </strong>
{element.whyHere}
</div>
{/* Bonds */}
{element.bonds.length > 0 && (
<div>
<h4 className="text-[0.6rem] uppercase tracking-[0.07em] font-semibold text-textMuted mb-[0.3rem]">
Bonds With
</h4>
<div className="flex flex-wrap gap-[0.3rem]">
{element.bonds.map((sym) => {
const target = elMap[sym];
return (
<button
key={sym}
onClick={() => target && onBondClick(sym)}
disabled={!target}
className="bg-background border border-border rounded-[4px] px-[0.4rem] py-[0.12rem] text-[0.67rem] text-textSecondary hover:border-accentBlue hover:text-accentBlue transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{sym}
{target && `${target.name}`}
</button>
);
})}
</div>
</div>
)}
</div>
</div>
);
}
function MetaCell({
label,
value,
valueColor,
}: {
label: string;
value: string;
valueColor?: string;
}) {
return (
<div className="bg-background rounded-[6px] px-[0.6rem] py-[0.45rem]">
<div className="text-[0.5rem] uppercase tracking-[0.07em] text-textMuted">{label}</div>
<div
className="text-[0.78rem] font-semibold mt-[2px]"
style={valueColor ? { color: valueColor } : undefined}
>
{value}
</div>
</div>
);
}

View File

@@ -6,31 +6,8 @@
.scrollbar-hide::-webkit-scrollbar { display: none; }
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
/* ── Light theme (default) ── */
/* ── Dark theme (default — applies to :root so it works without JS) ── */
:root {
--background: #ffffff;
--surface: #f8f9fa;
--surface-hover: #eef0f2;
--surface-elevated: #e4e5e9;
--border: #d4d5db;
--border-subtle: #e2e3e8;
--border-highlight: #b8b9c4;
--text-primary: #1a1b25;
--text-secondary: #4a4b5c;
--text-tertiary: #7a7b8e;
--text-muted: #a0a1b0;
--accent-blue: #2563eb;
--accent-red: #ef4444;
--accent-amber: #d97706;
--accent-green: #059669;
--accent-purple: #7c3aed;
}
/* ── Dark theme (explicit toggle) ── */
[data-theme="dark"] {
--background: #111117;
--surface: #1a1a22;
--surface-hover: #242430;
@@ -52,29 +29,27 @@
--accent-purple: #b49aff;
}
/* ── System preference fallback (no explicit toggle yet) ── */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--background: #111117;
--surface: #1a1a22;
--surface-hover: #242430;
--surface-elevated: #2a2a38;
/* ── Light theme (explicit opt-in via toggle) ── */
[data-theme="light"] {
--background: #ffffff;
--surface: #f8f9fa;
--surface-hover: #eef0f2;
--surface-elevated: #e4e5e9;
--border: #333342;
--border-subtle: #282836;
--border-highlight: #48485a;
--border: #d4d5db;
--border-subtle: #e2e3e8;
--border-highlight: #b8b9c4;
--text-primary: #f0f0f4;
--text-secondary: #bfc0d0;
--text-tertiary: #8a8b9e;
--text-muted: #5c5d70;
--text-primary: #1a1b25;
--text-secondary: #4a4b5c;
--text-tertiary: #7a7b8e;
--text-muted: #a0a1b0;
--accent-blue: #5b9eff;
--accent-red: #ff6b6b;
--accent-amber: #ffbe44;
--accent-green: #3ee8a8;
--accent-purple: #b49aff;
}
--accent-blue: #2563eb;
--accent-red: #ef4444;
--accent-amber: #d97706;
--accent-green: #059669;
--accent-purple: #7c3aed;
}
body {
@@ -138,3 +113,9 @@ h1, h2, h3, h4, h5, h6 {
background-image: radial-gradient(var(--border-highlight) 1px, transparent 0);
background-size: 24px 24px;
}
/* Periodic-table detail panel pop-in (matches original index.html @keyframes pop) */
@keyframes ptPop {
from { opacity: 0; transform: translateY(14px) scale(0.96); }
to { opacity: 1; transform: translateY(0) scale(1); }
}

View File

@@ -37,11 +37,22 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta httpEquiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net; connect-src 'self' https://api.github.com https://mlsysbook.ai https://harvard-edge.github.io; img-src 'self' data: https://mlsysbook.ai https://harvard-edge.github.io; frame-ancestors 'none';" />
{/*
Dev-mode needs 'unsafe-eval' because Next.js's React Refresh / HMR
runtime evaluates strings as JavaScript. Production locks this down.
*/}
<meta
httpEquiv="Content-Security-Policy"
content={`default-src 'self'; script-src 'self' 'unsafe-inline'${
process.env.NODE_ENV === "development" ? " 'unsafe-eval'" : ""
}; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net; connect-src 'self' https://api.github.com https://mlsysbook.ai https://harvard-edge.github.io; img-src 'self' data: https://mlsysbook.ai https://harvard-edge.github.io;`}
/>
<script dangerouslySetInnerHTML={{ __html: `
(function() {
// Dark is the default site-wide; users can opt into light via the
// theme toggle, which persists in localStorage.
var t = localStorage.getItem('staffml_theme');
if (!t) t = matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
if (t !== 'light' && t !== 'dark') t = 'dark';
document.documentElement.dataset.theme = t;
})();
`}} />

View File

@@ -7,7 +7,7 @@ import { ECOSYSTEM_BASE } from "../lib/env";
import {
Library, Target, Crosshair, BarChart3, BookOpen, Github,
Menu, X, Sun, Moon, Map, Cpu, Server, ChevronDown, Info,
Star, Bug, Send,
Star, Bug, Send, Atom,
} from "lucide-react";
import clsx from "clsx";
import StreakBadge from "@/components/StreakBadge";
@@ -25,6 +25,7 @@ const primaryLinks = [
const toolLinks = [
{ href: "/plans", label: "Study Plans", icon: Map },
{ href: "/framework", label: "Framework", icon: Atom },
{ href: "/contribute", label: "Contribute", icon: Send },
{ href: "/roofline", label: "Roofline", icon: Cpu },
{ href: "/simulator", label: "Simulator", icon: Server },

View File

@@ -10,7 +10,7 @@ interface ThemeContextValue {
}
const ThemeContext = createContext<ThemeContextValue>({
theme: "light",
theme: "dark",
toggleTheme: () => {},
});
@@ -19,15 +19,19 @@ export function useTheme() {
}
function getInitialTheme(): Theme {
if (typeof window === "undefined") return "light";
if (typeof window === "undefined") return "dark";
const stored = localStorage.getItem("staffml_theme") as Theme | null;
if (stored === "light" || stored === "dark") return stored;
// Default to dark; honor OS preference only if explicitly light
return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
// Dark is the site-wide default. OS preference is intentionally NOT
// honored — users can opt into light via the theme toggle.
return "dark";
}
export default function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>("light");
// SSR/first-paint defaults to dark to match the inline script in layout.tsx.
// The useEffect below reconciles with the DOM value the inline script set,
// which may be "light" if the user has an OS preference or stored choice.
const [theme, setTheme] = useState<Theme>("dark");
// Sync with actual DOM on mount (inline script already set data-theme)
useEffect(() => {

View File

@@ -0,0 +1,260 @@
// @generated — DO NOT EDIT BY HAND
// Source of truth: periodic-table/table.yml
// Regenerate: npm run sync:periodic-table (from interviews/staffml)
//
// Edits to this file will be overwritten on the next sync. To change
// any element, compound, color, or label, edit table.yml and re-run
// the sync script (or just run `npm run dev` — predev hook handles it).
export type BlockKey = "R" | "C" | "X" | "K" | "M";
export interface Block {
key: BlockKey;
name: string;
sub: string;
color: string;
cols: number[];
}
export const blocks: Record<BlockKey, Block> = {
R: { key: "R", name: "Represent", sub: "What holds information", color: "#42a5f5", cols: [1, 2] },
C: { key: "C", name: "Compute", sub: "What transforms", color: "#ef6c00", cols: [3, 4, 5, 6, 7, 8] },
X: { key: "X", name: "Communicate", sub: "What moves", color: "#26a69a", cols: [9, 10, 11] },
K: { key: "K", name: "Control", sub: "What decides", color: "#fdd835", cols: [12, 13, 14] },
M: { key: "M", name: "Measure", sub: "What observes", color: "#78909c", cols: [15] },
};
export const rowLabels = [
"Data",
"Math",
"Algorithms",
"Architecture",
"Optimization",
"Runtime",
"Hardware",
"Production",
];
export interface Element {
num: number;
sym: string;
name: string;
block: BlockKey;
row: number;
col: number;
year: string;
desc: string;
bonds: string[];
whyHere: string;
}
export const elements: Element[] = [
{ num: 70, sym: "Rc", name: "Record", block: "R", row: 1, col: 1, year: "—", desc: "The fundamental atomic unit of raw information (a single row, image, or document).", bonds: [], whyHere: "Row 0 (Data): the raw state. Represent." },
{ num: 71, sym: "Ds", name: "Dataset", block: "R", row: 1, col: 2, year: "—", desc: "A structured collection of records.", bonds: ["Rc", "Sm"], whyHere: "Row 0 (Data): the collective state. Represent." },
{ num: 72, sym: "Tr", name: "Transform", block: "C", row: 1, col: 4, year: "—", desc: "The deterministic action of altering raw data (cropping, resizing, parsing).", bonds: ["Rc"], whyHere: "Row 0 (Data): raw manipulation. Compute." },
{ num: 73, sym: "Ag", name: "Aggregate", block: "C", row: 1, col: 5, year: "—", desc: "Combining multiple records into summary statistics.", bonds: ["Ds"], whyHere: "Row 0 (Data): statistical manipulation. Compute." },
{ num: 74, sym: "Fl", name: "Flow/Stream", block: "X", row: 1, col: 9, year: "—", desc: "The continuous movement of raw data from source to system (ETL, Kafka).", bonds: ["Rc", "Ds"], whyHere: "Row 0 (Data): data pipeline. Communicate." },
{ num: 75, sym: "Fm", name: "Format", block: "X", row: 1, col: 10, year: "—", desc: "The structural encoding of data for storage or transit (Parquet, TFRecord).", bonds: ["Rc", "Fl"], whyHere: "Row 0 (Data): serialization. Communicate." },
{ num: 76, sym: "Fi", name: "Filter", block: "K", row: 1, col: 12, year: "—", desc: "The deterministic logic that includes or excludes records based on predicates.", bonds: ["Rc", "Tr"], whyHere: "Row 0 (Data): data gating. Control." },
{ num: 77, sym: "Sm", name: "Schema", block: "K", row: 1, col: 13, year: "—", desc: "The structural constraint defining the expected types and fields of a record.", bonds: ["Rc", "Ds"], whyHere: "Row 0 (Data): type constraint. Control." },
{ num: 80, sym: "En", name: "Entropy", block: "M", row: 1, col: 14, year: "1948", desc: "The Shannon information-theoretic limit; the absolute bound on data compressibility.", bonds: ["Vl"], whyHere: "Row 0 (Data): information limit. Measure." },
{ num: 78, sym: "Vl", name: "Volume", block: "M", row: 1, col: 15, year: "—", desc: "The physical size or cardinality of the dataset (Bytes, Row Count).", bonds: ["Ds"], whyHere: "Row 0 (Data): scale metric. Measure." },
{ num: 1, sym: "Tn", name: "Tensor", block: "R", row: 2, col: 1, year: "—", desc: "The fundamental mathematical structure holding information (scalars, vectors, matrices).", bonds: ["Op", "Cr", "Ob"], whyHere: "Row 1 (Math): most primitive object. Represent: it IS information." },
{ num: 2, sym: "Pr", name: "Probability", block: "R", row: 2, col: 2, year: "1654", desc: "The mathematical primitive for representing uncertainty — distributions, densities.", bonds: ["Tn", "Dv", "Ob"], whyHere: "Row 1 (Math): uncertain state. Represent: encodes beliefs." },
{ num: 87, sym: "St", name: "State", block: "R", row: 2, col: 3, year: "—", desc: "The mathematical representation of an environment or context (RL, SSMs).", bonds: ["Ob"], whyHere: "Row 1 (Math): contextual state. Represent." },
{ num: 3, sym: "Op", name: "Operator", block: "C", row: 2, col: 4, year: "—", desc: "The mathematical action of mapping one space to another (linear or non-linear transforms).", bonds: ["Tn"], whyHere: "Row 1 (Math): pure transformation. Compute: transforms spaces." },
{ num: 4, sym: "Cr", name: "Chain Rule", block: "X", row: 2, col: 9, year: "1676", desc: "The fundamental mathematical mechanism that allows composed derivatives to be computed.", bonds: ["Op"], whyHere: "Row 1 (Math): derivative composition. Communicate: basis for error flow." },
{ num: 5, sym: "Ob", name: "Objective", block: "K", row: 2, col: 12, year: "—", desc: "The mathematical formulation of the goal (Argmin/Argmax).", bonds: ["Cr", "Dv"], whyHere: "Row 1 (Math): the goal state. Control: defines \"better\" or \"worse\"." },
{ num: 6, sym: "Cs", name: "Constraint", block: "K", row: 2, col: 13, year: "—", desc: "The mathematical primitive for defining bounds and restrictions on variables.", bonds: ["Ob"], whyHere: "Row 1 (Math): solution space restriction. Control: hard boundaries." },
{ num: 7, sym: "Dv", name: "Divergence", block: "M", row: 2, col: 15, year: "—", desc: "The mathematical quantification of distance between distributions or tensors (e.g., KL, L2).", bonds: ["Tn", "Pr"], whyHere: "Row 1 (Math): information measure. Measure: quantifies difference." },
{ num: 8, sym: "Pm", name: "Parameter", block: "R", row: 3, col: 1, year: "—", desc: "The irreducible learned memory or state of an algorithm (weights, biases).", bonds: ["Dd", "Cv", "Gd"], whyHere: "Row 2 (Algorithm): learned state. Represent: instantiation of math state." },
{ num: 9, sym: "Eb", name: "Embedding", block: "R", row: 3, col: 2, year: "—", desc: "The fundamental algorithmic act of mapping a discrete symbol into continuous space.", bonds: ["Tn", "Dd"], whyHere: "Row 2 (Algorithm): discrete-to-continuous mapping." },
{ num: 10, sym: "Sp", name: "Sample", block: "R", row: 3, col: 3, year: "—", desc: "The irreducible unit of empirical data distribution (a single data point).", bonds: ["Eb", "Lf"], whyHere: "Row 2 (Algorithm): data representation. Represent: the input unit." },
{ num: 11, sym: "Dd", name: "Dense Dot", block: "C", row: 3, col: 4, year: "—", desc: "The irreducible algorithm for fully connected, all-to-all information transformation.", bonds: ["Pm"], whyHere: "Row 2 (Algorithm): all-to-all transform. Compute." },
{ num: 12, sym: "Cv", name: "Convolution", block: "C", row: 3, col: 5, year: "—", desc: "The irreducible algorithm for local, weight-shared spatial information transformation.", bonds: ["Pm"], whyHere: "Row 2 (Algorithm): local transform. Compute." },
{ num: 13, sym: "Po", name: "Pooling", block: "C", row: 3, col: 6, year: "—", desc: "The algorithmic primitive for spatial or temporal reduction (Max, Average).", bonds: ["Cv", "Dd"], whyHere: "Row 2 (Algorithm): primitive operation. Compute." },
{ num: 14, sym: "Sm", name: "Sampling", block: "C", row: 3, col: 7, year: "—", desc: "The primitive for stochastic selection from a probability distribution.", bonds: ["Pr"], whyHere: "Row 2 (Algorithm): primitive operation. Compute." },
{ num: 86, sym: "Ac", name: "Activation", block: "C", row: 3, col: 8, year: "—", desc: "Non-linear functions (ReLU, GELU) providing expressive power.", bonds: ["Dd"], whyHere: "Row 2 (Algorithm): non-linear transform. Compute." },
{ num: 15, sym: "Ad", name: "Autodiff", block: "X", row: 3, col: 9, year: "1970", desc: "The algorithmic primitive that mechanically computes exact derivatives through arbitrary control flow.", bonds: ["Cr", "Pm"], whyHere: "Row 2 (Algorithm): error routing. Communicate." },
{ num: 16, sym: "Tk", name: "Tokenization", block: "X", row: 3, col: 10, year: "—", desc: "Segmenting raw input into discrete processing units.", bonds: ["Eb"], whyHere: "Row 2 (Algorithm): input segmentation. Communicate." },
{ num: 90, sym: "Ct", name: "Critic", block: "K", row: 3, col: 11, year: "—", desc: "The value function evaluating the expected return of a state (Actor-Critic RL).", bonds: ["St", "Gd"], whyHere: "Row 2 (Algorithm): evaluative model. Control." },
{ num: 17, sym: "Gd", name: "Grad Descent", block: "K", row: 3, col: 12, year: "1847", desc: "The core control loop: takes communicated gradients and updates Parameters.", bonds: ["Ad", "Pm", "Lf"], whyHere: "Row 2 (Algorithm): update mechanism. Control." },
{ num: 18, sym: "Rw", name: "Reward", block: "K", row: 3, col: 13, year: "—", desc: "A scalar control signal evaluating the quality of an action (RL).", bonds: ["Sp", "Gd"], whyHere: "Row 2 (Algorithm): evaluative signal. Control." },
{ num: 19, sym: "Iz", name: "Initialization", block: "K", row: 3, col: 14, year: "—", desc: "The algorithmic control for setting the starting state of parameters.", bonds: ["Pm", "Pr"], whyHere: "Row 2 (Algorithm): starting state control. Control." },
{ num: 20, sym: "Lf", name: "Loss Function", block: "M", row: 3, col: 15, year: "—", desc: "The specific algorithmic computation of the mathematical distance (e.g., Cross-Entropy).", bonds: ["Dv", "Gd"], whyHere: "Row 2 (Algorithm): algorithmic measure. Measure." },
{ num: 21, sym: "Tp", name: "Topology", block: "R", row: 4, col: 1, year: "—", desc: "The fundamental structural assumption placed on data (Sequence, Grid, Graph).", bonds: ["At", "Gt", "Cv"], whyHere: "Row 3 (Architecture): data structure. Represent." },
{ num: 22, sym: "Hs", name: "Hidden State", block: "R", row: 4, col: 2, year: "—", desc: "The architectural primitive for persistent intermediate representation.", bonds: ["Fb", "At", "Gt"], whyHere: "Row 3 (Architecture): structural memory. Represent." },
{ num: 81, sym: "Ix", name: "Indexing", block: "R", row: 4, col: 3, year: "—", desc: "The high-dimensional partitioning of vector space (e.g., HNSW) for sub-linear retrieval.", bonds: ["Tp"], whyHere: "Row 3 (Architecture): structured retrieval. Represent." },
{ num: 23, sym: "At", name: "Attention", block: "C", row: 4, col: 4, year: "—", desc: "Letting data dynamically decide which other data it interacts with.", bonds: ["Mk"], whyHere: "Row 3 (Architecture): dynamic routing. Compute." },
{ num: 24, sym: "Gt", name: "Gating", block: "C", row: 4, col: 5, year: "—", desc: "Using data to scale or shut off other data (Multiplicative flow).", bonds: ["Tn"], whyHere: "Row 3 (Architecture): conditional flow. Compute." },
{ num: 25, sym: "Nm", name: "Normalization", block: "C", row: 4, col: 6, year: "—", desc: "The transform that re-centers and re-scales data distributions between layers.", bonds: ["Tn", "Pm"], whyHere: "Row 3 (Architecture): distribution transform. Compute." },
{ num: 26, sym: "Ro", name: "Routing", block: "C", row: 4, col: 7, year: "—", desc: "Conditional data direction to specific sub-units (e.g., Experts).", bonds: ["Gt", "Mk", "Dd"], whyHere: "Row 3 (Architecture): conditional flow. Compute." },
{ num: 27, sym: "Sk", name: "Skip/Res", block: "X", row: 4, col: 9, year: "—", desc: "The fundamental primitive of identity mapping. Allows information to bypass computation.", bonds: ["Tp"], whyHere: "Row 3 (Architecture): information highway. Communicate." },
{ num: 28, sym: "Fb", name: "Feedback", block: "X", row: 4, col: 10, year: "—", desc: "The structural primitive of routing a signal backward in the graph (Recurrence).", bonds: ["Hs", "Tp"], whyHere: "Row 3 (Architecture): temporal loop. Communicate." },
{ num: 29, sym: "Mk", name: "Masking", block: "K", row: 4, col: 12, year: "—", desc: "The structural enforcement of causality or prevention of information leakage.", bonds: ["At", "Tp"], whyHere: "Row 3 (Architecture): structural constraint. Control." },
{ num: 82, sym: "Ro", name: "Routing", block: "K", row: 4, col: 13, year: "—", desc: "The dynamic, data-dependent dispatch of tensors (e.g., Mixture of Experts).", bonds: ["Gt"], whyHere: "Row 3 (Architecture): dynamic flow. Control." },
{ num: 30, sym: "Rf", name: "Receptive Fld", block: "M", row: 4, col: 15, year: "—", desc: "The measurement of how far information can travel within the architecture in one pass.", bonds: ["Tp", "At", "Cv"], whyHere: "Row 3 (Architecture): spatial/temporal reach. Measure." },
{ num: 31, sym: "Fc", name: "Factorization", block: "R", row: 5, col: 1, year: "—", desc: "Approximating a massive matrix as the product of smaller ones (Low-Rank).", bonds: ["Pm", "Qz", "Sp"], whyHere: "Row 4 (Optimization): rank reduction. Represent." },
{ num: 32, sym: "Os", name: "Optim State", block: "R", row: 5, col: 2, year: "—", desc: "The irreducible memory of the optimization process (momentum, velocity).", bonds: ["Gd", "Sc", "Pm"], whyHere: "Row 4 (Optimization): optimization memory. Represent." },
{ num: 33, sym: "Qz", name: "Quantization", block: "C", row: 5, col: 4, year: "—", desc: "Reducing the bit-width of numbers (FP8, INT4).", bonds: ["Fc", "Sp", "Ws"], whyHere: "Row 4 (Optimization): precision reduction. Compute." },
{ num: 34, sym: "Sp", name: "Sparsification", block: "C", row: 5, col: 5, year: "—", desc: "Turning dense compute sparse by forcing weights or activations to zero.", bonds: ["Fc", "Qz", "Rg"], whyHere: "Row 4 (Optimization): density reduction. Compute." },
{ num: 89, sym: "Wa", name: "Weight Avg", block: "C", row: 5, col: 8, year: "—", desc: "Averaging model weights across time or distributed workers (e.g., SWA, EMA).", bonds: ["Pm"], whyHere: "Row 4 (Optimization): parameter smoothing. Compute." },
{ num: 35, sym: "Ws", name: "Weight Sharing", block: "X", row: 5, col: 9, year: "1980s", desc: "The structural optimization of communicating the same learned state across multiple functional paths (e.g., CNNs).", bonds: ["Pm", "Tp"], whyHere: "Row 4 (Optimization): state reuse. Communicate." },
{ num: 36, sym: "En", name: "Ensembling", block: "X", row: 5, col: 10, year: "—", desc: "Merging weights or outputs across time/workers to improve generalization (SWA).", bonds: ["Pm", "Gd", "Ws"], whyHere: "Row 4 (Optimization): spatial/temporal merging. Communicate." },
{ num: 88, sym: "Re", name: "Retrieve", block: "X", row: 5, col: 11, year: "—", desc: "Fetching stored state or external knowledge (e.g., from a KV Cache or Vector DB).", bonds: ["Hs"], whyHere: "Row 4 (Optimization): state retrieval. Communicate." },
{ num: 37, sym: "Sc", name: "Scheduling", block: "K", row: 5, col: 12, year: "—", desc: "Dynamically decaying or modulating control signals over time.", bonds: ["Gd", "Rg"], whyHere: "Row 4 (Optimization): dynamic modulation. Control." },
{ num: 38, sym: "Rg", name: "Regularization", block: "K", row: 5, col: 13, year: "—", desc: "The structural penalty applied to the objective to force simpler solutions.", bonds: ["Sc", "Sp", "Ob"], whyHere: "Row 4 (Optimization): complexity penalty. Control." },
{ num: 39, sym: "Tm", name: "Termination", block: "K", row: 5, col: 14, year: "—", desc: "The control primitive that evaluates conditions to halt an iterative optimization loop.", bonds: ["Gd", "Lf"], whyHere: "Row 4 (Optimization): temporal bound. Control." },
{ num: 40, sym: "Id", name: "Info Density", block: "M", row: 5, col: 15, year: "—", desc: "The measure of optimization efficiency (Bits per Parameter).", bonds: ["Qz", "Fc", "Sp"], whyHere: "Row 4 (Optimization): compression metric. Measure." },
{ num: 41, sym: "Cc", name: "Caching", block: "R", row: 6, col: 1, year: "—", desc: "Holding intermediate state in fast memory to prevent recomputation (e.g., KV Cache).", bonds: ["At", "Bt", "Pl"], whyHere: "Row 5 (Runtime): state persistence. Represent." },
{ num: 42, sym: "Cp", name: "Checkpointing", block: "R", row: 6, col: 2, year: "—", desc: "Saving and restoring model state for fault tolerance or memory efficiency.", bonds: ["Pm", "As", "Al"], whyHere: "Row 5 (Runtime): state persistence. Represent." },
{ num: 43, sym: "Ir", name: "Int. Rep.", block: "R", row: 6, col: 3, year: "—", desc: "The software state of a computation graph before hardware execution (ONNX, PT2).", bonds: ["Cl", "Fs"], whyHere: "Row 5 (Runtime): structural state. Represent." },
{ num: 44, sym: "Fs", name: "Fusion", block: "C", row: 6, col: 4, year: "—", desc: "Merging multiple operations into a single execution kernel to minimize memory IO.", bonds: ["Op", "At", "Pl"], whyHere: "Row 5 (Runtime): op merging. Compute." },
{ num: 45, sym: "Bt", name: "Batching", block: "C", row: 6, col: 5, year: "—", desc: "Grouping independent inputs for parallel processing.", bonds: ["Cc", "Dd", "Pl"], whyHere: "Row 5 (Runtime): request grouping. Compute." },
{ num: 46, sym: "Ti", name: "Tiling", block: "C", row: 6, col: 6, year: "—", desc: "Partitioning computation into sub-blocks to optimize for memory hierarchy.", bonds: ["Ma", "Sr", "Fs"], whyHere: "Row 5 (Runtime): compute partitioning. Compute." },
{ num: 47, sym: "Cl", name: "Compilation", block: "C", row: 6, col: 7, year: "—", desc: "Lowering high-level operators into hardware-executable kernels.", bonds: ["Ir", "Fs", "Ti"], whyHere: "Row 5 (Runtime): graph-to-kernel translation. Compute." },
{ num: 83, sym: "Vr", name: "Virtualization", block: "R", row: 6, col: 8, year: "—", desc: "The abstraction of physical memory via page tables (e.g., PagedAttention) to solve fragmentation.", bonds: ["Cc"], whyHere: "Row 5 (Runtime): memory mapping. Represent." },
{ num: 48, sym: "Pl", name: "Pipelining", block: "X", row: 6, col: 9, year: "—", desc: "Overlapping the execution of sequential stages across different compute units.", bonds: ["Bt", "Sy", "Al"], whyHere: "Row 5 (Runtime): stage scheduling. Communicate." },
{ num: 49, sym: "Sy", name: "Sync / Coll", block: "X", row: 6, col: 10, year: "—", desc: "Aggregating and broadcasting state across distributed devices.", bonds: ["Ad", "Gd", "Pl"], whyHere: "Row 5 (Runtime): gradient/state sync. Communicate." },
{ num: 50, sym: "Pf", name: "Prefetching", block: "X", row: 6, col: 11, year: "—", desc: "Proactively moving data into faster memory tiers before it is needed.", bonds: ["Ic", "Dr", "Pl"], whyHere: "Row 5 (Runtime): data anticipation. Communicate." },
{ num: 51, sym: "Al", name: "Allocation", block: "K", row: 6, col: 12, year: "—", desc: "The dynamic assignment of hardware resources to software tasks.", bonds: ["Cc", "Cp", "Ar"], whyHere: "Row 5 (Runtime): resource control. Control." },
{ num: 52, sym: "Ut", name: "Utilization", block: "M", row: 6, col: 15, year: "—", desc: "The percentage of theoretical hardware capacity actively used (MFU).", bonds: ["Bt", "Fs"], whyHere: "Row 5 (Runtime): efficiency metric. Measure." },
{ num: 53, sym: "Sr", name: "SRAM", block: "R", row: 7, col: 1, year: "—", desc: "On-chip, low-capacity, extremely high-bandwidth memory (Registers, Scratchpads).", bonds: ["Cc", "Ma", "Ic"], whyHere: "Row 6 (Hardware): fast state. Represent." },
{ num: 54, sym: "Dr", name: "DRAM", block: "R", row: 7, col: 2, year: "—", desc: "Off-chip, high-capacity, lower-bandwidth memory (HBM, DDR).", bonds: ["Cp", "Sr", "Ic"], whyHere: "Row 6 (Hardware): bulk state. Represent." },
{ num: 55, sym: "Ma", name: "MAC Unit", block: "C", row: 7, col: 4, year: "—", desc: "Multiply-Accumulate unit. The fundamental silicon logic gate for tensor math.", bonds: ["Sr", "Dd", "Vu"], whyHere: "Row 6 (Hardware): arithmetic logic. Compute." },
{ num: 56, sym: "Vu", name: "Vector Unit", block: "C", row: 7, col: 5, year: "—", desc: "Single Instruction, Multiple Data (SIMD) ALU. The silicon primitive for parallel arithmetic.", bonds: ["Ma", "Sr"], whyHere: "Row 6 (Hardware): parallel compute logic. Compute." },
{ num: 79, sym: "An", name: "Analog ALU", block: "C", row: 7, col: 6, year: "—", desc: "Continuous-voltage compute unit (e.g., memristor, optical) for extremely low-power inference.", bonds: ["Ma"], whyHere: "Row 6 (Hardware): non-digital compute. Compute." },
{ num: 57, sym: "Ic", name: "Interconnect", block: "X", row: 7, col: 9, year: "—", desc: "The physical wiring moving data between silicon components (NoC, PCIe, NVLink).", bonds: ["Sr", "Dr", "Sy"], whyHere: "Row 6 (Hardware): device link. Communicate." },
{ num: 58, sym: "Rt", name: "HW Router", block: "X", row: 7, col: 10, year: "—", desc: "Silicon logic that directs packets across the physical interconnect.", bonds: ["Ic", "Ar"], whyHere: "Row 6 (Hardware): physical network logic. Communicate." },
{ num: 59, sym: "Ar", name: "Arbiter", block: "K", row: 7, col: 12, year: "—", desc: "Hardware logic that schedules instructions and manages contention.", bonds: ["Ma", "Ic", "Al"], whyHere: "Row 6 (Hardware): execution control. Control." },
{ num: 60, sym: "Ck", name: "Clock/Sync", block: "K", row: 7, col: 13, year: "—", desc: "The hardware primitive for temporal control, synchronization, and barriers.", bonds: ["Ar", "Ma"], whyHere: "Row 6 (Hardware): temporal control. Control." },
{ num: 84, sym: "Td", name: "Thermodynamics", block: "M", row: 7, col: 14, year: "—", desc: "The ultimate physical limitation (Landauer limit, thermal throttling) capping system scale.", bonds: ["Ew"], whyHere: "Row 6 (Hardware): thermal limit. Measure." },
{ num: 61, sym: "Ew", name: "Energy", block: "M", row: 7, col: 15, year: "—", desc: "The physical power consumed to perform computation (Joules/token).", bonds: ["Ma", "Dr"], whyHere: "Row 6 (Hardware): power metric. Measure." },
{ num: 62, sym: "As", name: "Artifact Store", block: "R", row: 8, col: 1, year: "—", desc: "Durable, distributed storage for trained models and datasets (S3, Model Registry).", bonds: ["Cp", "Dr", "Ex"], whyHere: "Row 7 (Production): persistent state. Represent." },
{ num: 63, sym: "Ex", name: "Exec Engine", block: "C", row: 8, col: 4, year: "—", desc: "The production worker node that executes compiled graphs on incoming requests.", bonds: ["As", "Bt", "Mq"], whyHere: "Row 7 (Production): execution loop. Compute." },
{ num: 64, sym: "Rp", name: "RPC Protocol", block: "X", row: 8, col: 9, year: "—", desc: "The synchronous network protocol for moving data between distributed services.", bonds: ["Ex", "Ld", "La"], whyHere: "Row 7 (Production): sync interface. Communicate." },
{ num: 65, sym: "Mq", name: "Msg Queue", block: "X", row: 8, col: 10, year: "—", desc: "The asynchronous network primitive for buffering and streaming data (Kafka).", bonds: ["Ex", "Rp"], whyHere: "Row 7 (Production): async interface. Communicate." },
{ num: 85, sym: "Rs", name: "Resilience", block: "K", row: 8, col: 11, year: "—", desc: "The systemic countermeasures (checkpointing, elastic recovery) for macroscopic hardware decay.", bonds: ["Oc"], whyHere: "Row 7 (Production): fault tolerance. Control." },
{ num: 66, sym: "Ld", name: "Load Balancer", block: "K", row: 8, col: 12, year: "—", desc: "The fleet-level control unit routing incoming requests to available hardware.", bonds: ["Rp", "Ex", "Oc"], whyHere: "Row 7 (Production): traffic control. Control." },
{ num: 67, sym: "Oc", name: "Orchestrator", block: "K", row: 8, col: 13, year: "—", desc: "The fleet-level control plane that scales, restarts, and manages the lifecycle of execution nodes (e.g., K8s).", bonds: ["Ld", "Av"], whyHere: "Row 7 (Production): fleet control loop. Control." },
{ num: 68, sym: "La", name: "Latency", block: "M", row: 8, col: 14, year: "—", desc: "The end-to-end time from user request to final response.", bonds: ["Ex", "Rp"], whyHere: "Row 7 (Production): time metric. Measure." },
{ num: 69, sym: "Av", name: "Availability", block: "M", row: 8, col: 15, year: "—", desc: "Service Level Agreement metric measuring uptime and fault tolerance.", bonds: ["La", "Oc"], whyHere: "Row 7 (Production): reliability metric. Measure." },
];
// Lookup map by symbol — last write wins (matches the original
// behavior for documented symbol collisions like Sm/Sp/Ro/En).
export const elMap: Record<string, Element> = {};
elements.forEach((e) => { elMap[e.sym] = e; });
// ── Compounds ─────────────────────────────────────────────────────────────
// Each formula is a list of typed tokens parsed from the YAML's formula
// string at sync time, so the React renderer never has to parse anything.
// sym tokens reference an element by symbol, with optional subscript.
// op tokens are literal connective text (→ ∥ ⇌ ↺ [ ] ( ) ?).
export type FormulaToken =
| { kind: "sym"; sym: string; sub?: string }
| { kind: "op"; text: string };
export interface Compound {
name: string;
formula: FormulaToken[];
}
export interface CompoundSection {
title: string;
hint?: string;
items: Compound[];
}
export const compounds: CompoundSection[] = [
{
title: "Core Architectures",
hint: "Fundamental end-to-end model structures",
items: [
{ name: "Transformer", formula: [{ kind: "sym", sym: "Eb" }, { kind: "op", text: " → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ" }] },
{ name: "Encoder-Decoder Transformer", formula: [{ kind: "sym", sym: "Eb" }, { kind: "op", text: " → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ_enc → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "At", sub: "cross" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ_dec → " }, { kind: "sym", sym: "Dd" }] },
{ name: "Vision Transformer (ViT)", formula: [{ kind: "sym", sym: "Tk", sub: "patch" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Eb" }, { kind: "op", text: " → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ → " }, { kind: "sym", sym: "Dd" }] },
{ name: "Multimodal (Whisper)", formula: [{ kind: "sym", sym: "Tk", sub: "audio" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Eb" }, { kind: "op", text: " → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ" }] },
{ name: "CNN", formula: [{ kind: "op", text: "[" }, { kind: "sym", sym: "Cv" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ac" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Po" }, { kind: "op", text: "]ᴺ → " }, { kind: "sym", sym: "Dd" }] },
{ name: "ResNet", formula: [{ kind: "sym", sym: "Eb" }, { kind: "op", text: " → [" }, { kind: "sym", sym: "Cv" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: "]ᴺ → " }, { kind: "sym", sym: "Po" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }] },
{ name: "LSTM", formula: [{ kind: "sym", sym: "Sp" }, { kind: "op", text: " → (" }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Gt" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Fb" }, { kind: "op", text: "(" }, { kind: "sym", sym: "Hs" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Ac" }] },
{ name: "State Space Model (SSM)", formula: [{ kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ac" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Fb" }, { kind: "op", text: "(" }, { kind: "sym", sym: "Hs" }, { kind: "op", text: ")" }] },
{ name: "Mamba (Selective SSM)", formula: [{ kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gt" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Fb" }, { kind: "op", text: "(" }, { kind: "sym", sym: "Hs" }, { kind: "op", text: ")" }] },
{ name: "GNN (Graph Neural Network)", formula: [{ kind: "sym", sym: "Tp" }, { kind: "op", text: " → " }, { kind: "sym", sym: "At" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Po" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }] },
],
},
{
title: "Structural & Training Patterns",
hint: "Reusable sub-blocks and paradigms",
items: [
{ name: "Linear Attention", formula: [{ kind: "sym", sym: "At" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Fc" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Fb" }, { kind: "op", text: "(" }, { kind: "sym", sym: "Hs" }, { kind: "op", text: ")" }] },
{ name: "Mixture of Experts (MoE)", formula: [{ kind: "sym", sym: "Ro" }, { kind: "op", text: " ? (" }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " ∥ … ∥ " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Gt" }] },
{ name: "Multi-Head Attention", formula: [{ kind: "sym", sym: "Dd" }, { kind: "op", text: " → (" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") ∥ … ∥ (" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Dd" }] },
{ name: "Batch Normalization", formula: [{ kind: "sym", sym: "Bt" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Nm" }] },
{ name: "Contrastive Learning (CLIP)", formula: [{ kind: "op", text: "(" }, { kind: "sym", sym: "Tk", sub: "img" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Tk", sub: "txt" }, { kind: "op", text: ") → (" }, { kind: "sym", sym: "Eb" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Eb" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ob", sub: "contrastive" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "Masked Autoencoder (MAE)", formula: [{ kind: "sym", sym: "Mk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Eb" }, { kind: "op", text: " → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ → " }, { kind: "sym", sym: "Ob" }] },
],
},
{
title: "Generative & Latent Models",
items: [
{ name: "Diffusion Model", formula: [{ kind: "op", text: "[" }, { kind: "sym", sym: "St" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → (" }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ac" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: ")]ᴺ → " }, { kind: "sym", sym: "Ob" }] },
{ name: "Diffusion Transformer (DiT)", formula: [{ kind: "sym", sym: "Tk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Eb" }, { kind: "op", text: " → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ → " }, { kind: "sym", sym: "St" }] },
{ name: "VAE", formula: [{ kind: "sym", sym: "Eb" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → (" }, { kind: "sym", sym: "Pr" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "St" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ob" }] },
{ name: "GAN", formula: [{ kind: "op", text: "(" }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ac" }, { kind: "op", text: ") ⇌ (" }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ac" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Ob" }] },
{ name: "World Model (JEPA/Sora)", formula: [{ kind: "sym", sym: "Eb" }, { kind: "op", text: " → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ → " }, { kind: "sym", sym: "Fb" }, { kind: "op", text: "(" }, { kind: "sym", sym: "Hs" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Ob" }] },
{ name: "Sparse Autoencoder (SAE)", formula: [{ kind: "sym", sym: "Hs" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ac" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sp" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ob" }] },
],
},
{
title: "Efficiency & Optimization",
items: [
{ name: "Knowledge Distillation", formula: [{ kind: "sym", sym: "Tp", sub: "teacher" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dv" }, { kind: "op", text: " ← " }, { kind: "sym", sym: "Tp", sub: "student" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gd" }] },
{ name: "Systolic Array (TPU Core)", formula: [{ kind: "op", text: "[" }, { kind: "sym", sym: "Ma" }, { kind: "op", text: " ↔ " }, { kind: "sym", sym: "Ic" }, { kind: "op", text: "]ᴺ" }] },
{ name: "Knowledge Distillation", formula: [{ kind: "sym", sym: "Tp", sub: "teacher" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dv" }, { kind: "op", text: " ← " }, { kind: "sym", sym: "Tp", sub: "student" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gd" }] },
{ name: "Systolic Array (TPU Core)", formula: [{ kind: "op", text: "[" }, { kind: "sym", sym: "Ma" }, { kind: "op", text: " ↔ " }, { kind: "sym", sym: "Ic" }, { kind: "op", text: "]ᴺ" }] },
{ name: "Flash Attention", formula: [{ kind: "sym", sym: "At" }, { kind: "op", text: " → (" }, { kind: "sym", sym: "Ti" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Fs" }, { kind: "op", text: ")" }] },
{ name: "LoRA", formula: [{ kind: "sym", sym: "Pm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Fc" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }] },
{ name: "Adam Optimizer", formula: [{ kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Os" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sc" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "Weight Averaging (SWA)", formula: [{ kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Wa" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "BitNet (1-bit LLM)", formula: [{ kind: "sym", sym: "Qz" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ac" }] },
{ name: "Quantization-Aware Training (QAT)", formula: [{ kind: "sym", sym: "Qz" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "Speculative Decoding", formula: [{ kind: "sym", sym: "St", sub: "draft" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Rw" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Bt" }] },
{ name: "Neural Architecture Search (NAS)", formula: [{ kind: "sym", sym: "Rw" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Tp" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ob" }] },
{ name: "Hyperparameter Optimization (HPO)", formula: [{ kind: "sym", sym: "Rw" }, { kind: "op", text: " → (" }, { kind: "sym", sym: "Sc" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Rg" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Ob" }] },
{ name: "DP-SGD (Differential Privacy)", formula: [{ kind: "op", text: "(" }, { kind: "sym", sym: "St" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Ct" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
],
},
{
title: "Alignment & Fine-Tuning",
items: [
{ name: "RLHF", formula: [{ kind: "sym", sym: "St" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ob", sub: "reward" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "DPO", formula: [{ kind: "op", text: "(" }, { kind: "sym", sym: "St" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "St" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Ob" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "PPO", formula: [{ kind: "sym", sym: "St" }, { kind: "op", text: " → (" }, { kind: "sym", sym: "Ob" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Ct" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "Chain-of-Thought (CoT)", formula: [{ kind: "sym", sym: "St" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Re" }, { kind: "op", text: "(" }, { kind: "sym", sym: "Hs" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Rw" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ob" }] },
{ name: "RAFT", formula: [{ kind: "op", text: "(" }, { kind: "sym", sym: "Eb" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Rw" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Cc" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "Prompt Tuning", formula: [{ kind: "sym", sym: "Eb", sub: "prompt" }, { kind: "op", text: " → [(" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Nm" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sk" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Dd" }, { kind: "op", text: "]ᴺ → " }, { kind: "sym", sym: "Dd" }] },
],
},
{
title: "Distributed & Scaling",
items: [
{ name: "Data Parallelism (DP)", formula: [{ kind: "sym", sym: "Bt" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sy" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "FSDP (Fully Sharded DP)", formula: [{ kind: "sym", sym: "Bt" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Fc" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sy" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Pm" }] },
{ name: "Pipeline Parallelism (PP)", formula: [{ kind: "sym", sym: "Pl" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sy" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Al" }] },
{ name: "Tensor Parallelism (TP)", formula: [{ kind: "sym", sym: "Fc" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sy" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Al" }] },
{ name: "Federated Learning", formula: [{ kind: "sym", sym: "Gd" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Wa" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Sy" }] },
{ name: "Model Merging / Ensembling", formula: [{ kind: "op", text: "(" }, { kind: "sym", sym: "Pm" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Pm" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Wa" }] },
],
},
{
title: "System & Production",
items: [
{ name: "RAG", formula: [{ kind: "sym", sym: "Eb" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Rw" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Cc" }, { kind: "op", text: " → (" }, { kind: "sym", sym: "At" }, { kind: "op", text: " ∥ " }, { kind: "sym", sym: "Mk" }, { kind: "op", text: ") → " }, { kind: "sym", sym: "Dd" }] },
{ name: "Inference Service", formula: [{ kind: "sym", sym: "Rp" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ld" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Ex" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Cc" }] },
{ name: "Feature Store", formula: [{ kind: "sym", sym: "As" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Cc" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Rp" }] },
{ name: "KV Cache", formula: [{ kind: "sym", sym: "At" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Cc" }] },
{ name: "Gradient Checkpointing", formula: [{ kind: "sym", sym: "Ad" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Cp" }, { kind: "op", text: " → " }, { kind: "sym", sym: "Al" }] },
],
},
];

1
periodic-table/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules/

36
periodic-table/Makefile Normal file
View File

@@ -0,0 +1,36 @@
# Periodic Table of ML Systems — build orchestration.
#
# table.yml is the single source of truth. Both index.html (the standalone
# shareable artifact) and the StaffML React data file are generated from it.
#
# Usage:
# make — same as `make all`
# make all — validate the YAML, regenerate index.html, sync the React TS file
# make html — regenerate periodic-table/index.html from table.yml
# make sync — regenerate interviews/staffml/src/data/periodicTable.ts from table.yml
# make validate — schema-check table.yml + run cross-reference invariants
# make clean — remove generated files (DESTRUCTIVE — only run after committing)
REPO := $(shell cd .. && pwd)
YAML := table.yml
HTML := index.html
TS := $(REPO)/interviews/staffml/src/data/periodicTable.ts
NODE := node
.PHONY: all html sync validate clean help
all: html sync
@echo "✓ Built $(HTML) and $(TS) from $(YAML)"
html:
@$(NODE) scripts/build-html.mjs
sync:
@$(NODE) $(REPO)/interviews/staffml/scripts/sync-periodic-table.mjs
validate:
@$(NODE) scripts/validate.mjs
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-12s %s\n", $$1, $$2}'

760
periodic-table/index.html Normal file
View File

@@ -0,0 +1,760 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Periodic Table of ML Systems</title>
<style>
:root {
--bg: #0f1117; --surface: #1a1d27; --surface2: #222636;
--text: #e8e8ed; --text-dim: #8b8fa3; --text-dimmer: #5d6177;
--border: #2d3044; --accent: #4fc3f7;
--represent: #42a5f5; --compute: #ef6c00; --communicate: #26a69a;
--control: #fdd835; --measure: #78909c; --frontier: #ffb74d;
}
* { margin:0; padding:0; box-sizing:border-box; }
body { font-family:'Inter',-apple-system,BlinkMacSystemFont,system-ui,sans-serif; background:var(--bg); color:var(--text); line-height:1.6; }
.container { max-width:1100px; margin:0 auto; padding:0 1.5rem; }
/* Hero */
.hero { text-align:center; padding:2.5rem 1rem 1rem; }
.hero-badge { display:inline-block; background:var(--surface); border:1px solid var(--border); border-radius:20px; padding:.25rem .8rem; font-size:.65rem; color:var(--accent); text-transform:uppercase; letter-spacing:.1em; font-weight:600; margin-bottom:.8rem; }
.hero h1 { font-size:2rem; font-weight:700; letter-spacing:-.03em; margin-bottom:.3rem; }
.hero h1 em { font-style:normal; color:var(--accent); }
.hero .lead { color:var(--text-dim); font-size:.92rem; max-width:660px; margin:0 auto; }
/* Concept */
.concept { padding:1.2rem 0 1.5rem; }
.concept h2 { font-size:1.05rem; font-weight:700; margin-bottom:.5rem; }
.concept p { color:var(--text-dim); font-size:.82rem; margin-bottom:.7rem; }
.concept strong { color:var(--text); }
.axes-box { display:grid; grid-template-columns:1fr 1fr; gap:.8rem; margin:1.2rem 0; }
.axis-card { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:1rem 1.2rem; }
.axis-card h3 { font-size:.82rem; font-weight:700; margin-bottom:.4rem; display:flex; align-items:center; gap:.4rem; }
.axis-card h3 .arr { color:var(--accent); }
.axis-card p { font-size:.75rem; color:var(--text-dim); margin-bottom:.4rem; }
.axis-rows { font-size:.7rem; color:var(--text-dim); line-height:1.7; }
.axis-rows span { color:var(--text); font-weight:600; }
.proof-box { background:var(--surface); border:1px solid var(--border); border-left:3px solid var(--accent); border-radius:8px; padding:.9rem 1.1rem; margin:1.2rem 0; }
.proof-box strong { color:var(--text); }
.proof-box p { font-size:.78rem; color:var(--text-dim); margin-bottom:.3rem; }
.proof-box p:last-child { margin-bottom:0; }
/* Controls */
.controls { display:flex; justify-content:center; padding:.3rem 1rem .6rem; }
.search-box { background:var(--surface); border:1px solid var(--border); border-radius:8px; padding:.45rem .9rem; color:var(--text); font-size:.82rem; width:280px; outline:none; }
.search-box:focus { border-color:var(--accent); }
.search-box::placeholder { color:var(--text-dimmer); }
.block-legend { display:flex; justify-content:center; gap:.8rem; padding:0 1rem .8rem; flex-wrap:wrap; }
.bl-item { display:flex; align-items:center; gap:.3rem; font-size:.7rem; color:var(--text-dim); cursor:pointer; padding:.2rem .5rem; border-radius:4px; user-select:none; }
.bl-item:hover,.bl-item.active { background:var(--surface); }
.bl-dot { width:10px; height:10px; border-radius:2px; }
/* Table */
.table-wrap { display:flex; justify-content:center; overflow-x:auto; padding:0 .5rem; }
.table-outer { display:flex; gap:0; }
.y-labels { display:flex; flex-direction:column; gap:3px; margin-right:6px; padding-top:26px; }
.y-lbl { height:58px; display:flex; align-items:center; justify-content:flex-end; padding-right:5px; font-size:.52rem; color:var(--text-dimmer); font-weight:600; text-transform:uppercase; letter-spacing:.04em; text-align:right; white-space:nowrap; }
.y-lbl-spacer { height:16px; }
.block-headers { display:flex; gap:3px; margin-bottom:2px; }
.bh { text-align:center; font-size:.5rem; font-weight:700; text-transform:uppercase; letter-spacing:.06em; padding:3px 0; border-radius:3px 3px 0 0; }
.periodic-grid { display:grid; grid-template-columns:repeat(15,58px); grid-template-rows:repeat(8,58px); gap:3px; }
/* Element */
.el { background:var(--surface); border:1px solid var(--border); border-radius:5px; padding:2px; cursor:pointer; display:flex; flex-direction:column; align-items:center; justify-content:center; position:relative; transition:all .15s; overflow:hidden; }
.el::before { content:''; position:absolute; top:0; left:0; width:100%; height:2px; background:var(--el-c); opacity:.7; }
.el:hover { transform:scale(1.18); z-index:10; border-color:var(--el-c); box-shadow:0 0 18px color-mix(in srgb,var(--el-c) 30%,transparent); }
.el.dimmed { opacity:.1; pointer-events:none; }
.el .num { font-size:.48rem; color:var(--text-dimmer); position:absolute; top:2px; left:4px; }
.el .sym { font-size:1.05rem; font-weight:700; color:var(--el-c); line-height:1.1; }
.el .nm { font-size:.42rem; color:var(--text-dim); text-align:center; line-height:1.15; }
.spacer-row { grid-column:1/-1; }
.series-label { display:flex; align-items:center; justify-content:center; font-size:.52rem; color:var(--frontier); font-weight:600; letter-spacing:.05em; text-transform:uppercase; grid-column:span 2; border:1px dashed color-mix(in srgb,var(--frontier) 40%,transparent); border-radius:5px; }
/* Detail Panel */
.overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:100; justify-content:center; align-items:center; padding:1rem; }
.overlay.open { display:flex; }
.panel { background:var(--surface); border:1px solid var(--border); border-radius:14px; padding:1.8rem; max-width:500px; width:100%; position:relative; animation:pop .2s ease; }
@keyframes pop { from{opacity:0;transform:translateY(14px) scale(.96)} to{opacity:1;transform:translateY(0) scale(1)} }
.close-btn { position:absolute; top:.7rem; right:1rem; background:none; border:none; color:var(--text-dim); font-size:1.4rem; cursor:pointer; }
.close-btn:hover { color:var(--text); }
.d-header { display:flex; align-items:flex-end; gap:.9rem; margin-bottom:1rem; padding-bottom:1rem; border-bottom:1px solid var(--border); }
.d-symbox { width:70px; height:70px; background:var(--bg); border:2px solid var(--el-c,var(--border)); border-radius:9px; display:flex; flex-direction:column; align-items:center; justify-content:center; flex-shrink:0; }
.d-symbox .sym { font-size:1.7rem; font-weight:700; }
.d-symbox .num { font-size:.6rem; color:var(--text-dim); }
.d-name { font-size:1.15rem; font-weight:700; }
.d-block-tag { font-size:.65rem; text-transform:uppercase; letter-spacing:.08em; font-weight:600; }
.d-meta { display:grid; grid-template-columns:1fr 1fr 1fr; gap:.5rem; margin-bottom:.9rem; }
.mi { background:var(--bg); border-radius:6px; padding:.45rem .6rem; }
.mi-label { font-size:.5rem; text-transform:uppercase; letter-spacing:.07em; color:var(--text-dimmer); }
.mi-val { font-size:.78rem; font-weight:600; }
.d-desc { font-size:.8rem; line-height:1.6; color:var(--text-dim); margin-bottom:.9rem; }
.d-why { background:var(--bg); border-radius:7px; padding:.6rem .8rem; font-size:.73rem; color:var(--text-dim); margin-bottom:.9rem; line-height:1.5; }
.d-why strong { color:var(--text); }
.d-bonds h4 { font-size:.6rem; text-transform:uppercase; letter-spacing:.07em; color:var(--text-dimmer); margin-bottom:.3rem; }
.bond-tags { display:flex; flex-wrap:wrap; gap:.3rem; }
.bond-tag { background:var(--bg); border:1px solid var(--border); border-radius:4px; padding:.12rem .4rem; font-size:.67rem; cursor:pointer; }
.bond-tag:hover { border-color:var(--accent); color:var(--accent); }
/* Compounds */
.compounds { padding:1.2rem 0 1.5rem; }
.compounds h2 { font-size:1.05rem; font-weight:700; margin-bottom:.8rem; }
.compound-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)); gap:.8rem; }
.c-card { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:1rem; cursor:pointer; transition:border-color .2s, box-shadow .2s; }
.c-card.c-highlighted { border-color:var(--accent); box-shadow:0 0 12px color-mix(in srgb, var(--accent) 25%, transparent); }
.c-card.c-dimmed { opacity:.15; }
.el.el-xref { border-color:var(--el-c); box-shadow:0 0 14px color-mix(in srgb, var(--el-c) 35%, transparent); animation:xpulse .6s ease; }
@keyframes xpulse { 0%{transform:scale(1)} 50%{transform:scale(1.12)} 100%{transform:scale(1)} }
.c-legend { display:flex; gap:1.2rem; margin-bottom:1.2rem; flex-wrap:wrap; justify-content:center; }
.cl-item { font-size:.65rem; color:var(--text-dim); display:flex; align-items:center; gap:.35rem; }
.cl-item span { color:var(--accent); font-weight:700; font-family:monospace; font-size:.85rem; }
.c-name { font-size:.85rem; font-weight:700; margin-bottom:.4rem; color:var(--accent); }
.c-formula { font-size:.7rem; font-family:monospace; background:var(--bg); padding:.4rem; border-radius:5px; color:var(--text-dim); }
.c-formula span { font-weight:600; position:relative; cursor:help; border-bottom:1px dotted color-mix(in srgb, var(--sym-color, var(--text-dim)) 50%, transparent); color:var(--sym-color, var(--text)); transition:opacity .15s; }
.c-formula span:hover { opacity:.8; }
.c-formula span .tip { display:none; position:absolute; bottom:calc(100% + 6px); left:50%; transform:translateX(-50%); background:var(--surface2); border:1px solid var(--border); border-radius:5px; padding:.25rem .5rem; font-size:.65rem; font-family:'Inter',-apple-system,sans-serif; white-space:nowrap; color:var(--text); z-index:50; pointer-events:none; box-shadow:0 4px 12px rgba(0,0,0,.4); }
.c-formula span:hover .tip { display:block; }
.footer { text-align:center; padding:2rem 1rem; color:var(--text-dimmer); font-size:.62rem; border-top:1px solid var(--border); }
.footer a { color:var(--accent); text-decoration:none; }
@media(max-width:768px) { .hero h1{font-size:1.4rem} .axes-box,.fq-grid{grid-template-columns:1fr} .fq.full{grid-column:1} }
</style>
</head>
<body>
<div class="hero">
<div class="hero-badge">v0.1 — 63 Elements · 49 Compounds</div>
<h1>The <em>Periodic Table</em> of Machine Learning Systems</h1>
<p class="lead">Two fundamental axes — abstraction layer and information-processing role — organize ML concepts the way electron shells and valence organize chemistry.</p>
</div>
<div class="container concept">
<h2>The Two Axes</h2>
<div class="axes-box">
<div class="axis-card">
<h3><span class="arr"></span> Rows: Abstraction Layer</h3>
<p>Like electron shells, each layer <strong>builds on and contains</strong> the ones above. You can't have optimization without a model, or deployment without hardware.</p>
<div class="axis-rows">
<span>1. Math</span> — primitives<br><span>2. Algorithms</span> — building blocks<br><span>3. Architecture</span> — structural patterns<br><span>4. Optimization</span> — efficiency techniques<br><span>5. Runtime</span> — systems infrastructure<br><span>6. Hardware</span> — physical compute<br><span>7. Production</span> — deployment &amp; ops
</div>
</div>
<div class="axis-card">
<h3><span class="arr"></span> Columns: Information-Processing Role</h3>
<p>From computer architecture and systems theory — five roles that exist in <strong>any</strong> information-processing system.</p>
<div class="axis-rows">
<span style="color:var(--represent)">Represent</span> — encodes, stores, structures<br><span style="color:var(--compute)">Compute</span> — transforms information<br><span style="color:var(--communicate)">Communicate</span> — moves between components<br><span style="color:var(--control)">Control</span> — decides what happens<br><span style="color:var(--measure)">Measure</span> — observes without transforming
</div>
</div>
</div>
<div class="proof-box">
<p><strong>The same-column test:</strong> Concepts in the same column genuinely behave alike.</p>
<p><strong style="color:var(--represent)">Represent:</strong> Tensor, Probability, Parameter, Embedding, Topology, Hidden State, Optimizer State, Caching, Checkpointing, SRAM, DRAM, Blob Storage — all hold and structure information across all 7 layers.</p>
<p><strong style="color:var(--compute)">Compute:</strong> Operator, Activation, Dense Dot, Convolution, Pooling, Attention, Routing, Quantization, Fusion, Tiling, Compilation — all transform inputs to outputs.</p>
<p><strong style="color:var(--communicate)">Communicate:</strong> Chain Rule, Backprop, Tokenization, Skip/Res, Distillation, Weight Averaging, Pipelining, Sync, Prefetching, Interconnect, RPC Protocol — all move information between components without deciding what to do with it.</p>
<p><strong style="color:var(--control)">Control:</strong> Objective, Constraint, Grad Descent, Search, Initialization, Masking, Scheduling, Regularization, Allocation, Arbiter, Load Balancer, Telemetry — all make decisions that govern system behavior.</p>
<p><strong style="color:var(--measure)">Measure:</strong> Entropy, Loss Function, Receptive Field, Info Density, Throughput, Energy, Latency — all observe without changing. Noble gases.</p>
</div>
</div>
<div class="container">
<div class="controls"><input type="text" class="search-box" id="search" placeholder="Search elements..."></div>
<div class="block-legend" id="legend"></div>
</div>
<div class="table-wrap">
<div class="table-outer">
<div class="y-labels" id="yLabels"></div>
<div>
<div class="block-headers" id="blockHeaders"></div>
<div class="periodic-grid" id="grid"></div>
</div>
</div>
</div>
<div class="container compounds">
<style>
.compounds h3 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-dimmer); margin: 1.5rem 0 0.8rem; border-bottom: 1px solid var(--border); padding-bottom: 0.3rem; }
.isomer-hint { color: var(--accent); font-size: 0.65rem; font-weight: normal; margin-left: 0.5rem; text-transform: none; }
</style>
<h2>Molecular ML (Compounds)</h2>
<p style="color:var(--text-dim); font-size:.82rem; margin-bottom:.8rem;">Just as H₂O = hydrogen + oxygen, every ML system decomposes into primitives from the table above. Read each formula left to right — symbols are element codes, operators show how they bond.</p>
<div class="c-legend">
<div class="cl-item"><span></span> Sequential</div>
<div class="cl-item"><span></span> Parallel</div>
<div class="cl-item"><span>?</span> Conditional</div>
<div class="cl-item"><span></span> Adversarial</div>
<div class="cl-item"><span></span> Feedback Loop</div>
<div class="cl-item"><span>[ ]ᴺ</span> Repeated Block</div>
</div>
<!-- @gen:compounds -->
<h3>Core Architectures <span class="isomer-hint">Fundamental end-to-end model structures</span></h3>
<div class="compound-grid">
<div class="c-card">
<div class="c-name">Transformer</div>
<div class="c-formula"><span>Eb</span> → [(<span>At</span><span>Mk</span>) → <span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ</div>
</div>
<div class="c-card">
<div class="c-name">Encoder-Decoder Transformer</div>
<div class="c-formula"><span>Eb</span> → [(<span>At</span><span>Mk</span>) → <span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ<sub>enc</sub> → [(<span>At</span><span>Mk</span>) → <span>At</span><sub>cross</sub><span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ<sub>dec</sub><span>Dd</span></div>
</div>
<div class="c-card">
<div class="c-name">Vision Transformer (ViT)</div>
<div class="c-formula"><span>Tk</span><sub>patch</sub><span>Eb</span> → [(<span>At</span><span>Mk</span>) → <span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ → <span>Dd</span></div>
</div>
<div class="c-card">
<div class="c-name">Multimodal (Whisper)</div>
<div class="c-formula"><span>Tk</span><sub>audio</sub><span>Eb</span> → [(<span>At</span><span>Mk</span>) → <span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ</div>
</div>
<div class="c-card">
<div class="c-name">CNN</div>
<div class="c-formula">[<span>Cv</span><span>Ac</span><span>Po</span>]ᴺ → <span>Dd</span></div>
</div>
<div class="c-card">
<div class="c-name">ResNet</div>
<div class="c-formula"><span>Eb</span> → [<span>Cv</span><span>Nm</span><span>Sk</span>]ᴺ → <span>Po</span><span>Dd</span></div>
</div>
<div class="c-card">
<div class="c-name">LSTM</div>
<div class="c-formula"><span>Sp</span> → (<span>Dd</span><span>Dd</span>) → <span>Gt</span><span>Fb</span>(<span>Hs</span>) → <span>Ac</span></div>
</div>
<div class="c-card">
<div class="c-name">State Space Model (SSM)</div>
<div class="c-formula"><span>Dd</span><span>Ac</span><span>Fb</span>(<span>Hs</span>)</div>
</div>
<div class="c-card">
<div class="c-name">Mamba (Selective SSM)</div>
<div class="c-formula"><span>Dd</span><span>Gt</span><span>Fb</span>(<span>Hs</span>)</div>
</div>
<div class="c-card">
<div class="c-name">GNN (Graph Neural Network)</div>
<div class="c-formula"><span>Tp</span><span>At</span><span>Po</span><span>Dd</span></div>
</div>
</div>
<h3>Structural &amp; Training Patterns <span class="isomer-hint">Reusable sub-blocks and paradigms</span></h3>
<div class="compound-grid">
<div class="c-card">
<div class="c-name">Linear Attention</div>
<div class="c-formula"><span>At</span><span>Fc</span><span>Fb</span>(<span>Hs</span>)</div>
</div>
<div class="c-card">
<div class="c-name">Mixture of Experts (MoE)</div>
<div class="c-formula"><span>Ro</span> ? (<span>Dd</span> ∥ … ∥ <span>Dd</span>) → <span>Gt</span></div>
</div>
<div class="c-card">
<div class="c-name">Multi-Head Attention</div>
<div class="c-formula"><span>Dd</span> → (<span>At</span><span>Mk</span>) ∥ … ∥ (<span>At</span><span>Mk</span>) → <span>Dd</span></div>
</div>
<div class="c-card">
<div class="c-name">Batch Normalization</div>
<div class="c-formula"><span>Bt</span><span>Nm</span></div>
</div>
<div class="c-card">
<div class="c-name">Contrastive Learning (CLIP)</div>
<div class="c-formula">(<span>Tk</span><sub>img</sub><span>Tk</span><sub>txt</sub>) → (<span>Eb</span><span>Eb</span>) → <span>Dd</span><span>Ob</span><sub>contrastive</sub><span>Gd</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">Masked Autoencoder (MAE)</div>
<div class="c-formula"><span>Mk</span><span>Eb</span> → [(<span>At</span><span>Mk</span>) → <span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ → <span>Ob</span></div>
</div>
</div>
<h3>Generative &amp; Latent Models</h3>
<div class="compound-grid">
<div class="c-card">
<div class="c-name">Diffusion Model</div>
<div class="c-formula">[<span>St</span><span>Nm</span> → (<span>Dd</span><span>Ac</span><span>Sk</span>)]ᴺ → <span>Ob</span></div>
</div>
<div class="c-card">
<div class="c-name">Diffusion Transformer (DiT)</div>
<div class="c-formula"><span>Tk</span><span>Eb</span> → [(<span>At</span><span>Mk</span>) → <span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ → <span>St</span></div>
</div>
<div class="c-card">
<div class="c-name">VAE</div>
<div class="c-formula"><span>Eb</span><span>Dd</span> → (<span>Pr</span><span>St</span>) → <span>Dd</span><span>Ob</span></div>
</div>
<div class="c-card">
<div class="c-name">GAN</div>
<div class="c-formula">(<span>Dd</span><span>Ac</span>) ⇌ (<span>Dd</span><span>Ac</span>) → <span>Ob</span></div>
</div>
<div class="c-card">
<div class="c-name">World Model (JEPA/Sora)</div>
<div class="c-formula"><span>Eb</span> → [(<span>At</span><span>Mk</span>) → <span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ → <span>Fb</span>(<span>Hs</span>) → <span>Ob</span></div>
</div>
<div class="c-card">
<div class="c-name">Sparse Autoencoder (SAE)</div>
<div class="c-formula"><span>Hs</span><span>Dd</span><span>Ac</span><span>Sp</span><span>Dd</span><span>Ob</span></div>
</div>
</div>
<h3>Efficiency &amp; Optimization</h3>
<div class="compound-grid">
<div class="c-card">
<div class="c-name">Knowledge Distillation</div>
<div class="c-formula"><span>Tp</span><sub>teacher</sub><span>Dv</span><span>Tp</span><sub>student</sub><span>Gd</span></div>
</div>
<div class="c-card">
<div class="c-name">Systolic Array (TPU Core)</div>
<div class="c-formula">[<span>Ma</span><span>Ic</span>]ᴺ</div>
</div>
<div class="c-card">
<div class="c-name">Knowledge Distillation</div>
<div class="c-formula"><span>Tp</span><sub>teacher</sub><span>Dv</span><span>Tp</span><sub>student</sub><span>Gd</span></div>
</div>
<div class="c-card">
<div class="c-name">Systolic Array (TPU Core)</div>
<div class="c-formula">[<span>Ma</span><span>Ic</span>]ᴺ</div>
</div>
<div class="c-card">
<div class="c-name">Flash Attention</div>
<div class="c-formula"><span>At</span> → (<span>Ti</span><span>Fs</span>)</div>
</div>
<div class="c-card">
<div class="c-name">LoRA</div>
<div class="c-formula"><span>Pm</span><span>Fc</span><span>Dd</span></div>
</div>
<div class="c-card">
<div class="c-name">Adam Optimizer</div>
<div class="c-formula"><span>Gd</span><span>Os</span><span>Sc</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">Weight Averaging (SWA)</div>
<div class="c-formula"><span>Gd</span><span>Wa</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">BitNet (1-bit LLM)</div>
<div class="c-formula"><span>Qz</span><span>Dd</span><span>Ac</span></div>
</div>
<div class="c-card">
<div class="c-name">Quantization-Aware Training (QAT)</div>
<div class="c-formula"><span>Qz</span><span>Gd</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">Speculative Decoding</div>
<div class="c-formula"><span>St</span><sub>draft</sub><span>Rw</span><span>Bt</span></div>
</div>
<div class="c-card">
<div class="c-name">Neural Architecture Search (NAS)</div>
<div class="c-formula"><span>Rw</span><span>Tp</span><span>Ob</span></div>
</div>
<div class="c-card">
<div class="c-name">Hyperparameter Optimization (HPO)</div>
<div class="c-formula"><span>Rw</span> → (<span>Sc</span><span>Rg</span>) → <span>Ob</span></div>
</div>
<div class="c-card">
<div class="c-name">DP-SGD (Differential Privacy)</div>
<div class="c-formula">(<span>St</span><span>Ct</span>) → <span>Gd</span><span>Pm</span></div>
</div>
</div>
<h3>Alignment &amp; Fine-Tuning</h3>
<div class="compound-grid">
<div class="c-card">
<div class="c-name">RLHF</div>
<div class="c-formula"><span>St</span><span>Ob</span><sub>reward</sub><span>Gd</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">DPO</div>
<div class="c-formula">(<span>St</span><span>St</span>) → <span>Ob</span><span>Gd</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">PPO</div>
<div class="c-formula"><span>St</span> → (<span>Ob</span><span>Ct</span>) → <span>Gd</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">Chain-of-Thought (CoT)</div>
<div class="c-formula"><span>St</span><span>Re</span>(<span>Hs</span>) → <span>Rw</span><span>Ob</span></div>
</div>
<div class="c-card">
<div class="c-name">RAFT</div>
<div class="c-formula">(<span>Eb</span><span>Rw</span><span>Cc</span>) → <span>Gd</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">Prompt Tuning</div>
<div class="c-formula"><span>Eb</span><sub>prompt</sub> → [(<span>At</span><span>Mk</span>) → <span>Nm</span><span>Sk</span><span>Dd</span>]ᴺ → <span>Dd</span></div>
</div>
</div>
<h3>Distributed &amp; Scaling</h3>
<div class="compound-grid">
<div class="c-card">
<div class="c-name">Data Parallelism (DP)</div>
<div class="c-formula"><span>Bt</span><span>Gd</span><span>Sy</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">FSDP (Fully Sharded DP)</div>
<div class="c-formula"><span>Bt</span><span>Fc</span><span>Gd</span><span>Sy</span><span>Pm</span></div>
</div>
<div class="c-card">
<div class="c-name">Pipeline Parallelism (PP)</div>
<div class="c-formula"><span>Pl</span><span>Sy</span><span>Al</span></div>
</div>
<div class="c-card">
<div class="c-name">Tensor Parallelism (TP)</div>
<div class="c-formula"><span>Fc</span><span>Sy</span><span>Al</span></div>
</div>
<div class="c-card">
<div class="c-name">Federated Learning</div>
<div class="c-formula"><span>Gd</span><span>Wa</span><span>Sy</span></div>
</div>
<div class="c-card">
<div class="c-name">Model Merging / Ensembling</div>
<div class="c-formula">(<span>Pm</span><span>Pm</span>) → <span>Wa</span></div>
</div>
</div>
<h3>System &amp; Production</h3>
<div class="compound-grid">
<div class="c-card">
<div class="c-name">RAG</div>
<div class="c-formula"><span>Eb</span><span>Rw</span><span>Cc</span> → (<span>At</span><span>Mk</span>) → <span>Dd</span></div>
</div>
<div class="c-card">
<div class="c-name">Inference Service</div>
<div class="c-formula"><span>Rp</span><span>Ld</span><span>Ex</span><span>Cc</span></div>
</div>
<div class="c-card">
<div class="c-name">Feature Store</div>
<div class="c-formula"><span>As</span><span>Cc</span><span>Rp</span></div>
</div>
<div class="c-card">
<div class="c-name">KV Cache</div>
<div class="c-formula"><span>At</span><span>Cc</span></div>
</div>
<div class="c-card">
<div class="c-name">Gradient Checkpointing</div>
<div class="c-formula"><span>Ad</span><span>Cp</span><span>Al</span></div>
</div>
</div>
<!-- @end:compounds -->
</div>
<div class="overlay" id="overlay">
<div class="panel"><button class="close-btn" id="closeBtn">&times;</button><div id="panelContent"></div></div>
</div>
<div class="footer">A project of <a href="https://mlsysbook.ai">Machine Learning Systems</a> — Harvard CS249r &middot; Vijay Janapa Reddi &middot; 2025</div>
<script>
/* @gen:blocks */
const blocks = {
R: { name:'Represent', sub:'What holds information', color:'#42a5f5', cols:[1,2] },
C: { name:'Compute', sub:'What transforms', color:'#ef6c00', cols:[3,4,5,6,7,8] },
X: { name:'Communicate', sub:'What moves', color:'#26a69a', cols:[9,10,11] },
K: { name:'Control', sub:'What decides', color:'#fdd835', cols:[12,13,14] },
M: { name:'Measure', sub:'What observes', color:'#78909c', cols:[15] },
};
const rowLabels = ['Data','Math','Algorithms','Architecture','Optimization','Runtime','Hardware','Production'];
/* @end:blocks */
/* @gen:elements */
// [num, sym, name, block, row, col, year, desc, bonds[], whyHere]
const elements = [
// Row 0: Data (The Raw Material)
[70,'Rc','Record','R',1,1,'—','The fundamental atomic unit of raw information (a single row, image, or document).',[],'Row 0 (Data): the raw state. Represent.'],
[71,'Ds','Dataset','R',1,2,'—','A structured collection of records.',['Rc','Sm'],'Row 0 (Data): the collective state. Represent.'],
[72,'Tr','Transform','C',1,4,'—','The deterministic action of altering raw data (cropping, resizing, parsing).',['Rc'],'Row 0 (Data): raw manipulation. Compute.'],
[73,'Ag','Aggregate','C',1,5,'—','Combining multiple records into summary statistics.',['Ds'],'Row 0 (Data): statistical manipulation. Compute.'],
[74,'Fl','Flow/Stream','X',1,9,'—','The continuous movement of raw data from source to system (ETL, Kafka).',['Rc','Ds'],'Row 0 (Data): data pipeline. Communicate.'],
[75,'Fm','Format','X',1,10,'—','The structural encoding of data for storage or transit (Parquet, TFRecord).',['Rc','Fl'],'Row 0 (Data): serialization. Communicate.'],
[76,'Fi','Filter','K',1,12,'—','The deterministic logic that includes or excludes records based on predicates.',['Rc','Tr'],'Row 0 (Data): data gating. Control.'],
[77,'Sm','Schema','K',1,13,'—','The structural constraint defining the expected types and fields of a record.',['Rc','Ds'],'Row 0 (Data): type constraint. Control.'],
[80,'En','Entropy','M',1,14,'1948','The Shannon information-theoretic limit; the absolute bound on data compressibility.',['Vl'],'Row 0 (Data): information limit. Measure.'],
[78,'Vl','Volume','M',1,15,'—','The physical size or cardinality of the dataset (Bytes, Row Count).',['Ds'],'Row 0 (Data): scale metric. Measure.'],
// Row 1: Math (The Theoretical Bedrock)
[1,'Tn','Tensor','R',2,1,'—','The fundamental mathematical structure holding information (scalars, vectors, matrices).',['Op','Cr','Ob'],'Row 1 (Math): most primitive object. Represent: it IS information.'],
[2,'Pr','Probability','R',2,2,'1654','The mathematical primitive for representing uncertainty — distributions, densities.',['Tn','Dv','Ob'],'Row 1 (Math): uncertain state. Represent: encodes beliefs.'],
[87,'St','State','R',2,3,'—','The mathematical representation of an environment or context (RL, SSMs).',['Ob'],'Row 1 (Math): contextual state. Represent.'],
[3,'Op','Operator','C',2,4,'—','The mathematical action of mapping one space to another (linear or non-linear transforms).',['Tn'],'Row 1 (Math): pure transformation. Compute: transforms spaces.'],
[4,'Cr','Chain Rule','X',2,9,'1676','The fundamental mathematical mechanism that allows composed derivatives to be computed.',['Op'],'Row 1 (Math): derivative composition. Communicate: basis for error flow.'],
[5,'Ob','Objective','K',2,12,'—','The mathematical formulation of the goal (Argmin/Argmax).',['Cr','Dv'],'Row 1 (Math): the goal state. Control: defines "better" or "worse".'],
[6,'Cs','Constraint','K',2,13,'—','The mathematical primitive for defining bounds and restrictions on variables.',['Ob'],'Row 1 (Math): solution space restriction. Control: hard boundaries.'],
[7,'Dv','Divergence','M',2,15,'—','The mathematical quantification of distance between distributions or tensors (e.g., KL, L2).',['Tn','Pr'],'Row 1 (Math): information measure. Measure: quantifies difference.'],
// Row 2: Algorithms (The Operations)
[8,'Pm','Parameter','R',3,1,'—','The irreducible learned memory or state of an algorithm (weights, biases).',['Dd','Cv','Gd'],'Row 2 (Algorithm): learned state. Represent: instantiation of math state.'],
[9,'Eb','Embedding','R',3,2,'—','The fundamental algorithmic act of mapping a discrete symbol into continuous space.',['Tn','Dd'],'Row 2 (Algorithm): discrete-to-continuous mapping.'],
[10,'Sp','Sample','R',3,3,'—','The irreducible unit of empirical data distribution (a single data point).',['Eb','Lf'],'Row 2 (Algorithm): data representation. Represent: the input unit.'],
[11,'Dd','Dense Dot','C',3,4,'—','The irreducible algorithm for fully connected, all-to-all information transformation.',['Pm'],'Row 2 (Algorithm): all-to-all transform. Compute.'],
[12,'Cv','Convolution','C',3,5,'—','The irreducible algorithm for local, weight-shared spatial information transformation.',['Pm'],'Row 2 (Algorithm): local transform. Compute.'],
[13,'Po','Pooling','C',3,6,'—','The algorithmic primitive for spatial or temporal reduction (Max, Average).',['Cv','Dd'],'Row 2 (Algorithm): primitive operation. Compute.'],
[14,'Sm','Sampling','C',3,7,'—','The primitive for stochastic selection from a probability distribution.',['Pr'],'Row 2 (Algorithm): primitive operation. Compute.'],
[86,'Ac','Activation','C',3,8,'—','Non-linear functions (ReLU, GELU) providing expressive power.',['Dd'],'Row 2 (Algorithm): non-linear transform. Compute.'],
[15,'Ad','Autodiff','X',3,9,'1970','The algorithmic primitive that mechanically computes exact derivatives through arbitrary control flow.',['Cr','Pm'],'Row 2 (Algorithm): error routing. Communicate.'],
[16,'Tk','Tokenization','X',3,10,'—','Segmenting raw input into discrete processing units.',['Eb'],'Row 2 (Algorithm): input segmentation. Communicate.'],
[90,'Ct','Critic','K',3,11,'—','The value function evaluating the expected return of a state (Actor-Critic RL).',['St','Gd'],'Row 2 (Algorithm): evaluative model. Control.'],
[17,'Gd','Grad Descent','K',3,12,'1847','The core control loop: takes communicated gradients and updates Parameters.',['Ad','Pm','Lf'],'Row 2 (Algorithm): update mechanism. Control.'],
[18,'Rw','Reward','K',3,13,'—','A scalar control signal evaluating the quality of an action (RL).',['Sp','Gd'],'Row 2 (Algorithm): evaluative signal. Control.'],
[19,'Iz','Initialization','K',3,14,'—','The algorithmic control for setting the starting state of parameters.',['Pm','Pr'],'Row 2 (Algorithm): starting state control. Control.'],
[20,'Lf','Loss Function','M',3,15,'—','The specific algorithmic computation of the mathematical distance (e.g., Cross-Entropy).',['Dv','Gd'],'Row 2 (Algorithm): algorithmic measure. Measure.'],
// Row 3: Architecture (The Topologies)
[21,'Tp','Topology','R',4,1,'—','The fundamental structural assumption placed on data (Sequence, Grid, Graph).',['At','Gt','Cv'],'Row 3 (Architecture): data structure. Represent.'],
[22,'Hs','Hidden State','R',4,2,'—','The architectural primitive for persistent intermediate representation.',['Fb','At','Gt'],'Row 3 (Architecture): structural memory. Represent.'],
[81,'Ix','Indexing','R',4,3,'—','The high-dimensional partitioning of vector space (e.g., HNSW) for sub-linear retrieval.',['Tp'],'Row 3 (Architecture): structured retrieval. Represent.'],
[23,'At','Attention','C',4,4,'—','Letting data dynamically decide which other data it interacts with.',['Mk'],'Row 3 (Architecture): dynamic routing. Compute.'],
[24,'Gt','Gating','C',4,5,'—','Using data to scale or shut off other data (Multiplicative flow).',['Tn'],'Row 3 (Architecture): conditional flow. Compute.'],
[25,'Nm','Normalization','C',4,6,'—','The transform that re-centers and re-scales data distributions between layers.',['Tn','Pm'],'Row 3 (Architecture): distribution transform. Compute.'],
[26,'Ro','Routing','C',4,7,'—','Conditional data direction to specific sub-units (e.g., Experts).',['Gt','Mk','Dd'],'Row 3 (Architecture): conditional flow. Compute.'],
[27,'Sk','Skip/Res','X',4,9,'—','The fundamental primitive of identity mapping. Allows information to bypass computation.',['Tp'],'Row 3 (Architecture): information highway. Communicate.'],
[28,'Fb','Feedback','X',4,10,'—','The structural primitive of routing a signal backward in the graph (Recurrence).',['Hs','Tp'],'Row 3 (Architecture): temporal loop. Communicate.'],
[29,'Mk','Masking','K',4,12,'—','The structural enforcement of causality or prevention of information leakage.',['At','Tp'],'Row 3 (Architecture): structural constraint. Control.'],
[82,'Ro','Routing','K',4,13,'—','The dynamic, data-dependent dispatch of tensors (e.g., Mixture of Experts).',['Gt'],'Row 3 (Architecture): dynamic flow. Control.'],
[30,'Rf','Receptive Fld','M',4,15,'—','The measurement of how far information can travel within the architecture in one pass.',['Tp','At','Cv'],'Row 3 (Architecture): spatial/temporal reach. Measure.'],
// Row 4: Optimization (The Physics of Efficiency)
[31,'Fc','Factorization','R',5,1,'—','Approximating a massive matrix as the product of smaller ones (Low-Rank).',['Pm','Qz','Sp'],'Row 4 (Optimization): rank reduction. Represent.'],
[32,'Os','Optim State','R',5,2,'—','The irreducible memory of the optimization process (momentum, velocity).',['Gd','Sc','Pm'],'Row 4 (Optimization): optimization memory. Represent.'],
[33,'Qz','Quantization','C',5,4,'—','Reducing the bit-width of numbers (FP8, INT4).',['Fc','Sp','Ws'],'Row 4 (Optimization): precision reduction. Compute.'],
[34,'Sp','Sparsification','C',5,5,'—','Turning dense compute sparse by forcing weights or activations to zero.',['Fc','Qz','Rg'],'Row 4 (Optimization): density reduction. Compute.'],
[89,'Wa','Weight Avg','C',5,8,'—','Averaging model weights across time or distributed workers (e.g., SWA, EMA).',['Pm'],'Row 4 (Optimization): parameter smoothing. Compute.'],
[35,'Ws','Weight Sharing','X',5,9,'1980s','The structural optimization of communicating the same learned state across multiple functional paths (e.g., CNNs).',['Pm','Tp'],'Row 4 (Optimization): state reuse. Communicate.'],
[36,'En','Ensembling','X',5,10,'—','Merging weights or outputs across time/workers to improve generalization (SWA).',['Pm','Gd','Ws'],'Row 4 (Optimization): spatial/temporal merging. Communicate.'],
[88,'Re','Retrieve','X',5,11,'—','Fetching stored state or external knowledge (e.g., from a KV Cache or Vector DB).',['Hs'],'Row 4 (Optimization): state retrieval. Communicate.'],
[37,'Sc','Scheduling','K',5,12,'—','Dynamically decaying or modulating control signals over time.',['Gd','Rg'],'Row 4 (Optimization): dynamic modulation. Control.'],
[38,'Rg','Regularization','K',5,13,'—','The structural penalty applied to the objective to force simpler solutions.',['Sc','Sp','Ob'],'Row 4 (Optimization): complexity penalty. Control.'],
[39,'Tm','Termination','K',5,14,'—','The control primitive that evaluates conditions to halt an iterative optimization loop.',['Gd','Lf'],'Row 4 (Optimization): temporal bound. Control.'],
[40,'Id','Info Density','M',5,15,'—','The measure of optimization efficiency (Bits per Parameter).',['Qz','Fc','Sp'],'Row 4 (Optimization): compression metric. Measure.'],
// Row 5: Runtime (Software Execution Primitives)
[41,'Cc','Caching','R',6,1,'—','Holding intermediate state in fast memory to prevent recomputation (e.g., KV Cache).',['At','Bt','Pl'],'Row 5 (Runtime): state persistence. Represent.'],
[42,'Cp','Checkpointing','R',6,2,'—','Saving and restoring model state for fault tolerance or memory efficiency.',['Pm','As','Al'],'Row 5 (Runtime): state persistence. Represent.'],
[43,'Ir','Int. Rep.','R',6,3,'—','The software state of a computation graph before hardware execution (ONNX, PT2).',['Cl','Fs'],'Row 5 (Runtime): structural state. Represent.'],
[44,'Fs','Fusion','C',6,4,'—','Merging multiple operations into a single execution kernel to minimize memory IO.',['Op','At','Pl'],'Row 5 (Runtime): op merging. Compute.'],
[45,'Bt','Batching','C',6,5,'—','Grouping independent inputs for parallel processing.',['Cc','Dd','Pl'],'Row 5 (Runtime): request grouping. Compute.'],
[46,'Ti','Tiling','C',6,6,'—','Partitioning computation into sub-blocks to optimize for memory hierarchy.',['Ma','Sr','Fs'],'Row 5 (Runtime): compute partitioning. Compute.'],
[47,'Cl','Compilation','C',6,7,'—','Lowering high-level operators into hardware-executable kernels.',['Ir','Fs','Ti'],'Row 5 (Runtime): graph-to-kernel translation. Compute.'],
[83,'Vr','Virtualization','R',6,8,'—','The abstraction of physical memory via page tables (e.g., PagedAttention) to solve fragmentation.',['Cc'],'Row 5 (Runtime): memory mapping. Represent.'],
[48,'Pl','Pipelining','X',6,9,'—','Overlapping the execution of sequential stages across different compute units.',['Bt','Sy','Al'],'Row 5 (Runtime): stage scheduling. Communicate.'],
[49,'Sy','Sync / Coll','X',6,10,'—','Aggregating and broadcasting state across distributed devices.',['Ad','Gd','Pl'],'Row 5 (Runtime): gradient/state sync. Communicate.'],
[50,'Pf','Prefetching','X',6,11,'—','Proactively moving data into faster memory tiers before it is needed.',['Ic','Dr','Pl'],'Row 5 (Runtime): data anticipation. Communicate.'],
[51,'Al','Allocation','K',6,12,'—','The dynamic assignment of hardware resources to software tasks.',['Cc','Cp','Ar'],'Row 5 (Runtime): resource control. Control.'],
[52,'Ut','Utilization','M',6,15,'—','The percentage of theoretical hardware capacity actively used (MFU).',['Bt','Fs'],'Row 5 (Runtime): efficiency metric. Measure.'],
// Row 6: Hardware (Silicon Primitives)
[53,'Sr','SRAM','R',7,1,'—','On-chip, low-capacity, extremely high-bandwidth memory (Registers, Scratchpads).',['Cc','Ma','Ic'],'Row 6 (Hardware): fast state. Represent.'],
[54,'Dr','DRAM','R',7,2,'—','Off-chip, high-capacity, lower-bandwidth memory (HBM, DDR).',['Cp','Sr','Ic'],'Row 6 (Hardware): bulk state. Represent.'],
[55,'Ma','MAC Unit','C',7,4,'—','Multiply-Accumulate unit. The fundamental silicon logic gate for tensor math.',['Sr','Dd','Vu'],'Row 6 (Hardware): arithmetic logic. Compute.'],
[56,'Vu','Vector Unit','C',7,5,'—','Single Instruction, Multiple Data (SIMD) ALU. The silicon primitive for parallel arithmetic.',['Ma','Sr'],'Row 6 (Hardware): parallel compute logic. Compute.'],
[79,'An','Analog ALU','C',7,6,'—','Continuous-voltage compute unit (e.g., memristor, optical) for extremely low-power inference.',['Ma'],'Row 6 (Hardware): non-digital compute. Compute.'],
[57,'Ic','Interconnect','X',7,9,'—','The physical wiring moving data between silicon components (NoC, PCIe, NVLink).',['Sr','Dr','Sy'],'Row 6 (Hardware): device link. Communicate.'],
[58,'Rt','HW Router','X',7,10,'—','Silicon logic that directs packets across the physical interconnect.',['Ic','Ar'],'Row 6 (Hardware): physical network logic. Communicate.'],
[59,'Ar','Arbiter','K',7,12,'—','Hardware logic that schedules instructions and manages contention.',['Ma','Ic','Al'],'Row 6 (Hardware): execution control. Control.'],
[60,'Ck','Clock/Sync','K',7,13,'—','The hardware primitive for temporal control, synchronization, and barriers.',['Ar','Ma'],'Row 6 (Hardware): temporal control. Control.'],
[84,'Td','Thermodynamics','M',7,14,'—','The ultimate physical limitation (Landauer limit, thermal throttling) capping system scale.',['Ew'],'Row 6 (Hardware): thermal limit. Measure.'],
[61,'Ew','Energy','M',7,15,'—','The physical power consumed to perform computation (Joules/token).',['Ma','Dr'],'Row 6 (Hardware): power metric. Measure.'],
// Row 7: Production (Fleet Primitives)
[62,'As','Artifact Store','R',8,1,'—','Durable, distributed storage for trained models and datasets (S3, Model Registry).',['Cp','Dr','Ex'],'Row 7 (Production): persistent state. Represent.'],
[63,'Ex','Exec Engine','C',8,4,'—','The production worker node that executes compiled graphs on incoming requests.',['As','Bt','Mq'],'Row 7 (Production): execution loop. Compute.'],
[64,'Rp','RPC Protocol','X',8,9,'—','The synchronous network protocol for moving data between distributed services.',['Ex','Ld','La'],'Row 7 (Production): sync interface. Communicate.'],
[65,'Mq','Msg Queue','X',8,10,'—','The asynchronous network primitive for buffering and streaming data (Kafka).',['Ex','Rp'],'Row 7 (Production): async interface. Communicate.'],
[85,'Rs','Resilience','K',8,11,'—','The systemic countermeasures (checkpointing, elastic recovery) for macroscopic hardware decay.',['Oc'],'Row 7 (Production): fault tolerance. Control.'],
[66,'Ld','Load Balancer','K',8,12,'—','The fleet-level control unit routing incoming requests to available hardware.',['Rp','Ex','Oc'],'Row 7 (Production): traffic control. Control.'],
[67,'Oc','Orchestrator','K',8,13,'—','The fleet-level control plane that scales, restarts, and manages the lifecycle of execution nodes (e.g., K8s).',['Ld','Av'],'Row 7 (Production): fleet control loop. Control.'],
[68,'La','Latency','M',8,14,'—','The end-to-end time from user request to final response.',['Ex','Rp'],'Row 7 (Production): time metric. Measure.'],
[69,'Av','Availability','M',8,15,'—','Service Level Agreement metric measuring uptime and fault tolerance.',['La','Oc'],'Row 7 (Production): reliability metric. Measure.']
];
/* @end:elements */
// ═══════ RENDER ═══════
const grid = document.getElementById('grid');
const legendEl = document.getElementById('legend');
const overlay = document.getElementById('overlay');
const panelContent = document.getElementById('panelContent');
const search = document.getElementById('search');
const yLabelsEl = document.getElementById('yLabels');
const blockHeadersEl = document.getElementById('blockHeaders');
let activeBlock = null;
const elMap = {};
elements.forEach(e => { elMap[e[1]] = e; });
// Block headers
Object.entries(blocks).forEach(([key, b]) => {
const bh = document.createElement('div');
bh.className = 'bh';
bh.textContent = b.name;
bh.style.width = (b.cols.length * 58 + (b.cols.length - 1) * 3) + 'px';
bh.style.background = `color-mix(in srgb, ${b.color} 15%, transparent)`;
bh.style.color = b.color;
blockHeadersEl.appendChild(bh);
});
// Y-axis
rowLabels.forEach((lbl, i) => {
const el = document.createElement('div'); el.className = 'y-lbl';
el.textContent = `${i+1}. ${lbl}`; yLabelsEl.appendChild(el);
});
const ySp = document.createElement('div'); ySp.className = 'y-lbl-spacer'; yLabelsEl.appendChild(ySp);
const yF = document.createElement('div'); yF.className = 'y-lbl'; yF.textContent = 'Frontier';
yF.style.color = 'var(--text-dimmer)'; yF.style.borderTop = '1px dashed var(--border)'; yLabelsEl.appendChild(yF);
// Legend
Object.entries(blocks).forEach(([key, b]) => {
const item = document.createElement('div'); item.className = 'bl-item';
item.innerHTML = `<span class="bl-dot" style="background:${b.color}"></span>${b.name} <span style="color:var(--text-dimmer);font-size:.6rem">— ${b.sub}</span>`;
item.addEventListener('click', () => {
if (activeBlock===key){activeBlock=null;item.classList.remove('active')}
else{activeBlock=key;document.querySelectorAll('.bl-item').forEach(i=>i.classList.remove('active'));item.classList.add('active')}
applyFilter();
});
legendEl.appendChild(item);
});
// Elements
const posMap = {};
elements.forEach(e => { posMap[`${e[4]}-${e[5]}`] = e; });
for (let row = 1; row <= 9; row++) {
if (row === 8) {
const s = document.createElement('div'); s.className='spacer-row';
s.style.gridColumn='1/-1'; s.style.gridRow='8'; grid.appendChild(s); continue;
}
for (let col = 1; col <= 15; col++) {
const e = posMap[`${row}-${col}`];
if (!e) continue;
const block = blocks[e[3]];
const color = row === 9 ? 'var(--frontier)' : block.color;
const el = document.createElement('div'); el.className='el';
el.style.setProperty('--el-c', color);
el.style.gridRow = row; el.style.gridColumn = col;
el.dataset.block = e[3]; el.dataset.name = e[2].toLowerCase(); el.dataset.sym = e[1].toLowerCase();
el.innerHTML = `<span class="num">${e[0]}</span><span class="sym">${e[1]}</span><span class="nm">${e[2]}</span>`;
el.dataset.elSym = e[1];
el.addEventListener('click', (ev) => { ev.stopPropagation(); highlightCompoundsFor(e[1]); showDetail(e); });
grid.appendChild(el);
}
}
// Frontier label (y-axis only — no grid element needed)
function applyFilter() {
const q = search.value.toLowerCase().trim();
document.querySelectorAll('.el').forEach(el => {
const bM = !activeBlock || el.dataset.block === activeBlock;
const sM = !q || el.dataset.name.includes(q) || el.dataset.sym.includes(q);
el.classList.toggle('dimmed', !(bM && sM));
});
}
search.addEventListener('input', applyFilter);
function showDetail(e) {
const block = blocks[e[3]];
const color = block.color;
const bonds = (e[8]||[]).map(s=>{const b=elMap[s];return b?`<span class="bond-tag" data-sym="${s}">${s}${b[2]}</span>`:''}).join('');
panelContent.innerHTML = `
<div class="d-header">
<div class="d-symbox" style="--el-c:${color};border-color:${color}">
<span class="num">#${e[0]}</span><span class="sym" style="color:${color}">${e[1]}</span>
</div>
<div><div class="d-name">${e[2]}</div><div class="d-block-tag" style="color:${color}">${block.name} · ${rowLabels[e[4]-1]}</div></div>
</div>
<div class="d-meta">
<div class="mi"><div class="mi-label">Introduced</div><div class="mi-val">${e[6]}</div></div>
<div class="mi"><div class="mi-label">Role</div><div class="mi-val" style="color:${color}">${block.name}</div></div>
<div class="mi"><div class="mi-label">Layer</div><div class="mi-val">${rowLabels[e[4]-1]}</div></div>
</div>
<div class="d-desc">${e[7]}</div>
<div class="d-why"><strong>Why this position:</strong> ${e[9]}</div>
${bonds?`<div class="d-bonds"><h4>Bonds With</h4><div class="bond-tags">${bonds}</div></div>`:''}`;
panelContent.querySelectorAll('.bond-tag').forEach(t=>{t.addEventListener('click',()=>{const b=elMap[t.dataset.sym];if(b)showDetail(b)})});
overlay.classList.add('open');
}
// ── Cross-referencing: elements ↔ compounds ──
// Build maps BEFORE injecting tooltips (so textContent is clean)
const cardSymMap = new Map();
document.querySelectorAll('.c-card').forEach(card => {
const syms = new Set();
card.querySelectorAll('.c-formula span').forEach(span => {
const sym = span.textContent.replace(/[₀-₉\s]/g,'').trim();
if (elMap[sym]) syms.add(sym);
});
cardSymMap.set(card, syms);
});
const symCardMap = {};
cardSymMap.forEach((syms, card) => {
syms.forEach(sym => {
if (!symCardMap[sym]) symCardMap[sym] = [];
symCardMap[sym].push(card);
});
});
// NOW add tooltips and block colors to compound formula symbols
document.querySelectorAll('.c-formula span').forEach(span => {
const sym = span.textContent.replace(/[₀-₉\s]/g,'').trim();
const el = elMap[sym];
if (el) {
const block = blocks[el[3]];
if (block) span.style.setProperty('--sym-color', block.color);
const tip = document.createElement('span');
tip.className = 'tip';
tip.textContent = el[2];
span.appendChild(tip);
}
});
let xrefActive = false;
// Click element → highlight compounds containing it
function highlightCompoundsFor(sym) {
clearXref();
const cards = symCardMap[sym] || [];
if (cards.length === 0) return;
xrefActive = true;
document.querySelectorAll('.c-card').forEach(c => c.classList.add('c-dimmed'));
cards.forEach(c => { c.classList.remove('c-dimmed'); c.classList.add('c-highlighted'); });
// Scroll first matching compound into view
if (cards[0]) cards[0].scrollIntoView({ behavior:'smooth', block:'nearest' });
}
// Click compound → highlight its elements in the table
document.querySelectorAll('.c-card').forEach(card => {
card.addEventListener('click', (ev) => {
// Don't trigger if clicking a tooltip
if (ev.target.classList.contains('tip')) return;
clearXref();
const syms = cardSymMap.get(card);
if (!syms || syms.size === 0) return;
xrefActive = true;
// Highlight compound card
document.querySelectorAll('.c-card').forEach(c => c.classList.add('c-dimmed'));
card.classList.remove('c-dimmed');
card.classList.add('c-highlighted');
// Highlight elements in grid
document.querySelectorAll('.el').forEach(el => {
if (syms.has(el.dataset.elSym)) {
el.classList.add('el-xref');
}
});
// Scroll table into view
document.querySelector('.table-wrap').scrollIntoView({ behavior:'smooth', block:'nearest' });
});
});
function clearXref() {
xrefActive = false;
document.querySelectorAll('.c-card').forEach(c => { c.classList.remove('c-highlighted','c-dimmed'); });
document.querySelectorAll('.el').forEach(el => { el.classList.remove('el-xref'); });
}
// Click anywhere else to clear highlights
document.addEventListener('click', (ev) => {
if (!xrefActive) return;
if (ev.target.closest('.el') || ev.target.closest('.c-card') || ev.target.closest('.panel')) return;
clearXref();
});
document.getElementById('closeBtn').addEventListener('click',()=>{ overlay.classList.remove('open'); });
overlay.addEventListener('click',e=>{if(e.target===overlay){ overlay.classList.remove('open'); }});
document.addEventListener('keydown',e=>{if(e.key==='Escape'){ overlay.classList.remove('open'); clearXref(); }});
</script>
</body>
</html>

33
periodic-table/package-lock.json generated Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "@mlsysbook/periodic-table",
"version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mlsysbook/periodic-table",
"version": "0.2.0",
"dependencies": {
"js-yaml": "^4.1.1"
}
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
}
}
}

View File

@@ -0,0 +1,15 @@
{
"name": "@mlsysbook/periodic-table",
"version": "0.2.0",
"private": true,
"description": "Build scripts for the Periodic Table of ML Systems. table.yml is the source of truth; build-html and validate consume it.",
"type": "module",
"scripts": {
"build": "node scripts/build-html.mjs",
"validate": "node scripts/validate.mjs",
"migrate": "node scripts/migrate-html-to-yaml.mjs"
},
"dependencies": {
"js-yaml": "^4.1.1"
}
}

View File

@@ -0,0 +1,286 @@
#!/usr/bin/env node
/**
* Build periodic-table/index.html from periodic-table/table.yml.
*
* Strategy: the existing index.html is the template. Data sections are
* marked with sentinel HTML comments (`<!-- @gen:blocks -->` etc.). On
* each run we replace the contents between the sentinels with freshly
* emitted markup; everything else (CSS, render JS, prose) is preserved.
*
* On the FIRST run (before sentinels exist) we use a one-time bootstrap
* that finds the current data definitions by their familiar `const ... =`
* patterns and inserts the sentinel comments around them.
*
* Usage: node periodic-table/scripts/build-html.mjs
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import yaml from "js-yaml";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO = path.resolve(__dirname, "..", "..");
const HTML_PATH = path.join(REPO, "periodic-table", "index.html");
const YAML_PATH = path.join(REPO, "periodic-table", "table.yml");
// ── 1. Load and validate ────────────────────────────────────────────────
const doc = yaml.load(fs.readFileSync(YAML_PATH, "utf8"));
validate(doc);
const KNOWN_SYMS = new Set(doc.elements.map((e) => e.sym));
// ── 2. Render the data sections from YAML ───────────────────────────────
const renderedBlocks = renderBlocksJs(doc.blocks, doc.rows);
const renderedElements = renderElementsJs(doc.elements);
const renderedCompounds = renderCompoundsHtml(doc.compounds, KNOWN_SYMS);
// ── 3. Read the current HTML, bootstrap sentinels if needed, patch ──────
let html = fs.readFileSync(HTML_PATH, "utf8");
html = bootstrapSentinels(html);
html = replaceBetween(html, "@gen:blocks", renderedBlocks);
html = replaceBetween(html, "@gen:elements", renderedElements);
html = replaceBetween(html, "@gen:compounds", renderedCompounds);
fs.writeFileSync(HTML_PATH, html);
console.log(`Wrote ${HTML_PATH}`);
console.log(` ${doc.elements.length} elements`);
console.log(` ${doc.compounds.reduce((n, s) => n + s.items.length, 0)} compounds across ${doc.compounds.length} sections`);
console.log(` ${(doc.known_collisions || []).length} documented symbol collisions`);
// ════════════════════════════════════════════════════════════════════════
// Validation
// ════════════════════════════════════════════════════════════════════════
function validate(doc) {
if (!doc || typeof doc !== "object") throw new Error("table.yml is empty or malformed");
if (!Array.isArray(doc.elements) || doc.elements.length === 0) throw new Error("No elements defined");
const issues = [];
const knownSyms = new Set(doc.elements.map((e) => e.sym));
const cellSeen = new Map();
for (const e of doc.elements) {
const k = `${e.row},${e.col}`;
if (cellSeen.has(k)) {
issues.push(`Cell collision at (${k}): #${cellSeen.get(k)} and #${e.id}`);
} else {
cellSeen.set(k, e.id);
}
}
for (const e of doc.elements) {
for (const b of e.bonds || []) {
if (!knownSyms.has(b)) issues.push(`Element #${e.id} ${e.sym}: unresolved bond '${b}'`);
}
}
const symCount = {};
for (const e of doc.elements) symCount[e.sym] = (symCount[e.sym] || 0) + 1;
const declared = new Set((doc.known_collisions || []).map((c) => c.sym));
for (const [sym, count] of Object.entries(symCount)) {
if (count > 1 && !declared.has(sym)) {
issues.push(`Undocumented symbol collision: ${sym} appears ${count} times — add to known_collisions`);
}
}
for (const section of doc.compounds || []) {
for (const item of section.items || []) {
const refs = extractFormulaSymbols(item.formula);
for (const ref of refs) {
if (!knownSyms.has(ref)) {
issues.push(`Compound "${item.name}" references unknown symbol '${ref}': ${item.formula}`);
}
}
}
}
if (issues.length > 0) {
console.error("VALIDATION FAILED:");
issues.forEach((i) => console.error(" " + i));
process.exit(1);
}
}
function extractFormulaSymbols(formula) {
const cleaned = formula.replace(/_[A-Za-z]+/g, "");
const re = /(?<![A-Za-z])([A-Z][a-z])(?![A-Za-z])/g;
const out = [];
let m;
while ((m = re.exec(cleaned)) !== null) out.push(m[1]);
return out;
}
// ════════════════════════════════════════════════════════════════════════
// Renderers
// ════════════════════════════════════════════════════════════════════════
function renderBlocksJs(blocks, rows) {
let out = "const blocks = {\n";
for (const b of blocks) {
out += ` ${b.key}: { name:${jsStr(b.name)}, sub:${jsStr(b.sub)}, color:${jsStr(b.color)}, cols:[${b.cols.join(",")}] },\n`;
}
out += "};\n";
out += `const rowLabels = [${rows.map((r) => jsStr(r.name)).join(",")}];`;
return out;
}
function renderElementsJs(elements) {
let out = "// [num, sym, name, block, row, col, year, desc, bonds[], whyHere]\n";
out += "const elements = [\n";
const byRow = {};
for (const e of elements) (byRow[e.row] ||= []).push(e);
const rowComments = {
1: "// Row 0: Data (The Raw Material)",
2: "// Row 1: Math (The Theoretical Bedrock)",
3: "// Row 2: Algorithms (The Operations)",
4: "// Row 3: Architecture (The Topologies)",
5: "// Row 4: Optimization (The Physics of Efficiency)",
6: "// Row 5: Runtime (Software Execution Primitives)",
7: "// Row 6: Hardware (Silicon Primitives)",
8: "// Row 7: Production (Fleet Primitives)",
};
const rowKeys = Object.keys(byRow).map(Number).sort((a, b) => a - b);
for (const row of rowKeys) {
out += ` ${rowComments[row] || `// Row ${row}`}\n`;
for (const e of byRow[row]) {
const yearLit = e.year === null || e.year === undefined ? `'—'` : jsStr(e.year);
const bondsLit = `[${(e.bonds || []).map(jsStr).join(",")}]`;
out += ` [${e.id},${jsStr(e.sym)},${jsStr(e.name)},${jsStr(e.block)},${e.row},${e.col},${yearLit},${jsStr(e.desc)},${bondsLit},${jsStr(e.why)}],\n`;
}
out += "\n";
}
// Drop the trailing blank line and trailing comma to keep diffs minimal.
out = out.replace(/,\n\n$/, "\n");
out += "];";
return out;
}
function renderCompoundsHtml(compounds, knownSyms) {
let out = "";
for (const section of compounds) {
out += `\n <h3>${escapeHtml(section.section)}`;
if (section.hint) {
out += ` <span class="isomer-hint">${escapeHtml(section.hint)}</span>`;
}
out += "</h3>\n";
out += ` <div class="compound-grid">\n`;
for (const item of section.items) {
out += ` <div class="c-card">\n`;
out += ` <div class="c-name">${escapeHtml(item.name)}</div>\n`;
out += ` <div class="c-formula">${formulaToHtml(item.formula, knownSyms)}</div>\n`;
out += ` </div>\n`;
}
out += ` </div>\n`;
}
return out;
}
/**
* Convert a formula string into the markup the existing CSS/JS expects:
* <span>Sym</span> for a known symbol
* <span>Sym</span><sub>xxx</sub> for a symbol with subscript
* literal text for everything else
*/
function formulaToHtml(formula, knownSyms) {
let out = "";
let i = 0;
while (i < formula.length) {
const two = formula.slice(i, i + 2);
const isSym =
two.length === 2 &&
/^[A-Z][a-z]$/.test(two) &&
knownSyms.has(two) &&
(i === 0 || !/[A-Za-z]/.test(formula[i - 1])) &&
(i + 2 >= formula.length || !/[A-Za-z]/.test(formula[i + 2]));
if (isSym) {
if (formula[i + 2] === "_") {
const subMatch = formula.slice(i + 3).match(/^[A-Za-z]+/);
if (subMatch) {
out += `<span>${two}</span><sub>${subMatch[0]}</sub>`;
i += 3 + subMatch[0].length;
continue;
}
}
out += `<span>${two}</span>`;
i += 2;
} else if (formula[i] === "_") {
// Subscript on a non-symbol position (like `]ᴺ_enc`, `]ᴺ_dec`).
// Original HTML used <sub> here too, so emit a <sub> tag.
const subMatch = formula.slice(i + 1).match(/^[A-Za-z]+/);
if (subMatch) {
out += `<sub>${subMatch[0]}</sub>`;
i += 1 + subMatch[0].length;
continue;
}
out += escapeHtml(formula[i]);
i += 1;
} else {
out += escapeHtml(formula[i]);
i += 1;
}
}
return out;
}
// ════════════════════════════════════════════════════════════════════════
// Sentinel bootstrap & replace-between
// ════════════════════════════════════════════════════════════════════════
function bootstrapSentinels(html) {
// Blocks + rowLabels
if (!html.includes("@gen:blocks")) {
const blocksRe = /(const blocks = \{[\s\S]*?\};\s*\nconst rowLabels = \[[^\]]*\];)/;
if (!blocksRe.test(html)) throw new Error("Bootstrap: could not locate `const blocks ... const rowLabels` to wrap with sentinel");
html = html.replace(blocksRe, "/* @gen:blocks */\n$1\n/* @end:blocks */");
}
// Elements
if (!html.includes("@gen:elements")) {
const elemsRe = /(\/\/ \[num, sym, name, block[\s\S]*?const elements = \[[\s\S]*?^\];)/m;
if (!elemsRe.test(html)) throw new Error("Bootstrap: could not locate `const elements = [...]` to wrap with sentinel");
html = html.replace(elemsRe, "/* @gen:elements */\n$1\n/* @end:elements */");
}
// Compounds: wrap from the first <h3> after the .c-legend through the
// last </div> before .container.compounds closes.
if (!html.includes("@gen:compounds")) {
const compoundsRe = /(<\/div>\s*\n\s*)(<h3>[\s\S]*?<\/div>)(\s*<\/div>\s*\n*<div class="overlay")/;
if (!compoundsRe.test(html)) throw new Error("Bootstrap: could not locate compound sections to wrap with sentinel");
html = html.replace(compoundsRe, "$1<!-- @gen:compounds -->\n$2\n <!-- @end:compounds -->$3");
}
return html;
}
function replaceBetween(html, name, replacement) {
// JS data sections use /* */ wrapping; HTML compound section uses <!-- -->
const isJs = name === "@gen:blocks" || name === "@gen:elements";
const start = isJs ? `/* ${name} */` : `<!-- ${name} -->`;
const endName = name.replace("@gen:", "@end:");
const end = isJs ? `/* ${endName} */` : `<!-- ${endName} -->`;
const startIdx = html.indexOf(start);
if (startIdx < 0) throw new Error(`Sentinel start not found: ${start}`);
const endIdx = html.indexOf(end, startIdx + start.length);
if (endIdx < 0) throw new Error(`Sentinel end not found: ${end}`);
const before = html.slice(0, startIdx + start.length);
const after = html.slice(endIdx);
return before + "\n" + replacement + "\n" + after;
}
// ════════════════════════════════════════════════════════════════════════
// String helpers
// ════════════════════════════════════════════════════════════════════════
function jsStr(s) {
return "'" + String(s).replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n") + "'";
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}

View File

@@ -0,0 +1,365 @@
#!/usr/bin/env node
/**
* One-shot migration: extract the live data from periodic-table/index.html
* and emit periodic-table/table.yml as the new source of truth.
*
* Usage:
* node periodic-table/scripts/migrate-html-to-yaml.mjs
*
* After this runs, the YAML is canonical. Re-running the migrator is safe
* but will overwrite the YAML — only do that if you're recovering from a
* corrupt YAML, since any hand-edits to the YAML will be lost.
*/
import fs from "node:fs";
import path from "node:path";
import vm from "node:vm";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const REPO = path.resolve(__dirname, "..", "..");
const HTML_PATH = path.join(REPO, "periodic-table", "index.html");
const YAML_PATH = path.join(REPO, "periodic-table", "table.yml");
// ── 1. Extract `blocks`, `rowLabels`, `elements` from the script tag ──
const html = fs.readFileSync(HTML_PATH, "utf8");
const scriptMatch = html.match(/<script>([\s\S]*?)<\/script>/);
if (!scriptMatch) throw new Error("No <script> block found in index.html");
const scriptText = scriptMatch[1];
// Take only the data declarations (not the render code, which references DOM)
const dataMatch = scriptText.match(/^const blocks[\s\S]*?^\];$/m);
if (!dataMatch) throw new Error("Could not locate `const blocks ... const elements [...]` block");
const ctx = vm.createContext({});
vm.runInContext(
dataMatch[0] + "\nthis.blocks = blocks; this.rowLabels = rowLabels; this.elements = elements;",
ctx,
);
const blocksRaw = ctx.blocks;
const rowLabelsRaw = ctx.rowLabels;
const elementsRaw = ctx.elements;
console.log(`Extracted: ${elementsRaw.length} elements, ${Object.keys(blocksRaw).length} blocks, ${rowLabelsRaw.length} row labels`);
// ── 2. Translate the JS literals into YAML-friendly typed structures ──
// Block-key order should match the visual left-to-right order, which the
// original `cols` arrays imply. Sort by min(cols).
const blockKeyOrder = Object.entries(blocksRaw)
.sort((a, b) => Math.min(...a[1].cols) - Math.min(...b[1].cols))
.map(([k]) => k);
const blocks = blockKeyOrder.map((k) => ({
key: k,
name: blocksRaw[k].name,
sub: blocksRaw[k].sub,
color: blocksRaw[k].color,
cols: blocksRaw[k].cols,
}));
// Convert the row labels from a flat string array into objects with index/key.
// `key` is a slug derived from the name.
const rows = rowLabelsRaw.map((name, i) => ({
index: i + 1,
key: name.toLowerCase().replace(/[^a-z]/g, "_"),
name,
}));
// Each tuple is [num, sym, name, block, row, col, year, desc, bonds, why]
const elements = elementsRaw.map((tuple) => {
const [id, sym, name, block, row, col, year, desc, bonds, why] = tuple;
return {
id,
sym,
name,
block,
row,
col,
year: year === "—" || year === "" ? null : year,
desc,
bonds: bonds || [],
why,
};
});
// Sort elements: by row, then col, then id (stable for ties).
elements.sort((a, b) => a.row - b.row || a.col - b.col || a.id - b.id);
// ── 3. Extract compounds from the .c-card markup ──
// The structure is:
// <h3>Section Name <span class="isomer-hint">hint text</span></h3>
// <div class="compound-grid">
// <div class="c-card">
// <div class="c-name">CompoundName</div>
// <div class="c-formula"><span>Sym</span><sub>sub</sub> ... </div>
// </div>
// ...
// </div>
// Pull the entire compounds container so we can walk it section by section.
const compoundsBlockMatch = html.match(/<h2>Molecular ML[\s\S]*?<\/div>\s*<\/div>\s*\n*<div class="overlay"/);
if (!compoundsBlockMatch) throw new Error("Could not locate the Molecular ML compounds block");
const compoundsBlock = compoundsBlockMatch[0];
// Walk h3 by h3 to identify sections
const sectionRegex = /<h3>([^<]+?)(?:\s*<span class="isomer-hint">([^<]+)<\/span>)?\s*<\/h3>([\s\S]*?)(?=<h3>|<\/div>\s*<\/div>\s*\n*<div class="overlay")/g;
const compounds = [];
let m;
while ((m = sectionRegex.exec(compoundsBlock)) !== null) {
const sectionName = m[1].trim();
const hint = m[2] ? m[2].trim() : undefined;
const sectionBody = m[3];
// Now find each c-card inside the section body
const cardRegex = /<div class="c-card">([\s\S]*?)<\/div>\s*<\/div>/g;
// Actually the structure has nested divs. Easier: match c-name + c-formula pairs.
const itemRegex = /<div class="c-name">([\s\S]*?)<\/div>\s*<div class="c-formula">([\s\S]*?)<\/div>/g;
const items = [];
let im;
while ((im = itemRegex.exec(sectionBody)) !== null) {
const name = stripHtml(im[1]).trim();
const formula = formulaMarkupToString(im[2]);
items.push({ name, formula });
}
if (items.length > 0) {
compounds.push({ section: sectionName, ...(hint ? { hint } : {}), items });
}
}
console.log(`Extracted: ${compounds.length} compound sections, ${compounds.reduce((n, s) => n + s.items.length, 0)} compounds total`);
// ── 4. Identify symbol collisions for the known_collisions block ──
const symGroups = {};
for (const e of elements) {
(symGroups[e.sym] ||= []).push(e);
}
const knownCollisions = Object.entries(symGroups)
.filter(([, list]) => list.length > 1)
.map(([sym, list]) => ({
sym,
ids: list.map((e) => e.id).sort((a, b) => a - b),
note: list.map((e) => `${e.name} (#${e.id}, row ${e.row} col ${e.col}, ${e.block})`).join(" + "),
}));
console.log(`Documented ${knownCollisions.length} intentional symbol collisions`);
// ── 5. Validate before writing ──
const issues = [];
// Cell collisions (should be zero given the current data)
const cellGroups = {};
for (const e of elements) {
const key = `${e.row},${e.col}`;
(cellGroups[key] ||= []).push(e);
}
for (const [key, list] of Object.entries(cellGroups)) {
if (list.length > 1) {
issues.push(`Cell collision at (${key}): ${list.map((e) => `#${e.id} ${e.sym}`).join(", ")}`);
}
}
// Bond resolution
const knownSyms = new Set(elements.map((e) => e.sym));
for (const e of elements) {
for (const b of e.bonds) {
if (!knownSyms.has(b)) issues.push(`Element #${e.id} ${e.sym}: unresolved bond '${b}'`);
}
}
// Formula resolution — every two-letter [A-Z][a-z] substring in a formula
// must reference a known element symbol.
for (const section of compounds) {
for (const item of section.items) {
const refs = extractFormulaSymbols(item.formula);
for (const ref of refs) {
if (!knownSyms.has(ref)) {
issues.push(`Compound "${item.name}" references unknown symbol '${ref}' in formula: ${item.formula}`);
}
}
}
}
if (issues.length > 0) {
console.error("VALIDATION FAILED:");
issues.forEach((i) => console.error(" " + i));
process.exit(1);
}
console.log("Validation passed (cells, bonds, formula references)");
// ── 6. Emit YAML by hand ──
const out = emitYaml({
version: "0.2",
title: "The Periodic Table of Machine Learning Systems",
subtitle: "Two fundamental axes — abstraction layer and information-processing role — organize ML concepts the way electron shells and valence organize chemistry.",
blocks,
rows,
elements,
compounds,
known_collisions: knownCollisions,
});
fs.writeFileSync(YAML_PATH, out);
console.log(`\nWrote ${YAML_PATH}`);
console.log(` ${elements.length} elements`);
console.log(` ${compounds.reduce((n, s) => n + s.items.length, 0)} compounds across ${compounds.length} sections`);
console.log(` ${knownCollisions.length} documented symbol collisions`);
// ════════════════════════════════════════════════════════════════════════
// Helpers
// ════════════════════════════════════════════════════════════════════════
function stripHtml(s) {
return s.replace(/<[^>]+>/g, "");
}
/**
* Convert a .c-formula's inner HTML back into the string form used in
* table.yml. Examples:
*
* "<span>Eb</span> → [(<span>At</span> ∥ <span>Mk</span>) → ..."
* → "Eb → [(At ∥ Mk) → ..."
*
* "<span>Tk</span><sub>patch</sub> → <span>Eb</span>"
* → "Tk_patch → Eb"
*
* "<span>Tk</span><sub>img</sub> ∥ <span>Tk</span><sub>txt</sub>"
* → "Tk_img ∥ Tk_txt"
*
* Strategy: walk the markup left to right. When we see <span>X</span>,
* emit X. When we see <sub>...</sub> immediately after a <span>, emit
* "_<sub-content>". Otherwise emit literal characters.
*/
function formulaMarkupToString(markup) {
// Normalize whitespace inside the markup but preserve significant spaces
let s = markup.replace(/\s+/g, " ").trim();
// Replace <span>X</span> -> X
s = s.replace(/<span[^>]*>([^<]*)<\/span>/g, "$1");
// Replace <sub>X</sub> -> _X (subscript marker)
s = s.replace(/<sub[^>]*>([^<]*)<\/sub>/g, "_$1");
// Strip any other HTML
s = s.replace(/<[^>]+>/g, "");
// Collapse double spaces around operators
s = s.replace(/\s+/g, " ").trim();
return s;
}
/**
* Inverse of the parser used downstream — extract every symbol reference
* from a formula string. A symbol is a two-letter token of the form
* `[A-Z][a-z]` that's not immediately preceded or followed by another
* letter (so we don't mis-tokenize words inside subscripts).
*/
function extractFormulaSymbols(formula) {
const out = [];
// Strip subscripts first: `Tk_patch` → keep `Tk`, drop `_patch`
// We do this by splitting on `_`: anything after an underscore until
// the next non-word char is subscript text.
const cleaned = formula.replace(/_[A-Za-z]+/g, "");
const re = /(?<![A-Za-z])([A-Z][a-z])(?![A-Za-z])/g;
let m;
while ((m = re.exec(cleaned)) !== null) out.push(m[1]);
return out;
}
// ────────────────────────────────────────────────────────────────────────
// Tiny hand-written YAML emitter. Only handles the shape we produce.
// ────────────────────────────────────────────────────────────────────────
function emitYaml(doc) {
let out = "";
out += `# Periodic Table of ML Systems — single source of truth.\n`;
out += `# Generated from periodic-table/index.html by scripts/migrate-html-to-yaml.mjs\n`;
out += `# Edit this file directly. Run \`make all\` (in periodic-table/) to regenerate\n`;
out += `# index.html and the StaffML React data file from this YAML.\n\n`;
out += `version: ${quote(doc.version)}\n`;
out += `title: ${quote(doc.title)}\n`;
out += `subtitle: ${quote(doc.subtitle)}\n\n`;
out += "# ─── Blocks (information-processing roles, columns) ───────────\n";
out += "blocks:\n";
for (const b of doc.blocks) {
out += ` - key: ${b.key}\n`;
out += ` name: ${quote(b.name)}\n`;
out += ` sub: ${quote(b.sub)}\n`;
out += ` color: ${quote(b.color)}\n`;
out += ` cols: [${b.cols.join(", ")}]\n`;
}
out += "\n";
out += "# ─── Rows (abstraction layers) ────────────────────────────────\n";
out += "rows:\n";
for (const r of doc.rows) {
out += ` - index: ${r.index}\n`;
out += ` key: ${quote(r.key)}\n`;
out += ` name: ${quote(r.name)}\n`;
}
out += "\n";
out += `# ─── Elements (${doc.elements.length} total) ─────────────────────────────\n`;
out += "elements:\n";
for (const e of doc.elements) {
out += ` - id: ${e.id}\n`;
out += ` sym: ${quote(e.sym)}\n`;
out += ` name: ${quote(e.name)}\n`;
out += ` block: ${e.block}\n`;
out += ` row: ${e.row}\n`;
out += ` col: ${e.col}\n`;
out += ` year: ${e.year === null ? "null" : quote(e.year)}\n`;
out += ` desc: ${quote(e.desc)}\n`;
if (e.bonds.length === 0) {
out += ` bonds: []\n`;
} else {
out += ` bonds: [${e.bonds.map(quote).join(", ")}]\n`;
}
out += ` why: ${quote(e.why)}\n`;
}
out += "\n";
const compoundCount = doc.compounds.reduce((n, s) => n + s.items.length, 0);
out += `# ─── Compounds (${compoundCount} total across ${doc.compounds.length} sections) ──────────────\n`;
out += "# Formulas are written in the same notation as the paper:\n";
out += "# Sym -> two-letter element reference (resolves to elements[*].sym)\n";
out += "# _xxx -> subscript on the preceding token (e.g. Tk_patch, ]ᴺ_enc)\n";
out += "# -> sequential composition\n";
out += "# ‖ parallel\n";
out += "# ? conditional\n";
out += "# ⇌ adversarial\n";
out += "# ↺ feedback loop\n";
out += "# [...]ᴺ repeated block\n";
out += "compounds:\n";
for (const section of doc.compounds) {
out += ` - section: ${quote(section.section)}\n`;
if (section.hint) out += ` hint: ${quote(section.hint)}\n`;
out += ` items:\n`;
for (const item of section.items) {
out += ` - name: ${quote(item.name)}\n`;
out += ` formula: ${quote(item.formula)}\n`;
}
}
out += "\n";
if (doc.known_collisions.length > 0) {
out += "# ─── Documented intentional symbol collisions ─────────────────\n";
out += "# Lookup behavior is last-wins; consumers may disambiguate by id\n";
out += "# or by (row, col). The validator allows only collisions listed here.\n";
out += "known_collisions:\n";
for (const c of doc.known_collisions) {
out += ` - sym: ${quote(c.sym)}\n`;
out += ` ids: [${c.ids.join(", ")}]\n`;
out += ` note: ${quote(c.note)}\n`;
}
}
return out;
}
/**
* Conservative YAML string quoter. Always uses double quotes and escapes
* the few characters that matter inside double-quoted YAML strings.
*/
function quote(s) {
if (s === null || s === undefined) return "null";
const str = String(s);
return '"' + str.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
}

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env node
/**
* Validate periodic-table/table.yml against the schema and the cross-reference
* invariants (cell collisions, bond resolution, formula resolution, undocumented
* symbol collisions). Exits 0 on success, 1 on failure.
*
* Usage: node periodic-table/scripts/validate.mjs
* make validate (from periodic-table/)
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import yaml from "js-yaml";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const YAML_PATH = path.join(__dirname, "..", "table.yml");
const doc = yaml.load(fs.readFileSync(YAML_PATH, "utf8"));
const issues = [];
if (!doc || typeof doc !== "object") {
console.error("FAIL: table.yml is empty or malformed");
process.exit(1);
}
if (!Array.isArray(doc.elements) || doc.elements.length === 0) {
console.error("FAIL: no elements defined");
process.exit(1);
}
// ── Cross-reference invariants ──────────────────────────────────────────
const knownSyms = new Set(doc.elements.map((e) => e.sym));
const cellSeen = new Map();
for (const e of doc.elements) {
// Schema-ish field validation (in case the user hand-edited and broke shape)
if (typeof e.id !== "number") issues.push(`Element ${e.sym || "?"}: id must be a number`);
if (typeof e.sym !== "string" || !/^[A-Z][a-z]$/.test(e.sym)) issues.push(`Element #${e.id}: sym '${e.sym}' must match [A-Z][a-z]`);
if (typeof e.row !== "number" || e.row < 1 || e.row > 8) issues.push(`Element #${e.id} ${e.sym}: row ${e.row} out of range 1-8`);
if (typeof e.col !== "number" || e.col < 1 || e.col > 15) issues.push(`Element #${e.id} ${e.sym}: col ${e.col} out of range 1-15`);
if (!["R", "C", "X", "K", "M"].includes(e.block)) issues.push(`Element #${e.id} ${e.sym}: block '${e.block}' must be one of R/C/X/K/M`);
// Cell collisions
const k = `${e.row},${e.col}`;
if (cellSeen.has(k)) {
const prev = cellSeen.get(k);
issues.push(`Cell collision at (${k}): #${prev.id} ${prev.sym} and #${e.id} ${e.sym}`);
} else {
cellSeen.set(k, e);
}
}
// Bond resolution
for (const e of doc.elements) {
for (const b of e.bonds || []) {
if (!knownSyms.has(b)) issues.push(`Element #${e.id} ${e.sym}: bond '${b}' references unknown element`);
}
}
// Symbol collisions: must be in known_collisions
const symCount = {};
for (const e of doc.elements) symCount[e.sym] = (symCount[e.sym] || 0) + 1;
const declared = new Set((doc.known_collisions || []).map((c) => c.sym));
for (const [sym, count] of Object.entries(symCount)) {
if (count > 1 && !declared.has(sym)) {
issues.push(`Undocumented symbol collision: '${sym}' appears ${count} times — add to known_collisions`);
}
}
// Formula resolution: every two-letter [A-Z][a-z] token in any formula must
// resolve to a known element symbol.
for (const section of doc.compounds || []) {
for (const item of section.items || []) {
const cleaned = item.formula.replace(/_[A-Za-z]+/g, "");
const re = /(?<![A-Za-z])([A-Z][a-z])(?![A-Za-z])/g;
let m;
while ((m = re.exec(cleaned)) !== null) {
if (!knownSyms.has(m[1])) {
issues.push(`Compound "${item.name}" references unknown symbol '${m[1]}': ${item.formula}`);
}
}
}
}
if (issues.length > 0) {
console.error(`✗ table.yml VALIDATION FAILED — ${issues.length} issue(s):`);
for (const i of issues) console.error(" " + i);
process.exit(1);
}
const compoundCount = (doc.compounds || []).reduce((n, s) => n + (s.items || []).length, 0);
console.log(`✓ table.yml is valid`);
console.log(` ${doc.elements.length} elements, ${compoundCount} compounds`);
console.log(` ${(doc.known_collisions || []).length} documented intentional symbol collisions`);
console.log(` 0 cell collisions, 0 unresolved bonds, 0 unresolved formula references`);

View File

@@ -0,0 +1,120 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://mlsysbook.ai/periodic-table/table.schema.json",
"title": "Periodic Table of ML Systems",
"description": "Single source of truth for the Periodic Table of ML Systems. Consumed by the standalone HTML emitter and the StaffML React app. Cross-reference invariants (bond resolution, formula resolution, no cell collisions) are enforced by the validator script in addition to this schema.",
"type": "object",
"required": ["version", "title", "blocks", "rows", "elements", "compounds"],
"additionalProperties": false,
"properties": {
"version": { "type": "string", "pattern": "^\\d+\\.\\d+(\\.\\d+)?$" },
"title": { "type": "string" },
"subtitle": { "type": "string" },
"blocks": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["key", "name", "sub", "color", "cols"],
"additionalProperties": false,
"properties": {
"key": { "type": "string", "enum": ["R", "C", "X", "K", "M"] },
"name": { "type": "string" },
"sub": { "type": "string" },
"color": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
"cols": {
"type": "array",
"minItems": 1,
"items": { "type": "integer", "minimum": 1, "maximum": 15 }
}
}
}
},
"rows": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["index", "key", "name"],
"additionalProperties": false,
"properties": {
"index": { "type": "integer", "minimum": 1, "maximum": 8 },
"key": { "type": "string", "pattern": "^[a-z_]+$" },
"name": { "type": "string" },
"tagline": { "type": "string" }
}
}
},
"elements": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["id", "sym", "name", "block", "row", "col", "desc", "bonds", "why"],
"additionalProperties": false,
"properties": {
"id": { "type": "integer", "minimum": 1 },
"sym": { "type": "string", "pattern": "^[A-Z][a-z]$" },
"name": { "type": "string", "minLength": 1 },
"block": { "type": "string", "enum": ["R", "C", "X", "K", "M"] },
"row": { "type": "integer", "minimum": 1, "maximum": 8 },
"col": { "type": "integer", "minimum": 1, "maximum": 15 },
"year": { "type": ["string", "null"] },
"desc": { "type": "string", "minLength": 1 },
"bonds": {
"type": "array",
"items": { "type": "string", "pattern": "^[A-Z][a-z]$" }
},
"why": { "type": "string", "minLength": 1 }
}
}
},
"compounds": {
"type": "array",
"items": {
"type": "object",
"required": ["section", "items"],
"additionalProperties": false,
"properties": {
"section": { "type": "string" },
"hint": { "type": "string" },
"items": {
"type": "array",
"items": {
"type": "object",
"required": ["name", "formula"],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"formula": { "type": "string", "minLength": 1 }
}
}
}
}
}
},
"known_collisions": {
"type": "array",
"description": "Documented intentional symbol collisions. Lookup behavior is last-wins; consumers may disambiguate by id or by (row, col).",
"items": {
"type": "object",
"required": ["sym", "ids", "note"],
"additionalProperties": false,
"properties": {
"sym": { "type": "string", "pattern": "^[A-Z][a-z]$" },
"ids": {
"type": "array",
"minItems": 2,
"items": { "type": "integer", "minimum": 1 }
},
"note": { "type": "string" }
}
}
}
}
}

1117
periodic-table/table.yml Normal file

File diff suppressed because it is too large Load Diff