cpsc-qft / docs /qsim.html
SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
eef9104 verified
Raw History Blame Contribute Delete
14.2 kB
<!DOCTYPE html>
<!--
Copyright (c) 2026 Ahmad Ali Parr and others.
SPDX-License-Identifier: LicenseRef-CPSC-ESCL-1.0 OR AGPL-3.0-only
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mercury — Quantum Circuit Console · CPSC-QFT</title>
<style>
:root { --bg:#0a0e14; --panel:#111722; --line:#1e2a3a; --txt:#d6e2f0; --dim:#7d8ea8;
--acc:#5cc8ff; --ok:#5df08a; --bad:#ff6b6b; --warn:#ffd479; }
* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--txt);
font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:13px; }
header { padding:14px 20px; border-bottom:1px solid var(--line); display:flex;
align-items:baseline; gap:14px; flex-wrap:wrap; }
header h1 { margin:0; font-size:17px; letter-spacing:2px; color:var(--acc); }
header span { color:var(--dim); font-size:12px; }
#status { margin-left:auto; font-size:12px; color:var(--warn); }
#status.ready { color:var(--ok); }
main { display:grid; grid-template-columns:minmax(340px,430px) 1fr; gap:14px; padding:14px 20px; }
@media (max-width:900px){ main{ grid-template-columns:1fr; } }
.card { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:12px; margin-bottom:14px; }
.card h2 { margin:0 0 10px; font-size:12px; letter-spacing:1.5px; color:var(--dim); text-transform:uppercase; }
label { display:block; color:var(--dim); font-size:11px; margin:8px 0 3px; }
input[type=text], input[type=number], textarea, select {
width:100%; background:#0a0e14; color:var(--txt); border:1px solid var(--line);
border-radius:5px; padding:7px 9px; font:inherit; font-size:12.5px; }
textarea { min-height:150px; resize:vertical; line-height:1.5; }
.row { display:flex; gap:8px; } .row > div { flex:1; }
button { background:#16202f; color:var(--txt); border:1px solid var(--line); border-radius:5px;
padding:7px 10px; font:inherit; font-size:12px; cursor:pointer; }
button:hover { border-color:var(--acc); color:var(--acc); }
button.primary { background:#12324a; border-color:var(--acc); color:var(--acc); font-weight:bold; }
button.primary:hover { background:#17405e; }
.palette { display:flex; flex-wrap:wrap; gap:6px; margin-top:6px; }
.palette button { padding:5px 9px; }
.presets { display:flex; flex-wrap:wrap; gap:6px; margin-top:8px; }
pre.diagram { background:#0a0e14; border:1px solid var(--line); border-radius:5px;
padding:10px; overflow-x:auto; font-size:12.5px; line-height:1.7; }
table { width:100%; border-collapse:collapse; font-size:12px; }
th, td { text-align:right; padding:4px 8px; border-bottom:1px solid var(--line); }
th { color:var(--dim); font-weight:normal; } td:first-child, th:first-child { text-align:left; }
.verdict-ok { color:var(--ok); font-weight:bold; } .verdict-bad { color:var(--bad); font-weight:bold; }
.rej { color:var(--bad); } .muted { color:var(--dim); }
canvas { width:100%; height:220px; background:#0a0e14; border:1px solid var(--line); border-radius:5px; }
.kv { display:flex; justify-content:space-between; padding:3px 0; }
.kv b { font-weight:normal; color:var(--dim); }
footer { padding:10px 20px 20px; color:var(--dim); font-size:11px; }
a { color:var(--acc); }
</style>
</head>
<body>
<header>
<h1>MERCURY</h1>
<a href="./" style="color:#7d8ea8;text-decoration:none">site</a>
<span>quantum circuit console · CPSC-QFT frontend · Ruby/WASM statevector simulator</span>
<span id="status">● loading Ruby…</span>
</header>
<main>
<div>
<div class="card">
<h2>Circuit</h2>
<div class="row">
<div><label>Qubits (1–12)</label><input type="number" id="n" value="2" min="1" max="12"></div>
<div><label>Initial state (bitstring)</label><input type="text" id="initial" value="00"></div>
</div>
<label>Script — one op per line: <span class="muted">h 0 · rx 1.57 1 · cnot 0 1 · cp 0.79 1 0 · rzz 0.5 0 1 · xx 0 1 · measure 0</span></label>
<textarea id="dsl">h 0
cnot 0 1</textarea>
<label>Gate palette (appends)</label>
<div class="palette" id="palette"></div>
<label>Presets</label>
<div class="presets">
<button data-preset="bell">Bell pair</button>
<button data-preset="ghz">GHZ-3</button>
<button data-preset="qft">QFT-3</button>
<button data-preset="trotter">Trotter scattering</button>
<button data-preset="adversarial">Adversarial X</button>
</div>
</div>
<div class="card">
<h2>Run</h2>
<div class="row">
<div><label>Shots (0 = statevector only)</label><input type="number" id="shots" value="1024" min="0" max="100000"></div>
<div><label>Seed (blank = random)</label><input type="text" id="seed" value="42"></div>
</div>
<label style="margin-top:10px"><input type="checkbox" id="cpsc" checked style="width:auto"> CPSC conservation check</label>
<div class="row">
<div><label>J (coupling)</label><input type="text" id="pj" value="1.0"></div>
<div><label>λ (transverse field)</label><input type="text" id="plam" value="0.0"></div>
<div><label style="margin-top:22px"><input type="checkbox" id="pmag" style="width:auto"> conserve ΣZ</label></div>
</div>
<div style="margin-top:10px"><button class="primary" id="run" style="width:100%; padding:10px">▶ Run circuit</button></div>
</div>
<div class="card">
<h2>Conservation (CPSC)</h2>
<div id="cpscOut"><span class="muted">Run a circuit to check symmetry-sector admission.</span></div>
</div>
</div>
<div>
<div class="card">
<h2>Circuit diagram</h2>
<pre class="diagram" id="diagram"><span class="muted">—</span></pre>
</div>
<div class="card">
<h2>Measurement histogram</h2>
<canvas id="hist" width="800" height="220"></canvas>
</div>
<div class="card">
<h2>Statevector (top amplitudes)</h2>
<table><thead><tr><th>basis</th><th>Re</th><th>Im</th><th>P</th></tr></thead>
<tbody id="svBody"><tr><td colspan="4" class="muted">—</td></tr></tbody></table>
</div>
<div class="card">
<h2>Probabilities</h2>
<table><thead><tr><th>basis</th><th>P</th></tr></thead>
<tbody id="probBody"><tr><td colspan="2" class="muted">—</td></tr></tbody></table>
</div>
</div>
</main>
<footer>
Mercury is a dependency-free Ruby statevector simulator (up to 12 qubits in-browser). The CPSC panel mirrors
<span class="muted">src/cpsc/analyzer.py</span>: gates are admitted only if they preserve the Z<sub>2</sub> parity sector —
a compile-time rejection rule, not post-selection. Trotter preset evolves the transverse-field Ising H under test.
</footer>
<script src="https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.3.0/dist/browser.umd.js"></script>
<script>
/* Mercury WASM boot — same pattern as the Intentsim console. */
const W = { vm: null, ready: false };
const RUBY_WASM_URL = "https://cdn.jsdelivr.net/npm/ruby-3_2-wasm-wasi@2.3.0/dist/ruby+stdlib.wasm";
function setBoot(t, ok) {
const el = document.getElementById("status");
el.textContent = t;
el.classList.toggle("ready", !!ok);
document.getElementById("run").disabled = !ok;
}
function rbEscape(s) { return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); }
function mercuryRun(req) {
const code = `handle_request('${rbEscape(JSON.stringify(req))}')`;
return JSON.parse(W.vm.eval(code).toString());
}
async function bootRuby() {
try {
if (!window["ruby-wasm-wasi"]) throw new Error("ruby-wasm-wasi bundle missing");
setBoot("● loading ruby 3.2 webassembly (≈32 mb)…");
const { DefaultRubyVM } = window["ruby-wasm-wasi"];
const response = await fetch(RUBY_WASM_URL);
if (!response.ok) throw new Error("wasm fetch failed: " + response.status);
setBoot("● compiling webassembly…");
const module = await WebAssembly.compile(await response.arrayBuffer());
setBoot("● starting ruby vm…");
const { vm } = await DefaultRubyVM(module);
setBoot("● evaluating mercury bundle…");
const src = await (await fetch("mercury_bundle.rb")).text();
if (!src.includes("module Mercury")) throw new Error("mercury bundle not found");
vm.eval(src);
W.vm = vm; W.ready = true;
setBoot("● ruby ready — live simulation", true);
} catch (e) {
setBoot("● wasm unreachable (" + (e.message || e) + ")");
}
}
const PARAM_N = {rx:1, ry:1, rz:1, cp:1, rzz:1, u:3};
const GATE_LIST = ["h","x","y","z","s","t","rx","ry","rz","cnot","cz","cp","swap","rzz","xx","ccx"];
function parseDSL(text) {
const ops = [];
for (const raw of text.split("\n")) {
const line = raw.trim();
if (!line || line.startsWith("#")) continue;
const t = line.split(/\s+/);
const gate = t[0].toLowerCase();
let idx = 1, params = [];
const pn = PARAM_N[gate] || 0;
for (let k = 0; k < pn; k++) {
if (idx >= t.length) throw new Error("missing param for " + gate);
const v = parseFloat(t[idx++]);
if (!isFinite(v)) throw new Error("bad param " + t[idx-1]);
params.push(v);
}
const qubits = [];
while (idx < t.length) {
const q = parseInt(t[idx++], 10);
if (!isFinite(q) || q < 0) throw new Error("bad qubit");
qubits.push(q);
}
ops.push([gate, qubits, params]);
}
return ops;
}
document.getElementById("run").disabled = true;
document.addEventListener("DOMContentLoaded", bootRuby);
// gate palette
const pal = document.getElementById("palette");
for (const g of GATE_LIST) {
const b = document.createElement("button");
b.textContent = g.toUpperCase();
b.onclick = () => {
const dsl = document.getElementById("dsl");
const pn = PARAM_N[g] || 0;
dsl.value += (dsl.value.endsWith("\n") || dsl.value === "" ? "" : "\n") + g + " " + "0.5 ".repeat(pn) + "0\n";
dsl.focus();
};
pal.appendChild(b);
}
const PRESETS = {
bell: { n:"2", initial:"00", dsl:"h 0\ncnot 0 1" },
ghz: { n:"3", initial:"000", dsl:"h 0\ncnot 0 1\ncnot 1 2" },
qft: { n:"3", initial:"000",
dsl:"h 0\ncp 1.5708 1 0\ncp 0.7854 2 0\nh 1\ncp 1.5708 2 1\nh 2\nswap 0 2" },
trotter:{ n:"4", initial:"0011", lam:"0.0",
dsl:"# Trotter: H = -J ZZ + lam X, J=1, t=0.4, 8 steps\n" +
Array(8).fill(
"rzz -0.1 0 1\nrzz -0.1 1 2\nrzz -0.1 2 3\nrzz -0.1 3 0"
).join("\n") },
adversarial: { n:"4", initial:"0000",
dsl:"rzz 0.5 0 1\nx 2\nrzz 0.5 2 3" }
};
document.querySelectorAll("[data-preset]").forEach(b => b.onclick = () => {
const p = PRESETS[b.dataset.preset];
document.getElementById("n").value = p.n;
document.getElementById("initial").value = p.initial;
document.getElementById("dsl").value = p.dsl;
if (p.lam !== undefined) document.getElementById("plam").value = p.lam;
});
document.getElementById("run").onclick = () => {
let req;
try {
const n = parseInt(document.getElementById("n").value, 10);
req = {
n, initial: document.getElementById("initial").value.trim(),
ops: parseDSL(document.getElementById("dsl").value),
shots: parseInt(document.getElementById("shots").value, 10) || 0,
seed: document.getElementById("seed").value.trim() === "" ? null
: parseInt(document.getElementById("seed").value, 10),
cpsc: document.getElementById("cpsc").checked,
magnetization: document.getElementById("pmag").checked,
j: parseFloat(document.getElementById("pj").value) || 0,
lam: parseFloat(document.getElementById("plam").value) || 0
};
} catch (e) { alert("Script error: " + e.message); return; }
let out;
try { out = mercuryRun(req); }
catch (e) { alert("Simulator error: " + e.message); return; }
if (out.error) { alert("Simulator error: " + out.error); return; }
render(out);
};
function render(out) {
document.getElementById("diagram").textContent = out.diagram;
// histogram: prefer counts, fall back to probs
const data = Object.keys(out.counts).length ? out.counts :
Object.fromEntries(out.probs.map(([b, p]) => [b, p]));
drawHist(data);
const svb = document.getElementById("svBody"); svb.innerHTML = "";
for (const [b, re, im] of out.statevector) {
const p = re*re + im*im;
svb.innerHTML += `<tr><td>|${b}⟩</td><td>${re.toFixed(4)}</td><td>${im.toFixed(4)}</td><td>${p.toFixed(4)}</td></tr>`;
}
const prb = document.getElementById("probBody"); prb.innerHTML = "";
for (const [b, p] of out.probs) prb.innerHTML += `<tr><td>|${b}⟩</td><td>${p.toFixed(4)}</td></tr>`;
const c = document.getElementById("cpscOut");
if (out.cpsc) {
const v = out.cpsc;
let html = v.admitted
? `<div class="verdict-ok">✓ ADMITTED — every gate preserves the symmetry sector</div>`
: `<div class="verdict-bad">✗ REJECTED at compile time</div>`;
for (const r of v.rejections) html += `<div class="rej">· ${r.gate}: ${r.reason}</div>`;
html += `<div class="kv"><b>parity leakage</b><span>${v.leakage.toExponential(2)}</span></div>`;
html += `<div class="kv"><b>⟨H⟩ (J,λ as set)</b><span>${v.energy.toFixed(6)}</span></div>`;
html += `<div class="kv"><b>norm</b><span>${out.norm.toFixed(6)}</span></div>`;
c.innerHTML = html;
} else c.innerHTML = '<span class="muted">CPSC check disabled.</span>';
}
function drawHist(data) {
const cv = document.getElementById("hist"), ctx = cv.getContext("2d");
const W = cv.width, H = cv.height;
ctx.clearRect(0, 0, W, H);
const keys = Object.keys(data).sort();
if (!keys.length) return;
const max = Math.max(...Object.values(data));
const bw = W / keys.length;
ctx.fillStyle = "#5cc8ff"; ctx.strokeStyle = "#1e2a3a";
ctx.font = "11px monospace"; ctx.textAlign = "center";
keys.forEach((k, i) => {
const h = (data[k] / max) * (H - 40);
const x = i * bw;
ctx.fillRect(x + 3, H - 25 - h, bw - 6, h);
ctx.fillStyle = "#7d8ea8";
ctx.fillText(k, x + bw / 2, H - 10);
ctx.fillText(String(Math.round(data[k] * 100) / 100), x + bw / 2, H - 28 - h);
ctx.fillStyle = "#5cc8ff";
});
}
</script>
</body>
</html>