open-standards/wai/quantum-studio/test/studio.test.mjs
Transaction Science 7cef125325
All checks were successful
wai-quantum WebGPU member / member · DX12 and Vulkan (cbio-windows) (pull_request) Successful in 12m23s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 5s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
wai-quantum on every processor / emulated · aarch64-unknown-linux-gnu (pull_request) Successful in 1m47s
wai-quantum on every processor / emulated · armv7-unknown-linux-gnueabihf (pull_request) Successful in 2m49s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
WAI conformance / does this change touch it (pull_request) Successful in 7s
WAI energy receipts / does this change touch it (pull_request) Successful in 10s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 7s
WAI neural sink gates / does this change touch it (pull_request) Successful in 11s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m12s
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 10s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 8s
wai-quantum command line / does this change touch it (pull_request) Successful in 8s
wai-quantum dataflow member / does this change touch it (pull_request) Successful in 6s
wai-quantum CUDA member / does this change touch it (pull_request) Successful in 7s
wai-quantum signal processor / does this change touch it (pull_request) Successful in 5s
wai-quantum FPGA member / does this change touch it (pull_request) Successful in 7s
wai-quantum HIP member / does this change touch it (pull_request) Successful in 6s
wai-quantum Metal member / does this change touch it (pull_request) Successful in 5s
wai-quantum neural-processor member / does this change touch it (pull_request) Successful in 6s
wai-quantum npu-cores member / does this change touch it (pull_request) Successful in 7s
wai-quantum on every processor / emulated · loongarch64-unknown-linux-gnu (pull_request) Successful in 3m38s
wai-quantum OpenCL member / does this change touch it (pull_request) Successful in 6s
wai-quantum CUDA member / member · CUDA (jetson-hub) (pull_request) Successful in 27s
wai-quantum-qpu / does this change touch it (pull_request) Successful in 7s
wai-quantum HIP member / member · HIP (framework-128gb) (pull_request) Successful in 20s
wai-quantum service / does this change touch it (pull_request) Successful in 6s
wai-quantum matrix-extension member / does this change touch it (pull_request) Successful in 7s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 6s
wai-quantum tensor-engine member / does this change touch it (pull_request) Successful in 7s
wai-quantum CPU vector units / does this change touch it (pull_request) Successful in 5s
wai-quantum portable tensor program / does this change touch it (pull_request) Successful in 6s
wai-quantum OpenCL member / member · OpenCL GPU and CPU (jetson-hub) (pull_request) Successful in 24s
wai-quantum WebGPU member / does this change touch it (pull_request) Successful in 5s
WAI mbt2018_mean integer decode (regenerated vectors) / does this change touch it (pull_request) Successful in 5s
wai-web wasm32 / does this change touch it (pull_request) Successful in 5s
wai-quantum portable tensor program / tensor program · GPUs (jetson-hub) (pull_request) Successful in 15s
wai-quantum npu-cores member / member · npu-cores, neural processor (framework-128gb silicon) (pull_request) Successful in 1m13s
wai-quantum OpenCL member / member · OpenCL GPU (framework-128gb) (pull_request) Successful in 13s
wai-quantum CPU vector units / member · vector units, avx2 and avx512 (framework-128gb silicon) (pull_request) Successful in 10s
wai-quantum portable tensor program / tensor program · integrated GPU (framework-128gb) (pull_request) Successful in 8s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m3s
wai-quantum on every processor / emulated · powerpc64-unknown-linux-gnu (pull_request) Successful in 5m25s
wai-quantum on every processor / emulated · powerpc64le-unknown-linux-gnu (pull_request) Successful in 6m1s
wai-quantum on every processor / emulated · riscv64gc-unknown-linux-gnu (pull_request) Successful in 4m2s
wai-quantum WebGPU member / member · integrated GPU, Vulkan (framework-128gb) (pull_request) Successful in 7m37s
wai-quantum on every processor / emulated · s390x-unknown-linux-gnu (pull_request) Successful in 4m8s
wai-quantum on every processor / emulated · sparc64-unknown-linux-gnu (pull_request) Successful in 6m16s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 7s
wai-quantum on every processor / emulated · i686-unknown-linux-gnu (pull_request) Successful in 4m10s
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Successful in 20m25s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m2s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 27s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 10m17s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 18m12s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m24s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Successful in 1m45s
wai-pack / wai-pack · linux (pull_request) Successful in 1m20s
wai-quantum command line / command line · linux (pull_request) Successful in 1m7s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 6m57s
wai-quantum CUDA member / member · CUDA absent (linux) (pull_request) Successful in 24s
wai-quantum dataflow member / member · dataflow device, simulated (pci 1e52:b140) (pull_request) Successful in 3m25s
wai-quantum dataflow member / member · dataflow device, simulated (pci 1e52:401e) (pull_request) Successful in 4m35s
wai-quantum FPGA member / member · FPGA simulation absent (linux) (pull_request) Successful in 21s
wai-quantum Metal member / member · Metal absent (linux) (pull_request) Successful in 26s
wai-quantum neural-processor member / member · neural processor absent (linux) (pull_request) Successful in 26s
wai-quantum OpenCL member / member · OpenCL absent (linux) (pull_request) Successful in 21s
wai-quantum-qpu / clients against their contracts (linux) (pull_request) Successful in 20s
wai-quantum service / service · linux (pull_request) Successful in 39s
wai-quantum matrix-extension member / member · matrix extension, emulated (aarch64 Linux) (pull_request) Successful in 19s
wai-quantum studio and desktop / studio · browser (pull_request) Successful in 3m23s
wai-quantum signal processor / processor and member · signal processor, emulated (pull_request) Successful in 14m8s
wai-quantum CPU vector units / member · vector unit, rvv 1024-bit (riscv64, emulated) (pull_request) Successful in 19s
wai-quantum CPU vector units / member · vector unit, sve 128-bit (aarch64, emulated) (pull_request) Successful in 28s
wai-quantum CPU vector units / member · vector unit, rvv 128-bit (riscv64, emulated) (pull_request) Successful in 14s
wai-quantum CPU vector units / member · vector unit, sve 2048-bit (aarch64, emulated) (pull_request) Successful in 10s
wai-quantum tensor-engine member / member · tensor engine, the kernel toolchain's simulator (linux) (pull_request) Successful in 8m5s
wai-quantum CPU vector units / member · vector unit, avx2 (x86_64 silicon) (pull_request) Successful in 15s
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Successful in 12s
wai-quantum portable tensor program / tensor program · CPU, mutants, GPU builds (pull_request) Successful in 29s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 3m12s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m9s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 3m13s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m18s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 5m22s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 15s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m23s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 15m0s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 12s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 26s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m36s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m35s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 6m54s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m14s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 24s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m18s
wai-quantum HIP member / member · HIP absent (linux-arm64) (pull_request) Successful in 25s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 6m16s
wai-quantum npu-cores member / member · npu-cores absent (linux-arm64) (pull_request) Successful in 23s
wai-quantum tensor-engine member / member · tensor engine absent (linux-arm64) (pull_request) Successful in 19s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 4m8s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 35m22s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 8m33s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 14m16s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 23s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 54s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 6m14s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 3m12s
wai-pack / wai-pack · macos (pull_request) Successful in 1m3s
wai-quantum command line / command line · macos (pull_request) Successful in 28s
wai-quantum dataflow member / member · dataflow device absent (macos) (pull_request) Successful in 14s
wai-quantum FPGA member / member · FPGA datapath, simulated (macos) (pull_request) Successful in 1m13s
wai-quantum Metal member / member · Metal (macos) (pull_request) Successful in 29s
wai-quantum neural-processor member / member · neural processor (macos) (pull_request) Successful in 5m49s
wai-quantum OpenCL member / member · OpenCL GPU (macos) (pull_request) Successful in 29s
wai-quantum service / service · macos (pull_request) Successful in 44s
wai-quantum matrix-extension member / member · matrix extension absent (macos) (pull_request) Successful in 9s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 2m12s
wai-quantum CPU vector units / member · vector unit absent (macos) (pull_request) Successful in 13s
wai-quantum portable tensor program / tensor program · Metal and the arm64 CPU (macos) (pull_request) Successful in 15s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 4m26s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 1m43s
wai-quantum 0.6.14: design critique, equivalence, alternatives and classical shortcuts across every surface; contraction trees that sweep along the line
This carries 0.6.13, which was not published on its own, and 0.6.14.

## 0.6.13

The engine is the microscope and the judge, a local model is a muse that waits to be
asked, and the reader is the author. Every figure is the engine's. Nothing changes the
reader's program until they apply a change.

engine::critique gives a program's measures before it runs:
- gates by kind; one-, two- and three-qubit gates; T gates; gates off the Clifford
  angles;
- depth and two-qubit depth, and whether it is a Clifford circuit;
- a fidelity at reference error rates, named as a model.
It also gives the program's smells, each with its lines and operations: gates that
cancel, rotations that merge or do nothing, idle qubits, and qubits never measured.
critique::variants rewrites the program's own text with the smells fixed, all of them
to a fixed point and each one alone. Each candidate carries its measures and the
equivalence verdict; one the check finds different is never offered.

engine::equiv compares two programs up to a global phase:
- by tableau for Clifford circuits, at any size;
- otherwise on every basis state, to 8 qubits;
- otherwise on random product states, to 22 qubits, which is evidence, not proof;
- otherwise unchecked, with the reason.
qasm::parse_lines gives each operation's line, and draw::layout each gate's operation
index.

engine::shortcuts lists every exact classical route the engine knows, priced on the
program, and names the cheapest for each question:
- the stabilizer tableau;
- the cut-rank sum;
- an exact matrix product state, at a bond bounded by the operator Schmidt ranks of
  the gates crossing each cut. The bound is held by a test against the true Schmidt
  ranks of random circuits;
- the light cone;
- a tensor-network contraction, when asked to be thorough;
- the state vector.
A cheap route proves a design classically easy; none being cheap proves nothing. On
the 70-qubit random graph-state circuit the quick check finds the cut-rank sum (widest
cut 30) in under 2 s. The thorough one finds widest cut 29 in about 4 minutes.

Quantum Studio gains:
- the critique, always on under the diagram, with the smells flagged on it, Show and
  Fix;
- the classical cost, on request;
- Alternatives: Improve (the engine's rewrites and the model's, each checked) and
  Explore (the model's different circuits, each measured and compared), with an atlas
  and Apply;
- Predict first, which sets the reader's expected outcome against the run;
- an assistant that asks questions first and speaks only when asked, offering (never
  sending) a hint when allowed. Its new tools are critique_qasm, check_equivalent and
  classical_shortcuts.
wai-web exports the four engine calls. The desktop app answers them natively.
wai-quantum-cli 0.1.1 adds critique, equivalent (exit 0/1/2), improve and shortcuts,
with four MCP tools.

examples/cutrank_qasm gains probs: the exact 2^n·p of given bit strings, with their
linear cross-entropy.

## 0.6.14

A deep circuit on a line, contracted qubit by qubit along time, keeps a boundary of
only the bonds between two neighbouring qubits: about half the depth for a
controlled-Z brickwork. The contraction search rarely found that tree on its own.

- Network::amplitude_split_placed builds the same network as amplitude_split, index
  for index, with each index's place in the circuit: a qubit and a time.
- tensor_places, sweep_orders and sweep_path turn the places into the trees that sweep
  qubit by qubit, or time by time.
- find_path_seeded and find_path_restarts_seeded score such trees beside the trials and
  polish the best of them all alike. A seed that is not a tree of the network is
  ignored.
- The engine's tensor_network method and the thorough shortcut lens take both sweeps as
  seeds.

On the 70-qubit random graph-state circuit, one amplitude's largest intermediate falls
from 2^48 to 2^36, and its multiply-adds from 2^56.1 to 2^48.7, in the thorough lens.
With 16 trials and no seeds the search finds 2^59. The pinned plans in
tests/bit_identity.rs call find_path itself and are unchanged.

The job service answers four design checks: POST /v1/critique, /v1/equivalent,
/v1/improve and /v1/shortcuts, with the matching MCP tools. They run in its own process
within bounds:
- each counts against the client's submissions;
- at most two run at once, and one that waits ten seconds is refused with a 503;
- equivalence work past 4e9 gate amplitudes, and a thorough search, are refused with a
  422 naming the local command.
The smoke script checks each one. It now follows a job that outlasts /v1/run's wait,
as a client does, instead of failing on a loaded host.

wai-quantum-cli 0.1.2 carries the faster contraction.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-22 02:40:04 -04:00

363 lines
28 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// The Studio in the installed browser, as a reader uses it:
//
// node build.mjs --pkg PATH/TO/ENGINE/PKG && node test/studio.test.mjs
//
// Serves dist/ (with the engine package in dist/pkg/); the page's index.html names the
// live job service, https://wai.transaction.science/quantum-jobs, which the service checks use.
// - the first example loads; the program is linted, drawn and planned as it is typed;
// - it runs in this browser: the stabilizer tableau, only 00 and 11, ⟨Z0 Z1⟩ = 1, the
// receipt checked here and signed by this browser's key;
// - an error in the program is marked on its line;
// - a rotation runs on the state vector, ⟨Z0⟩ = cos 0.3;
// - the same Bell job on the service gives the counts run here, shot for shot, and its
// receipt checks here as the service's (left out with STUDIO_SKIP_SERVICE=1);
// - Ctrl+Enter in the editor runs; the program and the history survive a reload;
// - the critique: its measures as the program changes and their change since the last
// run; a smell named with its lines, flagged on the diagram, shown, and fixed by the
// engine's rewrite, checked the same circuit;
// - Alternatives: Improve offers the engine's rewrite checked equivalent, plotted and
// applied; Explore without a model says it needs one;
// - the classical-shortcut lens: a Clifford ring with a closing rotation layer priced by
// the cut-rank sum, a Clifford circuit easy at any width, a contraction searched only
// when thorough, and a check marked stale once the program changes;
// - predict first: the form stops a run until the reader predicts, refuses an outcome of
// the wrong width, and sets the prediction against the counts;
// - the assistant, against a scripted model server: a tool call run on the engine, a
// proposal the engine cannot read sent back with its reason, the corrected one checked
// and run before it is offered, and Apply; it asks questions first by default and speaks
// only when asked; offering a hint after refused runs only when the reader allows it,
// and never asking the model itself; Improve and Explore with the model, each candidate
// read, measured and compared by the engine, a rewrite that is not the same circuit
// left out with its reason;
// - no console error and no failed request; at a phone's width nothing scrolls sideways.
// Exit 0 iff every check passes.
import { createServer } from 'node:http';
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
import { dirname, extname, join, normalize, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const { chromium } = (await import(process.env.WAI_E2E_DRIVER ?? 'playwright')).default;
const DIST = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'dist');
const SHOTS = resolve(dirname(fileURLToPath(import.meta.url)), 'screenshots');
const TYPES = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.wasm': 'application/wasm', '.json': 'application/json' };
const server = createServer((req, res) => {
let file = normalize(join(DIST, decodeURIComponent(new URL(req.url, 'http://x').pathname)));
if (!file.startsWith(DIST)) { res.writeHead(403).end(); return; }
if (existsSync(file) && statSync(file).isDirectory()) file = join(file, 'index.html');
if (!existsSync(file)) { res.writeHead(404).end('not found'); return; }
res.writeHead(200, { 'content-type': TYPES[extname(file)] ?? 'application/octet-stream' });
res.end(readFileSync(file));
});
await new Promise((r) => server.listen(0, '127.0.0.1', r));
const ORIGIN = `http://127.0.0.1:${server.address().port}`;
let fails = 0;
const check = (ok, what) => { console.log(`${ok ? 'PASS' : 'FAIL'} ${what}`); if (!ok) fails += 1; };
const LONG = 120_000;
const browser = await chromium.launch({ channel: process.env.WAI_E2E_CHANNEL ?? 'chrome', headless: true });
async function open(viewport = { width: 1400, height: 950 }) {
const ctx = await browser.newContext({ viewport, colorScheme: 'dark' });
const page = await ctx.newPage();
const seen = { errors: [], failed: [] };
page.on('console', (m) => { if (m.type() === 'error') seen.errors.push(m.text()); });
page.on('pageerror', (e) => seen.errors.push(String(e)));
page.on('requestfailed', (r) => seen.failed.push(`${r.url()} (${r.failure()?.errorText})`));
page.on('response', (r) => { if (r.status() >= 400) seen.failed.push(`${r.url()} (${r.status()})`); });
await page.goto(ORIGIN + '/', { waitUntil: 'domcontentloaded' });
return { ctx, page, seen };
}
const done = (page) => page.waitForFunction(() => !/^Running|^On the service/.test(document.querySelector('.qs-status')?.textContent ?? '') && document.querySelector('#qs-run') && !document.querySelector('#qs-run').disabled, null, { timeout: LONG });
const verified = (page) => page.waitForFunction(() => /verifies|does not verify|not checked/.test(document.querySelector('#qs-verified')?.textContent ?? ''), null, { timeout: LONG });
const counts = (page) => page.$$eval('.qs-bar', (bs) => bs.map((b) => [b.querySelector('.qs-key').textContent, b.querySelector('.qs-n').textContent]));
const { ctx, page, seen } = await open();
await page.waitForFunction(() => /qubits/.test(document.querySelector('#qs-lint')?.textContent ?? ''), null, { timeout: LONG });
check((await page.textContent('#qs-lint')) === '2 qubits · 2 gates', `the first example is linted as the engine reads it (${await page.textContent('#qs-lint')})`);
await page.waitForFunction(() => document.querySelector('#qs-circuit')?.textContent.includes('⊕'), null, { timeout: LONG });
const drawing = await page.textContent('#qs-circuit');
check(/q0: .*H.*●/.test(drawing) && /q1: .*⊕/.test(drawing), 'the circuit is drawn as the program is typed');
const svg = await page.$eval('#qs-diagram svg', (g) => ({ label: g.getAttribute('aria-label'), rings: g.querySelectorAll('.qc-ring').length, dots: g.querySelectorAll('.qc-dot').length, joins: g.querySelectorAll('.qc-join').length, meters: g.querySelectorAll('.qc-measure').length }));
check(svg.rings === 1 && svg.dots === 1 && svg.joins === 1 && svg.meters === 2 && /2 qubits/.test(svg.label), `the circuit is drawn as a diagram with a text alternative (${JSON.stringify(svg)})`);
await page.click('#qs-astext');
check(await page.isVisible('#qs-circuit') && await page.isHidden('#qs-diagram'), '“Text” shows the circuit as text to copy');
await page.click('#qs-astext');
await page.waitForFunction(() => /Stabilizer tableau/.test(document.querySelector('#qs-plan')?.textContent ?? ''), null, { timeout: LONG });
check(true, `the plan is shown before running (${await page.textContent('#qs-plan')})`);
await page.click('#qs-run');
await done(page);
await verified(page);
check((await page.textContent('.qs-method')) === 'Stabilizer tableau', 'run in this browser: the stabilizer tableau');
const here = await counts(page);
check(here.length === 2 && here.every(([k]) => k === '00' || k === '11'), `only 00 and 11 (${JSON.stringify(here)})`);
const zz = await page.$$eval('.qs-table tbody tr', (rs) => rs.map((r) => r.textContent));
check(zz.some((t) => t.startsWith('⟨Z0 Z1⟩') && t.includes('1.000000')), `⟨Z0 Z1⟩ = 1 (${zz.slice(0, 2).join(' | ')})`);
check((await page.textContent('#qs-verified')).startsWith('✓ verifies'), `the receipt verifies, checked here (${await page.textContent('#qs-verified')})`);
check((await page.textContent('#qs-signed-by')).startsWith('this browser’s key did:key:z6Mk'), `signed by this browser's key (${await page.textContent('#qs-signed-by')})`);
check((await page.$$('.qs-hitem')).length === 1, 'the run is in the history');
// What it looks like, for a reader of the change: the dark scheme and the light.
mkdirSync(SHOTS, { recursive: true });
await page.screenshot({ path: join(SHOTS, 'studio-dark.png'), fullPage: true });
await page.emulateMedia({ colorScheme: 'light' });
await page.screenshot({ path: join(SHOTS, 'studio-light.png'), fullPage: true });
await page.emulateMedia({ colorScheme: 'dark' });
// An error is marked on its line.
await page.click('.qs-editor .cm-content');
await page.keyboard.press('ControlOrMeta+End');
await page.keyboard.type('\nfoo q[0];');
await page.waitForFunction(() => document.querySelector('#qs-lint')?.textContent === 'problem', null, { timeout: LONG });
await page.waitForSelector('.cm-lintRange-error', { timeout: LONG });
check(true, 'an unknown gate is marked in the editor');
for (let i = 0; i < 10; i++) await page.keyboard.press('Backspace');
await page.waitForFunction(() => /qubits/.test(document.querySelector('#qs-lint')?.textContent ?? ''), null, { timeout: LONG });
// A rotation: any angle, on the state vector.
await page.selectOption('#qs-example', 'rotation');
await page.waitForFunction(() => /State vector/.test(document.querySelector('#qs-plan')?.textContent ?? ''), null, { timeout: LONG });
await page.click('#qs-run');
await done(page);
const z = await page.$$eval('.qs-table tbody tr', (rs) => rs.map((r) => r.textContent));
check((await page.textContent('.qs-method')) === 'State vector, double precision' && z.some((t) => t.startsWith('⟨Z0⟩') && t.includes(Math.cos(0.3).toFixed(6))), `rx(0.3) on the state vector, ⟨Z0⟩ = cos 0.3 (${z[0]})`);
// The same Bell job on the service, and here: the same counts.
// (STUDIO_SKIP_SERVICE=1 leaves it out, where a build may not reach the service.)
if (process.env.STUDIO_SKIP_SERVICE) {
console.log('SKIP the service checks (STUDIO_SKIP_SERVICE)');
} else {
await page.selectOption('#qs-example', 'bell');
await page.click('#qs-run'); await done(page); await verified(page);
const local = await counts(page);
await page.click('.qs-seg[data-where=service]');
await page.click('#qs-run'); await done(page); await verified(page);
const remote = await counts(page);
check(JSON.stringify(remote) === JSON.stringify(local), `the service gives the counts run here, shot for shot (${JSON.stringify(remote)})`);
check((await page.textContent('#qs-signed-by')).startsWith('the service’s key') && (await page.textContent('#qs-verified')).startsWith('✓ verifies'), `the service's receipt checks here, as the service's (${await page.textContent('#qs-signed-by')})`);
await page.click('.qs-seg[data-where=local]');
}
// Ctrl+Enter runs; the program and the history survive a reload. (A job run again
// unchanged gives the same signed result, which the history keeps once; so this one
// asks for other shots.)
const before = (await page.$$('.qs-hitem')).length;
await page.fill('#qs-shots', '321');
await page.click('.qs-editor .cm-content');
await page.keyboard.press('ControlOrMeta+Enter');
await done(page);
check((await page.$$('.qs-hitem')).length === before + 1, 'Ctrl+Enter in the editor runs the program');
const program = await page.evaluate(() => window.quantumStudio.view.state.doc.toString());
await page.reload({ waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => /qubits/.test(document.querySelector('#qs-lint')?.textContent ?? ''), null, { timeout: LONG });
check((await page.evaluate(() => window.quantumStudio.view.state.doc.toString())) === program, 'the program survives a reload');
await page.waitForFunction((n) => document.querySelectorAll('.qs-hitem').length >= n, before + 1, { timeout: LONG });
check(true, 'the history survives a reload');
// The critique, as the program changes.
const doc = () => page.evaluate(() => window.quantumStudio.view.state.doc.toString());
const setProgram = async (q) => { await page.evaluate((q) => window.quantumStudio.fill({ qasm: q }), q); await page.waitForFunction((q) => window.quantumStudio.view.state.doc.toString() === q, q); };
const chip = (k) => page.$eval(`#qs-measures [data-k=${k}]`, (c) => c.textContent).catch(() => '');
{
await page.selectOption('#qs-example', 'bell');
await page.waitForFunction(() => /2 gates/.test(document.querySelector('#qs-measures')?.textContent ?? ''), null, { timeout: LONG });
const m = await page.textContent('#qs-measures');
check(/2 qubits/.test(m) && /1 two-qubit/.test(m) && await page.isVisible('#qs-clifford') && /fidelity, modelled/.test(m) && (await page.$$('#qs-smells li')).length === 0,
`the critique gives the Bell pair's measures, that it is Clifford, a modelled fidelity, and no smells (${m})`);
const SMELLY = 'OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\nx q[1];\nx q[1];\ncx q[0], q[1];\nc = measure q;\n';
await setProgram(SMELLY);
await page.waitForSelector('.qs-smell[data-kind=cancels]', { timeout: LONG });
const smell = await page.textContent('.qs-smell[data-kind=cancels] .qs-smell-text');
check(smell === 'x on qubit 1 twice in a row (lines 5 and 6): together they do nothing', `a smell is named with its lines (${smell})`);
check((await page.$$('#qs-diagram .qc-item.qc-flag')).length === 2, 'its two gates are flagged on the diagram');
await page.hover('.qs-smell[data-kind=cancels]');
check((await page.$$('#qs-diagram .qc-item.qc-smell')).length === 2 && (await page.$$('#qs-diagram .qc-wire.qc-smell')).length === 0, 'pointing at the smell marks its gates, and not their wires');
await page.click('.qs-smell[data-kind=cancels] button >> text=Show');
const sel = await page.evaluate(() => { const v = window.quantumStudio.view, d = v.state.doc, r = v.state.selection.main; return [r.from === d.line(5).from, r.to === d.line(6).to]; });
check(sel[0] && sel[1], 'Show selects its lines in the editor');
await page.click('#qs-run'); await done(page);
await page.click('.qs-smell[data-kind=cancels] .qs-fix');
await page.waitForFunction(() => !/x q\[1\];/.test(window.quantumStudio.view.state.doc.toString()), null, { timeout: LONG });
const shows = await page.textContent('#qs-shows');
check(/^Fixed: x on qubit 1/.test(shows) && /same tableau/.test(shows), `Fix applies the engine's rewrite, checked the same circuit (${shows})`);
await page.waitForFunction(() => /−2/.test(document.querySelector('#qs-measures [data-k=gates]')?.textContent ?? ''), null, { timeout: LONG });
check(true, `the measures show their change since the last run (${await chip('gates')})`);
await page.click('.qs-editor .cm-content');
await page.keyboard.press('ControlOrMeta+z');
await page.waitForFunction((q) => window.quantumStudio.view.state.doc.toString() === q, SMELLY, { timeout: LONG });
check(true, 'Undo brings the fixed lines back');
await setProgram('OPENQASM 3.0;\nqubit[3] q;\nbit[1] c;\nh q[0];\nc[0] = measure q[0];\n');
await page.waitForSelector('.qs-smell[data-kind=idle]', { timeout: LONG });
check((await page.$$('#qs-diagram .qc-wire.qc-flag')).length === 2 && !(await page.$('.qs-smell[data-kind=idle] .qs-fix')), 'qubits nothing uses are flagged on their wires, with no Fix');
}
// Alternatives: Improve, without a model; Explore says it needs one.
{
const MERGE = 'OPENQASM 3.0;\nqubit[1] q;\nbit[1] c;\nh q[0];\nrz(0.25) q[0];\nrz(0.5) q[0];\nc[0] = measure q[0];\n';
await setProgram(MERGE);
await page.click('#qs-improve');
await page.waitForSelector('.qs-cand', { timeout: LONG });
const card = await page.textContent('.qs-cand');
check(/the engine/.test(card) && /same circuit · on every basis state/.test(card) && /2 gates/.test(card), `Improve offers the engine's rewrite, checked on every basis state (${card.slice(0, 160)})`);
check(await page.isVisible('#qs-atlas') && (await page.$$('#qs-atlas .qs-dot')).length === (await page.$$('.qs-cand')).length && (await page.$$('#qs-atlas .qs-ring')).length === 1, 'the atlas plots the draft and each alternative');
check(/Connect a model/.test(await page.textContent('#qs-alt-body')), 'without a model, Improve says the engine’s are the only ones');
check((await doc()) === MERGE, 'nothing changes the program until an alternative is applied');
await page.click('.qs-cand .qs-apply');
check(/rz\(0\.25 \+ 0\.5\) q\[0\];/.test(await doc()), 'Apply puts the alternative in the editor');
await page.click('#qs-explore');
await page.waitForSelector('#qs-alt-nomodel', { timeout: LONG });
check(true, 'Explore without a model says it needs one');
}
// The classical-shortcut lens.
{
let RING = 'OPENQASM 3.0;\nqubit[16] q;\nbit[16] c;\nh q;\n';
for (let i = 0; i < 16; i++) RING += `cz q[${i}], q[${(i + 1) % 16}];\n`;
RING += 'rz(0.3) q;\nc = measure q;\n';
await setProgram(RING);
await page.click('#qs-shortcuts');
await page.waitForSelector('#qs-shortcuts-summary', { timeout: LONG });
const summary = await page.textContent('#qs-shortcuts-summary');
const cut = await page.textContent('#qs-routes tr[data-route=cutrank]');
check(/cut-rank sum/.test(summary) && /widest cut 2 bits/.test(cut) && await page.$('#qs-routes tr.best[data-route=cutrank]') && /does not apply/.test(await page.textContent('#qs-routes tr[data-route=stabilizer]')),
`the lens prices a Clifford ring with a closing layer by the cut-rank sum (${summary})`);
check(/not searched/.test(await page.textContent('#qs-routes tr[data-route=tensor_network]')), 'the quick check leaves the contraction search for a thorough one');
await page.click('#qs-shortcuts-thorough');
await page.waitForFunction(() => /2\^/.test(document.querySelector('#qs-routes tr[data-route=tensor_network]')?.textContent ?? ''), null, { timeout: LONG });
check(true, `a thorough check searches a contraction tree (${(await page.textContent('#qs-routes tr[data-route=tensor_network]')).trim()})`);
await page.click('.qs-editor .cm-content');
await page.keyboard.press('ControlOrMeta+End');
await page.keyboard.type('\nh q[0];');
await page.waitForSelector('#qs-shortcuts-panel.stale .qs-stale-note', { timeout: LONG });
check(true, 'a check is marked stale once the program changes');
await page.selectOption('#qs-example', 'bell');
await page.waitForFunction(() => /2 gates/.test(document.querySelector('#qs-measures')?.textContent ?? ''), null, { timeout: LONG });
await page.click('#qs-shortcuts');
await page.waitForFunction(() => /^Classically easy at any width/.test(document.querySelector('#qs-shortcuts-summary')?.textContent ?? '') && !document.querySelector('#qs-shortcuts-panel.stale'), null, { timeout: LONG });
check(true, 'a Clifford circuit is classically easy at any width');
}
// Predict first.
{
await page.selectOption('#qs-example', 'bell');
await page.check('#qs-predict-first');
const ran = (await page.$$('.qs-hitem')).length;
await page.click('#qs-run');
await page.waitForSelector('#qs-predict', { timeout: LONG });
check((await page.$$('.qs-hitem')).length === ran && !(await page.$('.qs-method')), 'with Predict first, the run waits for a prediction');
await page.fill('#qs-predict-bits', '000');
await page.fill('#qs-predict-share', '50');
await page.click('#qs-predict-run');
check(/2 classical bits; the outcome has 3/.test(await page.textContent('#qs-predict-note')), 'an outcome of the wrong width is refused, with the width');
await page.fill('#qs-predict-bits', '00');
await page.click('#qs-predict-run');
await done(page);
await page.waitForSelector('#qs-predicted', { timeout: LONG });
const pred = await page.textContent('#qs-predicted');
check(/you said00 in 50%/.test(pred) && /the run00 in \d/.test(pred) && /(came out on top|Another outcome)/.test(pred) && /first prediction/.test(pred), `the prediction is set against the counts (${pred})`);
await page.uncheck('#qs-predict-first');
}
// The assistant, against a scripted model server: it calls a tool, proposes a program
// the engine cannot read, gets the engine's reason back, and proposes a corrected one,
// which the Studio checks and runs before offering it.
{
const asked = [];
const GOOD = 'OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n';
const model = createServer((req, res) => {
const cors = { 'access-control-allow-origin': '*', 'access-control-allow-headers': 'content-type', 'access-control-allow-methods': 'GET, POST, OPTIONS' };
if (req.method === 'OPTIONS') { res.writeHead(204, cors).end(); return; }
if (req.url.endsWith('/models')) { res.writeHead(200, { ...cors, 'content-type': 'application/json' }).end(JSON.stringify({ data: [{ id: 'scripted' }] })); return; }
let body = '';
req.on('data', (c) => (body += c));
req.on('end', () => {
const r = JSON.parse(body);
asked.push(r);
const msgs = r.messages;
// Alternatives' calls carry no tools: candidates, one of them wrong on purpose.
if (!r.tools) {
const want = msgs[1].content;
const content = /^Write \d+ programs that are the same circuit/.test(want)
? 'Idea: the two Hadamards cancel, and the X moves past the CNOT.\n```qasm\nOPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\ncx q[0], q[1];\nx q[1];\nc = measure q;\n```\nIdea: drop the X.\n```qasm\nOPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\ncx q[0], q[1];\nc = measure q;\n```'
: 'Idea: a GHZ state on three qubits.\n```qasm\nOPENQASM 3.0;\nqubit[3] q;\nbit[3] c;\nh q[0];\ncx q[0], q[1];\ncx q[1], q[2];\nc = measure q;\n```\n**Idea:** entangle with CZ instead.\n```qasm\nOPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\nh q[1];\ncz q[0], q[1];\nh q[1];\nc = measure q;\n```';
res.writeHead(200, { ...cors, 'content-type': 'application/json' }).end(JSON.stringify({ choices: [{ message: { role: 'assistant', content }, finish_reason: 'stop' }] }));
return;
}
const told = msgs.some((m) => m.role === 'user' && /could not read the program you proposed/.test(m.content));
const toolDone = msgs.some((m) => m.role === 'tool');
let message;
if (!toolDone) message = { role: 'assistant', content: '', tool_calls: [{ id: 'c1', type: 'function', function: { name: 'lint_qasm', arguments: JSON.stringify({ qasm: GOOD }) } }] };
else if (!told) message = { role: 'assistant', content: 'It entangles two qubits. A variant:\n```qasm\nOPENQASM 3.0;\nqubit[2] q;\nfoo q[0];\n```' };
else message = { role: 'assistant', content: 'Corrected:\n```qasm\n' + GOOD + '```' };
res.writeHead(200, { ...cors, 'content-type': 'application/json' }).end(JSON.stringify({ choices: [{ message, finish_reason: message.tool_calls ? 'tool_calls' : 'stop' }], usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 } }));
});
});
await new Promise((r) => model.listen(0, '127.0.0.1', r));
await page.click('#qs-ai-gear');
await page.fill('#qs-ai-endpoint', `http://127.0.0.1:${model.address().port}/v1`);
await page.click('#qs-ai-connect');
await page.waitForFunction(() => /connected · scripted/.test(document.querySelector('#qs-ai-state')?.textContent ?? ''), null, { timeout: LONG });
check(true, 'the assistant connects to a model server and reads its models');
await page.click('.qs-ai-quick button >> text=Explain this circuit');
await page.waitForSelector('.qs-ai-apply', { timeout: LONG });
const log = await page.$$eval('#qs-ai-log > li', (ls) => ls.map((l) => l.textContent));
check(log.some((t) => t.includes('used lint_qasm')), 'the model\'s tool call ran on the engine, and the log shows it');
check(log.some((t) => /could not read the proposed program \(line 3: unknown gate foo\)/.test(t)), `a proposal the engine cannot read goes back with the engine's reason (${log.find((t) => /could not read/.test(t)) ?? 'none'})`);
const proposal = await page.textContent('.qs-ai-proposal');
check(/checked: 2 qubits, 2 gates/.test(proposal) && /ran here on Stabilizer tableau/.test(proposal), `the corrected proposal is checked and run before it is offered (${proposal})`);
check(asked.length === 3 && asked.every((r) => r.tools?.some((t) => t.function.name === 'run_job') && r.messages[0].role === 'system' && /Never state a probability/.test(r.messages[0].content)), 'every call to the model carries the engine\'s tools and the rule that numbers come from them');
const toolMsg = asked[1].messages.find((m) => m.role === 'tool');
check(toolMsg && JSON.parse(toolMsg.content).qubits === 2, 'the tool\'s answer went back to the model');
await page.click('.qs-ai-apply');
check((await page.evaluate(() => window.quantumStudio.view.state.doc.toString())) === GOOD, 'Apply puts the checked program in the editor');
check((await page.inputValue('#qs-ai-stance')) === 'questions' && (await page.inputValue('#qs-ai-initiative')) === 'asked' && /ask one short question/.test(asked[0].messages[0].content),
'by default the assistant asks questions first and speaks only when asked');
const quick = await page.$$eval('.qs-ai-quick button', (bs) => bs.map((b) => b.textContent));
check(quick.includes('Ask me a question') && quick.includes('Hint'), `it can be asked for a question or a hint (${quick.join(', ')})`);
check(asked.every((r) => !r.tools || r.tools.some((t) => t.function.name === 'critique_qasm') && r.tools.some((t) => t.function.name === 'check_equivalent')), 'its tools include the critique and the equivalence check');
// Runs refused twice: no hint offered while it speaks only when asked; offered, not sent, when allowed.
const calls = asked.length;
await setProgram('OPENQASM 3.0;\nqubit[1] q;\nfoo q[0];\n');
for (let i = 0; i < 2; i++) { await page.click('#qs-run'); await done(page); }
check(await page.isHidden('#qs-ai-stuck'), 'while it speaks only when asked, refused runs offer no hint');
await page.selectOption('#qs-ai-initiative', 'hint');
for (let i = 0; i < 2; i++) { await page.click('#qs-run'); await done(page); }
check(await page.isVisible('#qs-ai-stuck') && asked.length === calls, 'allowed to, it offers a hint after refused runs, and asks the model nothing');
await page.selectOption('#qs-ai-initiative', 'asked');
// Improve and Explore with the model: the engine reads, measures and compares each.
const DRAFT = 'OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\nh q[0];\nx q[1];\ncx q[0], q[1];\nc = measure q;\n';
await setProgram(DRAFT);
await page.click('#qs-improve');
await page.waitForFunction(() => document.querySelectorAll('.qs-cand').length && !/looking/.test(document.querySelector('#qs-alt-body')?.textContent ?? ''), null, { timeout: LONG });
const cards = await page.$$eval('.qs-cand', (cs) => cs.map((c) => c.textContent));
const refused = await page.textContent('#qs-alt-refused').catch(() => '');
check(cards.some((t) => /the model/.test(t) && /the two Hadamards cancel/.test(t) && /same circuit · equal tableaus/.test(t)) && /drop the X: not the same circuit/.test(refused),
`Improve with the model: its rewrite checked the same circuit, its wrong one left out with the reason (${refused})`);
await page.fill('#qs-explore-aim', 'another entangler');
await page.click('#qs-explore');
await page.waitForFunction(() => /a GHZ state/.test(document.querySelector('#qs-alt-body')?.textContent ?? ''), null, { timeout: LONG });
const ideas = await page.$$eval('.qs-cand', (cs) => cs.map((c) => c.textContent));
const muse = asked.filter((r) => !r.tools).map((r) => r.messages[1].content);
check(ideas.length === 2 && ideas.some((t) => /3 qubits/.test(t) && /a different circuit/.test(t)) && ideas.some((t) => /entangle with CZ instead/.test(t)) && muse.some((m) => /toward this aim: another entangler/.test(m)),
`Explore: the model's ideas, each read and measured by the engine (${ideas.map((t) => t.slice(0, 60)).join(' | ')})`);
check((await doc()) === DRAFT, 'neither changes the program until one is applied');
// The candidates come back within the editor's redraw delay: wait for the draft's diagram.
await page.waitForFunction(() => /4 gates/.test(document.querySelector('#qs-lint')?.textContent ?? '') && document.querySelectorAll('#qs-diagram .qc-flag').length === 2, null, { timeout: LONG });
check(!/null/.test(await page.textContent('#qs-alt-body')) && !/null/.test(await page.textContent('#qs-measures')), 'no empty part is written out as “null”');
await page.screenshot({ path: join(SHOTS, 'studio-alternatives.png'), fullPage: true });
model.close();
}
check(seen.errors.length === 0, `no console error${seen.errors.length ? ': ' + seen.errors.join(' | ') : ''}`);
check(seen.failed.length === 0, `no failed request${seen.failed.length ? ': ' + seen.failed.join(' | ') : ''}`);
await ctx.close();
{
const { ctx, page } = await open({ width: 390, height: 844 });
await page.waitForFunction(() => /qubits/.test(document.querySelector('#qs-lint')?.textContent ?? ''), null, { timeout: LONG });
const over = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
check(over <= 0, `at 390 px nothing scrolls sideways (overflow ${over} px)`);
await ctx.close();
}
await browser.close();
server.close();
console.log(`\n${fails === 0 ? 'every check passed' : `${fails} check(s) failed`}`);
process.exit(fails === 0 ? 0 : 1);