open-standards/wai/wai-quantum-py/python/wai_quantum/static/result.js
Transaction Science 22fdc7a3cb wai-quantum-py: WAI Quantum in Python and notebooks, in-process
A new crate, wai/wai-quantum-py, built by maturin 1.15.0 into one abi3 wheel
per platform (pyo3 0.29.3, Python 3.9 on), module wai_quantum. Its extension
module wraps the shared job engine and nothing else: run, plan, lint, draw
(text and the engine SVG), layout, critique, improve, shortcuts,
equivalent, verify, seal, canonical, digest, methods, accuracy_classes and
history, values crossing one to one with JSON (NaN, integers past 64 bits
and non-string keys refused). A job is the service form as a plain dict
with helpers (counts, probabilities, expectations, amplitudes, method,
accuracy, receipt); Key reads and writes the command line key files and
finds the same user key; bloch gives each qubit reduced state from the
engine expectation values. A refusal raises EngineError with the engine
message, its line, and a code slot for the engine diagnostic codes.

Service is a client in the standard library alone for the job service
(submit, status, result, cancel, run), naming itself
wai-quantum-python/<version>, its receipts checked here offline.

Widgets (the widgets extra): Circuit (the engine drawing, gates clickable,
authorship marks from a history), Result, Receipt (the engine verdict) and
Bloch (drag to turn), each a plain ES module, each with static HTML (and
SVG) fallbacks that render without the widget library.

Five doc notebooks run in CI and are held to their stored outputs.
ci/check.sh is the whole check; 62 pytest tests hold the package to the
command line (same results, plans, lints, critiques, drawings, methods,
equivalence verdicts; receipts verified both ways; the same user key; the
digest recomputed from Python canonical bytes), to the service run locally,
and run the front ends under node on the payloads; Python 3.9 smoke with
the wheel alone. Workflow wai-quantum-py.yml runs it on linux and macos.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-27 19:51:08 -04:00

110 lines
5.5 KiB
JavaScript

// The Result widget: a job's result as the engine gave it. Its counts as bars, the
// most frequent first (or their sampled frequencies: each count over the shots),
// its expectation values with the bound or standard error the method gives, its
// amplitudes, the method and the accuracy class it names, and the receipt's check
// made by the engine in Python. The model's `frequencies` and `top` choose what
// the bars show.
const SVG = 'http://www.w3.org/2000/svg';
const BAR = '#7aa0ff';
function el(tag, attrs = {}, ...kids) {
const e = document.createElement(tag);
for (const [k, v] of Object.entries(attrs)) {
if (k === 'text') e.textContent = v;
else if (v != null) e.setAttribute(k, String(v));
}
for (const k of kids) if (k != null) e.append(k);
return e;
}
function svgEl(tag, attrs = {}, text) {
const e = document.createElementNS(SVG, tag);
for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, String(v));
if (text != null) e.textContent = text;
return e;
}
const num = (x) => (typeof x === 'number' ? (Number.isInteger(x) ? String(x) : String(Number(x.toPrecision(6)))) : String(x));
function badge(ok) {
return el('span', { class: `wq-badge ${ok ? 'wq-good' : 'wq-bad'}`, text: ok ? 'verified' : 'INVALID' });
}
function histogram(counts, shots, top, frequencies) {
const rows = Object.entries(counts).slice(0, top);
if (!rows.length) return null;
const labelW = Math.max(7.5 * Math.max(...rows.map(([k]) => k.length)) + 12, 40);
const barW = 260, rowH = 18;
const peak = Math.max(...rows.map(([, v]) => v)) || 1;
const W = labelW + barW + 70, H = rowH * rows.length + 4;
const s = svgEl('svg', { width: W, height: H, viewBox: `0 0 ${W} ${H}`, class: 'wq-histogram', role: 'img', 'aria-label': `Counts of ${rows.length} outcomes` });
rows.forEach(([k, v], i) => {
const y = 2 + i * rowH;
const w = Math.max((barW * v) / peak, 1);
s.append(svgEl('text', { x: labelW - 6, y: y + 9, 'text-anchor': 'end', 'dominant-baseline': 'central', class: 'wq-mono' }, k));
const r = svgEl('rect', { x: labelW, y: y + 2, width: w.toFixed(1), height: rowH - 4, rx: 2, fill: BAR });
r.append(svgEl('title', {}, `${k}: ${v} of ${shots}`));
s.append(r);
s.append(svgEl('text', { x: (labelW + w + 6).toFixed(1), y: y + 9, 'dominant-baseline': 'central', class: 'wq-small' }, frequencies && shots ? (v / shots).toFixed(4) : String(v)));
});
return s;
}
function table(head, rows) {
const t = el('table', { class: 'wq-table' }, el('tr', {}, ...head.map((h) => el('th', { text: h }))));
for (const r of rows) t.append(el('tr', {}, ...r.map((c) => el('td', { class: 'wq-mono', text: c }))));
return t;
}
function render({ model, el: host }) {
const root = el('div', { class: 'wq wq-result-widget' });
host.append(root);
function draw() {
const d = model.get('data') || {};
const top = Math.max(1, model.get('top') || 16);
const frequencies = !!model.get('frequencies');
const head = el('div', {}, el('b', { text: d.method || '?' }), ' · accuracy ', el('b', { text: d.accuracy || '?' }));
if (d.accuracy_note) head.append(' ', el('span', { class: 'wq-muted', text: `(${d.accuracy_note})` }));
const parts = [head];
if (d.why) parts.push(el('div', { class: 'wq-muted', text: d.why }));
const counts = d.counts || {};
const n = Object.keys(counts).length;
if (n) {
const toggle = el('input', { type: 'checkbox' });
toggle.checked = frequencies;
toggle.addEventListener('change', () => { model.set('frequencies', toggle.checked); model.save_changes(); });
const topBox = el('input', { type: 'number', min: 1, max: Math.max(n, 1), value: Math.min(top, n), class: 'wq-num' });
topBox.addEventListener('change', () => { model.set('top', Math.max(1, Number(topBox.value) || 1)); model.save_changes(); });
parts.push(el('div', { class: 'wq-bar' },
el('span', { text: `${d.shots} shots, ${d.distinct_outcomes ?? n} distinct outcomes; showing` }), topBox,
el('label', { class: 'wq-check' }, toggle, el('span', { text: 'as sampled frequencies' }))));
parts.push(histogram(counts, d.shots, top, frequencies));
const rest = Object.values(counts).slice(top).reduce((a, b) => a + b, 0) + (d.omitted_shots || 0);
if (rest) parts.push(el('div', { class: 'wq-muted', text: `${rest} more shots fell on outcomes not drawn` }));
}
const exps = d.expectations || [];
if (exps.length) {
parts.push(table(['observable', 'value', 'error'], exps.map((e) => [
e.observable, num(e.value),
e.bound != null ? `± ${num(e.bound)} (bound)` : e.stderr != null ? `± ${num(e.stderr)} (std. error)` : '',
])));
}
const amps = d.amplitudes || [];
if (amps.length) parts.push(table(['basis state', 're', 'im', '|a|²'], amps.map((a) => [a.bits, num(a.re), num(a.im), num(a.re * a.re + a.im * a.im)])));
if (d.receipt) {
const r = d.receipt;
const line = el('div', { class: 'wq-line' }, 'receipt ', badge(r.valid === true), ' signed by ', el('span', { class: 'wq-mono', title: r.signer || '', text: r.signer || r.key || '?' }));
if (r.demo) line.append(' ', el('span', { class: 'wq-muted', text: '(the published demo key: it shows the result is unaltered, not who signed it)' }));
parts.push(line);
}
root.replaceChildren(...parts.filter(Boolean));
}
draw();
for (const ev of ['change:data', 'change:frequencies', 'change:top']) model.on(ev, draw);
return () => { for (const ev of ['change:data', 'change:frequencies', 'change:top']) model.off(ev, draw); };
}
export default { render };