// 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, and the run's verdict shown as // a badge (executed, nothing checked against an intent), sealed by its receipt; // - a run of a program the engine refuses shows the verdict refused, with its code; // - "Your hardware" says, in a browser tab, that such a run goes through the desktop // app or the command line, since a tab must not hold a provider's credential; // - an error in the program is marked on its line; // - a QIR module and a jeff module open as OpenQASM 3 through the engine, with its // notes and check, and a module its profile forbids is refused with its code; // - a rotation runs on the state vector, ⟨Z0⟩ = cos 0.3; // - a device target: its coupling map with each pair and qubit coloured by its error, // a run placed on it and simulated under its noise with the target's hash in the // receipt and the layout drawn on the map, a snapshot of another format imported; // - 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; // - Versions: each change recorded with who made it; a proposal taken change by // change; whose hand each line and gate is in; the receipt binding it; a line // of work from an older version merged back; kept and downloaded; // - Rules: held lines marked, shaded and drawn; a gate set and a region; a // proposal's breaking changes shown with their rule and never taken; the // model told the rules; // - 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; a rewrite that reaches the GHZ // state by a different route is labelled so and flagged in Found, saved there with a // note, and kept with the page; an alternative that is the same circuit, taken whole, is // a choice the reader's taste learns from, and the card says how the draft becomes it, as // a chain of checked rewrites; // - the rewrite lens: each rewrite the program admits, its gates marked on the diagram, // one taken as the engine's proposal, and the list following the program; // - your own way: a while on one's own, kept in this browser, Explore waiting for it, a // version made then noted so, the convergence line, and its end; // - the study: a feature turned off and on, nothing kept until asked, a choice among // Improve's candidates kept with the engine's best, an ownership answer, a probe done with // the assistant paused and scored by the engine, the measures, a sealed protocol joined // (its condition set by the key, the features held), the record exported sealed, and // leaving it; // - 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; // - the compile lens: a rotation compiled to Clifford+T through checked passes, each with // its counts and check, the certificate verified here, the circuit after the last pass // and after a chosen one drawn, and a compile marked stale once the program changes; // - the estimate pane: a program with a gate under three controls and a rotation counted // (an ancilla, three Toffolis, the rotation at its word's T count), its estimate on a // chosen qubit, the frontier plotted and listed with the headline marked, a point // pointed at read out, every seal verified and the request re-run here, and an // estimate 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 correctness record: off by default, and while off nothing about the model's // answers is kept, not even a key in storage; turned on, a proposal is recorded with // the level the engine checked it to, the reader's "Not this" kept, and the failures // exported as an eval corpus in the conformance format; // - answer views: an answer in the answer view layer drawn from its text, each view's // numbers from the engine here, a number the model wrote in a view refused with its // code, the seal checked here; and an answer the scripted model streams, each view // drawn as its statement arrives, before the stream has ended; // - 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, each proposal with its verdict's badge, 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; // - generators: a program with an integer input shows it under the editor, is linted and // drawn at its value, and runs there; the Workbench starts from it, judges a hundred // blind edits, keeps the reader's log-depth tree as the cheapest, opens it in the // editor, and comes back with the page; the tree is named a known construction, GHZ by // doubling, with what it beats the reference on that the cost does not weigh; on a // line, the tree is an aberration, offered with the rule to drop, and flagged; your taste: // two kept generators compared and one chosen for its economy, the archive ranked by the // profile with each one's typicality, the dial turned, a second profile kept apart, and // the profiles kept with the page; with the // model, its generators are judged at every size, a wrong one and one with a gate the // specification forbids left out (an aberration), and it is told the specification and // shown the cheapest found; its CZ idea in Explore is a different route to GHZ; // - step through: the engine's debugger on the bit-exact method, a slider forward and // back to the same bits, the next gate named with its line and marked on the // diagram, each qubit on its Bloch sphere and phase disk, the amplitudes, the reduced // state of chosen qubits, teleportation's four branches a quarter each exactly, one // chosen and q2 then holding the state q0 held, and under noise the density matrix // beside its noiseless state; // - editing by the drawing: a gate dropped from the palette onto a wire, a gate dragged // to another wire, moved and deleted from the keyboard, each an edit to the program's // text recorded as the reader's, and a gate of a broadcast refused with its line; // - the circuit in words for a screen reader, and the drawing saved as SVG and LaTeX, // each the engine's; // - error correction: a pane closed until opened; a code shown with its certified distance and its seal checked, // qubits flipped on the lattice and their syndrome lit, the colour decoder's // correction marked and the residual judged, the certificate's witness shown, a // threshold measured and plotted one line per distance; and a run mitigated by // virtual distillation under its noise, raw and mitigated with their errors; // - the course, in English: a lesson's starter loaded and checked, not passed, What went // wrong? from the engine's verdict, a hint with the engine's pointer, the solution passed // and marked, a rule and a generator's cases each with their verdict, the assistant's hint // asked for without a model, the solution never on the page, and the progress kept in this // browser across a reload; // - the course in Chinese, from the browser's language: the interface, the lesson, the check // and what went wrong in Chinese, marked machine-assisted; the switch to English and back, // the choice kept; nothing scrolls sideways at a phone's width with the course open; // - 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; // The current version's line once it says all of `res`: a version is recorded // after the editor changes, so a check waits for the list rather than reading // it at once (false if it never does). const settles = (page, ...res) => page.waitForFunction((srcs) => { const text = document.querySelector('#qs-vs-list li.here')?.textContent ?? ''; return srcs.every((s) => new RegExp(s).test(text)); }, res.map((r) => r.source), { timeout: LONG }).then(() => true, () => false); const browser = await chromium.launch({ channel: process.env.WAI_E2E_CHANNEL ?? 'chrome', headless: true }); async function open(viewport = { width: 1400, height: 950 }, locale = 'en-US', path = '/') { const ctx = await browser.newContext({ viewport, colorScheme: 'dark', locale }); 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 + path, { 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'); { const badge = await page.$eval('#qs-verdict', (b) => ({ level: b.dataset.level, text: b.textContent, line: b.parentElement.textContent })); check(badge.level === 'executed' && /executed/.test(badge.text) && /no intent was given/.test(badge.line) && /sealed by the receipt/.test(badge.line), `the run states its verdict as a badge, sealed by its receipt (${badge.line})`); } // Your own hardware: in a browser tab the switch says where such a run goes, and stays here. await page.click('#qs-where-hardware'); check(/desktop app or the command line/.test(await page.textContent('.qs-status')) && /a browser tab must not hold the credentials/.test(await page.textContent('.qs-status')), `“Your hardware” says a run on your own hardware goes through the desktop app or the command line (${await page.textContent('.qs-status')})`); check((await page.getAttribute('[data-where=local]', 'aria-pressed')) === 'true' && await page.isHidden('#qs-provider'), 'and the Studio still runs in this browser'); // 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'); // Run as it is: refused, and the verdict says so with the reader's code. await page.click('#qs-run'); await page.waitForSelector('#qs-verdict[data-level=refused]', { timeout: LONG }); check(/Q103/.test(await page.$eval('#qs-verdict', (b) => b.parentElement.textContent)), `a refused run states the verdict refused, with its code (${await page.$eval('#qs-verdict', (b) => b.parentElement.textContent)})`); await page.click('.qs-editor .cm-content'); await page.keyboard.press('ControlOrMeta+End'); 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 program in another format: QIR and jeff opened, read by the engine to // OpenQASM 3 with its notes and its check; a module the reader refuses keeps // the editor as it was and says why, with its code. { const formats = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'quantum-conformance', 'formats'); await page.setInputFiles('#qs-open-file', { name: 'bell.ll', mimeType: 'text/plain', buffer: readFileSync(join(formats, 'qir', 'bell_base.ll')) }); await page.waitForFunction(() => /read from QIR/.test(document.querySelector('#qs-shows')?.textContent ?? ''), null, { timeout: LONG }); const fromQir = await page.evaluate(() => window.quantumStudio.view.state.doc.toString()); check(/^\/\/ Read from QIR 2\.0, base profile/m.test(fromQir) && /OPENQASM 3\.0;/.test(fromQir) && /cx q\[0\], q\[1\];/.test(fromQir) && /checked: equivalent/.test(await page.textContent('#qs-shows')), `a QIR module opens as OpenQASM 3, checked (${(await page.textContent('#qs-shows')).slice(0, 120)}…)`); await page.setInputFiles('#qs-open-file', { name: 'ghz.jeff', mimeType: 'application/octet-stream', buffer: readFileSync(join(formats, 'jeff', 'upstream', 'ghz-linear_3.jeff')) }); await page.waitForFunction(() => /read from jeff/.test(document.querySelector('#qs-shows')?.textContent ?? ''), null, { timeout: LONG }); const fromJeff = await page.evaluate(() => window.quantumStudio.view.state.doc.toString()); check(/Read from jeff 0\.3/.test(fromJeff) && /qubit\[3\] q;/.test(fromJeff) && /\[Q715\]/.test(await page.textContent('#qs-shows')), `a jeff module opens as OpenQASM 3 with the reader's notes (${(await page.textContent('#qs-shows')).slice(0, 160)}…)`); await page.waitForFunction(() => /qubits/.test(document.querySelector('#qs-lint')?.textContent ?? ''), null, { timeout: LONG }); await page.setInputFiles('#qs-open-file', { name: 'forbidden.ll', mimeType: 'text/plain', buffer: readFileSync(join(formats, 'qir', 'refuse', 'base_branch.ll')) }); await page.waitForFunction(() => /does not read as QIR/.test(document.querySelector('.qs-status')?.textContent ?? ''), null, { timeout: LONG }); check(/\[Q704\]/.test(await page.textContent('.qs-status')) && (await page.evaluate(() => window.quantumStudio.view.state.doc.toString())) === fromJeff, `a module its profile forbids is refused with its code, the editor kept (${await page.textContent('.qs-status')})`); } // 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]})`); // A device target: its coupling map drawn with each pair and qubit coloured by its // error; a run placed on its most reliable qubits, simulated under its noise, the // target's hash in the receipt, and the layout drawn on the map; a snapshot brought in // another format read and kept; and back to no device, the noise fields free again. { await page.selectOption('#qs-example', 'bell'); await page.waitForFunction(() => document.querySelector('#qs-lint')?.textContent === '2 qubits · 2 gates' && document.querySelector('#qs-circuit')?.textContent.includes('⊕'), null, { timeout: LONG }); await page.click('#qs-device > summary'); await page.waitForFunction(() => document.querySelectorAll('#qs-target option').length >= 5, null, { timeout: LONG }); await page.selectOption('#qs-target', 'synthetic-heavy-hex-27'); await page.waitForFunction(() => document.querySelectorAll('#qs-device-map .qs-dev-q').length === 27, null, { timeout: LONG }); const map = await page.$eval('#qs-device-map svg', (g) => ({ q: g.querySelectorAll('.qs-dev-q').length, pairs: g.querySelectorAll('.qs-dev-pair').length, label: g.getAttribute('aria-label'), bins: new Set([...g.querySelectorAll('.qs-dev-pair')].map((p) => p.getAttribute('data-bin'))).size, tips: g.querySelectorAll('title').length })); check(map.q === 27 && map.pairs === 28 && map.bins >= 4 && map.tips === 55 && /27 qubits and 28 coupled pairs/.test(map.label), `the coupling map: 27 qubits and 28 pairs, each coloured by its error in bins, each with its numbers on hover (${JSON.stringify(map)})`); check(await page.isDisabled('#qs-noise-readout') && await page.$eval('#qs-noise-note', (n) => !n.hidden), 'with a device chosen, the noise fields wait: the noise is the device’s own'); await page.waitForFunction(() => document.querySelectorAll('#qs-device-map .qs-dev-placed').length === 2, null, { timeout: LONG }); check(/^Placed on \d+, \d+; 0 swaps; expected success 0\.\d+ against 0\.\d+ for the fewest swaps/.test(await page.textContent('#qs-dev-placed')), `the plan places the program before it runs, and draws the layout on the map (${await page.textContent('#qs-dev-placed')})`); check(/Density matrix/.test(await page.textContent('#qs-plan')), `and plans it on the density matrix (${await page.textContent('#qs-plan')})`); await page.click('#qs-run'); await done(page); await verified(page); const res = await page.$eval('#qs-device-result', (b) => b.textContent); const hash = await page.textContent('#qs-dev-hash'); check(/synthetic-heavy-hex-27/.test(res) && /^[0-9a-f]{64}$/.test(hash) && /A model of the device, not a run on it/.test(res) && /total variation distance 0\.\d+/.test(res), `the result names the target, its hash, the placement and its distance from the ideal, and that it is a model (${res.slice(0, 140)}…)`); check((await page.textContent('.qs-method')) === 'Density matrix' && (await page.textContent('#qs-verified')).startsWith('✓ verifies'), 'run here on the density matrix, the receipt checked here'); const here = await counts(page); check(here.some(([k]) => k === '00') && here.some(([k]) => k === '11') && here.some(([k]) => k === '01' || k === '10'), `the device's noise moves some shots off 00 and 11 (${JSON.stringify(here)})`); await page.selectOption('#qs-dev-colour', 't1'); check(/T1 \(shorter is worse\)/.test(await page.$eval('#qs-device-map svg', (g) => g.getAttribute('aria-label'))) && /≥ \d/.test(await page.textContent('.qs-dev-legend')), 'the qubits can be coloured by T1, the legend reading longer as better'); await page.screenshot({ path: join(SHOTS, 'studio-device.png'), fullPage: true }); // A snapshot in the device-capabilities shape, imported and kept in this browser. const snapshot = readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'wai-quantum', 'src', 'engine', 'targets', 'snapshots', 'synthetic-ring-8.device-capabilities.json')); await page.setInputFiles('#qs-target-file', { name: 'ring.json', mimeType: 'application/json', buffer: snapshot }); await page.waitForFunction(() => document.querySelector('#qs-target')?.value === 'imported:synthetic-ring-8' && document.querySelectorAll('#qs-device-map .qs-dev-q').length === 8, null, { timeout: LONG }); check(/device-capabilities/.test(await page.textContent('.qs-status')), `a snapshot in another format is read and kept here (${await page.textContent('.qs-status')})`); await page.selectOption('#qs-target', ''); check(!(await page.isDisabled('#qs-noise-readout')) && (await page.$$('#qs-device-map svg')).length === 0, 'with no device, the noise fields are free again and no map is drawn'); } // 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'); // The run starts after the key, so its entry is waited for: the run button can still be // enabled, and the status not yet running, before the run has begun. const ranByKey = await page.waitForFunction((n) => document.querySelectorAll('.qs-hitem').length === n + 1, before, { timeout: LONG }).then(() => true, () => false); await done(page); check(ranByKey, '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()) && /checked the result/.test(document.querySelector('#qs-shows')?.textContent ?? ''), 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(/How: 1 rewrite, each checked/.test(card) && card.includes('rz(0.25) q[0] and rz(0.5) q[0] merge into rz(0.75) q[0]'), `the card says how the draft becomes it, a chain of checked rewrites (${card.slice(0, 220)})`); check((await doc()) === MERGE, 'nothing changes the program until an alternative is applied'); await page.click('.qs-cand .qs-apply'); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-apply', { timeout: LONG }); check((await doc()) === MERGE && /A proposal from the engine \(Alternatives\)/.test(await page.textContent('#qs-review')), 'Apply offers the alternative as the engine’s proposal, to take change by change'); await page.click('#qs-review-apply'); await page.waitForFunction(() => /rz\(0\.25 \+ 0\.5\) q\[0\];/.test(window.quantumStudio.view.state.doc.toString()), null, { timeout: LONG }); check(true, 'taking it puts the alternative in the editor'); await page.waitForFunction(() => document.querySelector('#qs-taste-count')?.textContent === '1', null, { timeout: LONG }); check(/^1 choice\b/.test(await page.textContent('#qs-taste-summary')), 'taken whole, an alternative that is the same circuit is a choice the reader’s taste learns from'); await page.click('#qs-explore'); await page.waitForSelector('#qs-alt-nomodel', { timeout: LONG }); check(true, 'Explore without a model says it needs one'); } // What is new: a rewrite that is the GHZ state, built unlike the known chain, is a // different route to it, and Found keeps it until the reader saves or dismisses it. { check((await page.textContent('#qs-found-count')) === '0 flagged · 0 saved' && await page.isVisible('#qs-found-empty'), 'Found starts empty'); await setProgram('OPENQASM 3.0;\nqubit[3] q;\nh q[2];\nx q[0];\nx q[0];\ncx q[2], q[1];\ncx q[1], q[0];\n'); await page.click('#qs-improve'); await page.waitForSelector('.qs-cand .qs-novelty', { timeout: LONG }); const labels = await page.$$eval('.qs-cand .qs-novelty', (bs) => bs.map((b) => [b.textContent, b.title])); const route = labels.find(([t]) => t === 'a different route to GHZ, by a chain of CNOTs'); check(route && await page.$('.qs-cand.find'), `a rewrite that makes the GHZ state, built unlike the chain, is labelled a different route (${labels.map(([t]) => t).join(' | ')})`); check(/0\.\d\d apart/.test(route?.[1] ?? ''), 'the label says how far apart they are built'); await page.waitForSelector('#qs-found-list .qs-found-item[data-kind=route]', { timeout: LONG }); check((await page.textContent('#qs-found-count')) === '1 flagged · 0 saved' && /different route/.test(await page.textContent('#qs-found-list')) && /Improve/.test(await page.textContent('#qs-found-list')), 'Found flags it, and says where it came from'); await page.click('#qs-found-list .qs-found-save'); await page.fill('#qs-found-list .qs-found-item input', 'the ladder, read backwards'); await page.dispatchEvent('#qs-found-list .qs-found-item input', 'change'); check((await page.textContent('#qs-found-count')) === '1 flagged · 1 saved' && (await page.textContent('#qs-found-list .qs-found-save')) === 'Saved', 'Save keeps it, and the count says so'); } // The rewrite lens: each rewrite that keeps the meaning, checked, taken one at a time. { const LENS = 'OPENQASM 3.0;\nqubit[2] q;\nh q[1];\ncx q[0], q[1];\nh q[1];\nt q[0];\nt q[0];\n'; await setProgram(LENS); // The diagram is drawn a moment after the edit; marks go on the one drawn. // (The measurements it draws at the end carry no gate.) await page.waitForFunction(() => /5 gates/.test(document.querySelector('#qs-lint')?.textContent ?? '') && [...document.querySelectorAll('#qs-diagram .qc-item')].filter((g) => /^\d+$/.test(g.dataset.op ?? '')).length === 5, null, { timeout: LONG }); await page.click('#qs-rewrites'); await page.waitForSelector('#qs-rewrite-list li', { timeout: LONG }); const rows = await page.$$eval('#qs-rewrite-list li', (ls) => ls.map((l) => [l.dataset.rule, l.textContent])); check(rows.some(([r, t]) => r === 'conjugate' && t.includes('cx q[0], q[1] between two h q[1] is cz q[0], q[1]')) && rows.some(([r, t]) => r === 'merge' && t.includes('t q[0] and t q[0] merge into s q[0]')), `the lens lists each rewrite the program admits, in words (${rows.map(([r]) => r).join(', ')})`); await page.hover('#qs-rewrite-list li[data-rule=conjugate]'); const marked = await page.waitForFunction(() => document.querySelectorAll('#qs-diagram .qc-item.qc-rewrite').length === 3, null, { timeout: 5000 }).then(() => true, () => false); check(marked, `pointing at one marks its gates on the diagram (${(await page.$$('#qs-diagram .qc-item.qc-rewrite')).length} marked)`); await page.click('#qs-rewrite-list li[data-rule=conjugate] .qs-rewrite-take'); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-apply', { timeout: LONG }); check(/A proposal from the engine \(Rewrite lens\)/.test(await page.textContent('#qs-review')) && (await doc()) === LENS, 'Take offers it as the engine’s proposal, to review'); await page.click('#qs-review-apply'); await page.waitForFunction(() => /cz q\[0\], q\[1\];/.test(window.quantumStudio.view.state.doc.toString()) && !/h q\[1\]/.test(window.quantumStudio.view.state.doc.toString()), null, { timeout: LONG }); await page.waitForFunction(() => !document.querySelector('#qs-rewrite-list li[data-rule=conjugate]') && document.querySelector('#qs-rewrite-list li[data-rule=merge]'), null, { timeout: LONG }); check(true, 'taken, the lens follows the program: the conjugation is gone and the merge is still offered'); } // Your own way: whether the versions converge on the model's, and a while on one's own. { check(/^Explore on my own/.test(await page.textContent('#qs-organic')) && await page.isHidden('#qs-organic-chip'), 'Versions offers a while on one’s own'); await page.click('#qs-organic'); check(await page.isVisible('#qs-organic-chip') && (await page.getAttribute('#qs-organic', 'aria-pressed')) === 'true' && (await page.evaluate(() => localStorage.getItem('wai.quantum-studio.organic'))) === '1', 'on one’s own: marked, and kept in this browser'); await page.click('#qs-explore'); await page.waitForSelector('#qs-alt-paused', { timeout: LONG }); check(true, 'Explore, which asks the model, waits'); await setProgram('OPENQASM 3.0;\nqubit[2] q;\nh q[0];\ncx q[0], q[1];\nz q[1];\n'); await page.evaluate(() => window.quantumStudio.versions.record()); const note = await page.evaluate(() => window.quantumStudio.versions.history().versions.at(-1).note); check(note === 'organic exploration', `a version made on one’s own is noted so (${note})`); await page.waitForFunction(() => /\.$/.test(document.querySelector('#qs-converge')?.textContent ?? ''), null, { timeout: LONG }); check(true, `the line says whether the versions converge on the model’s (${await page.textContent('#qs-converge')})`); await page.click('#qs-organic'); check(await page.isHidden('#qs-organic-chip') && /^Explore on my own/.test(await page.textContent('#qs-organic')), 'ended, the model may be asked again'); } // The study: features, a record kept, an ownership answer, a probe, a protocol joined, the record exported. { const SEALED = {"protocol":{"study":"the Studio's e2e study","hypotheses":["Without the taste profile, unassisted probe scores do not change."],"primary":"unassisted","conditions":[{"name":"everything"},{"name":"no taste","flags":{"taste":false}}],"control":"everything","analysis":{"resamples":1000,"seed":1,"level":0.95}},"seal":{"alg":"ed25519","canon":"rfc8785","key":"197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d61","digest":"a3d028f00e263d6ac33bfe61283f86e193316812f0f6bd16727108f937e39257","signature":"3f957f63a602925c49fa7fa010e29cea055ba4051180818f41d1d84539051027c4db992b5598ac36dd1145d6a0f408830743080b59ca6e80c129dbbdb0069d0a"}}; if (!(await page.$eval('#qs-features', (d) => d.open))) await page.click('#qs-features > summary'); await page.uncheck('#qs-flag-rewrites'); check(await page.isHidden('#qs-rewrites-panel'), 'a feature turned off is not there: the rewrite lens'); await page.check('#qs-flag-rewrites'); check(await page.isVisible('#qs-rewrites-panel'), 'and turned on, it is back'); check(await page.isHidden('#qs-study-tools'), 'nothing is kept until the reader asks'); await page.check('#qs-study-keep'); await page.waitForSelector('#qs-study-tools:not([hidden]) #qs-study-own', { timeout: LONG }); // A choice among Improve's candidates is kept, with the engine's best of them. await setProgram('OPENQASM 3.0;\nqubit[2] q;\nh q[0];\nh q[0];\nx q[1];\nx q[1];\ncx q[0], q[1];\n'); await page.click('#qs-improve'); await page.waitForFunction(() => document.querySelectorAll('.qs-cand').length >= 2 && !/looking/.test(document.querySelector('#qs-alt-body')?.textContent ?? ''), null, { timeout: LONG }); await page.click('.qs-cand >> nth=0 >> .qs-apply'); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-apply', { timeout: LONG }); await page.click('#qs-review-apply'); const kept = await page.evaluate(() => JSON.parse(localStorage.getItem('wai.quantum-studio.study')).choices); check(kept.length === 1 && kept[0].kind === 'alternatives' && kept[0].k >= 2 && kept[0].picked === 0 && Number.isInteger(kept[0].best), `the choice is kept, with how many were shown and the engine's best (${JSON.stringify(kept)})`); await page.click('#qs-study-own'); await page.waitForSelector('#qs-own-4', { timeout: LONG }); for (let i = 0; i < 5; i++) await page.selectOption(`#qs-own-${i}`, '6'); await page.click('#qs-own-save'); await page.waitForSelector('#qs-own-saved', { timeout: LONG }); check(true, 'an answer on ownership is kept'); await page.click('#qs-study-probe'); await page.waitForSelector('#qs-probe-answer', { timeout: LONG }); check(await page.isVisible('#qs-organic-chip'), 'during a probe the assistant is paused'); await page.selectOption('#qs-probe-pick', 'swap'); await page.fill('#qs-probe-answer', 'OPENQASM 3.0;\nqubit[2] q;\ncx q[0], q[1];\ncx q[1], q[0];\ncx q[0], q[1];\n'); await page.click('#qs-probe-score'); await page.waitForFunction(() => /^Verified\. Score 1\b/.test(document.querySelector('#qs-probe-result')?.textContent ?? ''), null, { timeout: LONG }); check(true, `the engine scores the probe (${await page.textContent('#qs-probe-result')})`); await page.click('#qs-probe-close'); check(await page.isHidden('#qs-organic-chip'), 'closed, the assistant is back'); await page.click('#qs-study-record'); await page.waitForSelector('#qs-study-measures', { timeout: LONG }); const cell = (k) => page.textContent(`#qs-study-measures tr[data-measure=${k}] td.num`); check((await cell('ownership')) === '6' && (await cell('unassisted')) === '1' && /^(0|1|0\.5)$/.test(await cell('selection')), `the record's measures are the engine's (ownership ${await cell('ownership')}, unassisted ${await cell('unassisted')}, selection ${await cell('selection')})`); // A study: the protocol is read, joined, and its condition holds the features. await page.setInputFiles('#qs-study-file', { name: 'study.sealed.json', mimeType: 'application/json', buffer: Buffer.from(JSON.stringify(SEALED)) }); await page.waitForSelector('#qs-study-join', { timeout: LONG }); const offer = await page.textContent('#qs-study-offer'); check(/Without the taste profile/.test(offer) && /digest a3d028f00e263d6a/.test(offer), 'the protocol is shown before joining: what it studies, and its digest'); await page.click('#qs-study-join'); await page.waitForFunction(() => /You take part in/.test(document.querySelector('#qs-study-status')?.textContent ?? ''), null, { timeout: LONG }); const cond = (await page.textContent('#qs-study-status')).match(/condition (everything|no taste)/)?.[1]; check(cond && (cond === 'no taste') === (await page.isDisabled('#qs-flag-taste')) && (cond === 'no taste') === (await page.isHidden('#qs-taste')) && !(await page.isDisabled('#qs-flag-rewrites')) && !(await page.isChecked('#qs-predict-first')), `joined: the key's condition (${cond}) sets the features it names and holds them; the others stay the reader's`); const files = []; page.on('download', (d) => files.push(d)); await page.click('#qs-study-export'); await page.waitForSelector('#qs-study-exported', { timeout: LONG }); await page.waitForTimeout(500); const sealedRecord = files.length ? JSON.parse(readFileSync(await files.at(-1).path(), 'utf8')) : null; const r = sealedRecord?.record; check(r?.protocol === SEALED.seal.digest && /^did:key:z6Mk/.test(r?.participant ?? '') && r.condition === cond && sealedRecord.seal?.signature?.length === 128 && r.ownership.length === 1 && r.probes.length === 1 && r.choices.length >= 1, `Export my record saves it sealed with this browser's key, for the researcher (${files.at(-1)?.suggestedFilename()})`); await page.click('#qs-study-leave'); check(!(await page.isDisabled('#qs-flag-taste')) && await page.isVisible('#qs-taste') && !(await page.isChecked('#qs-predict-first')), 'left, the features are the reader’s again, as they set them'); // The Study pane is the last of the side column: back to the top, where the editor's lines are drawn. await page.evaluate(() => window.scrollTo(0, 0)); } // Versions: each change and who made it; a proposal taken change by change; // whose hand each line and gate is in; the run's receipt binding the count; a // line of work from an older version, brought back in; and all of it kept. { const here = () => page.textContent('#qs-vs-list li.here'); const record = () => page.evaluate(() => window.quantumStudio.versions.record()); const BASE = 'OPENQASM 3.0;\nqubit[3] q;\nbit[3] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n'; await setProgram(BASE); await record(); check(/edited by you/.test(await here()), `what the reader types is recorded as theirs (${await here()})`); const first = await page.textContent('#qs-vs-list li:last-child'); check(/^v0started from the example /.test(first), `the first version is named for where the program came from (${first})`); const mine = await page.$eval('#qs-vs-list li.here', (l) => l.dataset.id); const PROPOSED = 'OPENQASM 3.0;\nqubit[3] q;\nbit[3] c;\nh q[1];\ncx q[0], q[1];\ncx q[1], q[2];\nc = measure q;\n'; await page.evaluate((q) => { window.quantumStudio.propose(q, { kind: 'assistant', name: 'scripted' }, 'entangle the third qubit'); }, PROPOSED); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-1', { timeout: LONG }); const offered = await page.$$eval('.qs-review-list > li', (ls) => ls.map((l) => l.textContent)); check(offered.length === 2 && /h q\[0\]/.test(offered[0]) && /\+ cx q\[1\], q\[2\]/.test(offered[1]) && await page.evaluate(() => window.quantumStudio.view.state.readOnly), `a proposal is shown change by change, and the program waits (${offered.map((t) => t.replace(/\s+/g, ' ')).join(' | ')})`); await page.uncheck('#qs-review-0'); check((await page.textContent('#qs-review-apply')) === 'Take 1 of 2', 'the reader takes the changes they want'); await page.click('#qs-review-apply'); const TAKEN = BASE.replace('cx q[0], q[1];\n', 'cx q[0], q[1];\ncx q[1], q[2];\n'); await page.waitForFunction((q) => window.quantumStudio.view.state.doc.toString() === q && !window.quantumStudio.view.state.readOnly, TAKEN, { timeout: LONG }); check(await settles(page, /proposed by the assistant \(scripted\)/, /1 of 2 taken/), `only the change taken reaches the program, recorded as the assistant's (${await here()})`); check(/1 of 2 changes taken; the rest were not taken/.test(await page.textContent('#qs-vs-detail')), 'the version keeps what was rejected'); await page.check('#qs-vs-hands'); // The editor draws gutter marks only for the lines in view, and it is as tall as the // side column: its first lines are brought into view. await page.evaluate(() => document.querySelector('.cm-editor').scrollIntoView({ block: 'start' })); await page.waitForFunction(() => document.querySelectorAll('.qs-hand-gutter .qs-hand:not(.qs-hand-spacer)').length === 7, null, { timeout: LONG }); const gutter = await page.$$eval('.qs-hand-gutter .qs-hand:not(.qs-hand-spacer)', (ms) => ms.map((m) => m.className.replace('qs-hand qs-hand-', ''))); await page.waitForFunction(() => document.querySelectorAll('#qs-diagram .qc-item.qc-by-assistant').length === 1, null, { timeout: LONG }); check(gutter[5] === 'assistant' && gutter.filter((h) => h === 'assistant').length === 1 && gutter.slice(1, 3).every((h) => h === 'person'), `“Who wrote it” marks each line in the gutter and each gate on the diagram (${gutter.join(', ')})`); // The reader edits the model's line: in both hands. await setProgram(TAKEN.replace('cx q[1], q[2];', 'cx q[0], q[2];')); await record(); await page.waitForFunction(() => document.querySelectorAll('.qs-hand-gutter .qs-hand-assistant_then_person').length === 1, null, { timeout: LONG }); check(/the assistant, then you \(scripted\): 1 gate, 1 line/.test(await page.textContent('#qs-vs-share')), `a model's line the reader edits is in both hands (${await page.textContent('#qs-vs-share')})`); await page.click('#qs-run'); await done(page); await page.waitForFunction(() => /recomputed/.test(document.querySelector('#qs-account')?.textContent ?? ''), null, { timeout: LONG }); const wrote = await page.textContent('#qs-written-by'); check(/1 by the assistant, then you \(scripted\)/.test(wrote) && /of 3 gates/.test(wrote) && (await page.textContent('#qs-verified')).startsWith('✓ verifies'), `the run's receipt binds whose hand each gate is in, recomputed here (${wrote}; ${await page.textContent('#qs-account')})`); // A line of work from an older version, then brought back in. await page.click(`#qs-vs-list li[data-id=${mine}] .qs-vs-item`); await page.click('#qs-vs-open'); await page.waitForFunction((q) => window.quantumStudio.view.state.doc.toString() === q, BASE, { timeout: LONG }); await setProgram(BASE.replace('h q[0];\n', 'h q[0];\nx q[2];\n')); await record(); const heads = await page.$$eval('#qs-vs-list li', (ls) => ls.filter((l) => /a head|in the editor/.test(l.textContent)).length); check(heads === 2 && (await page.$$('.qs-vs-svg .qs-vs-edge')).length >= 4, `an edit from an older version is a line of work of its own (${heads} heads)`); const other = await page.$eval('#qs-vs-list li:not(.here)', (l) => l.dataset.id); const otherHead = await page.$$eval('#qs-vs-list li', (ls) => ls.find((l) => /a head/.test(l.textContent))?.dataset.id); await page.click(`#qs-vs-list li[data-id=${otherHead}] .qs-vs-item`); await page.click('#qs-vs-merge'); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-apply', { timeout: LONG }); const merging = await page.$$eval('.qs-review-list > li', (ls) => ls.map((l) => l.textContent)); // Keep this line's x q[2]; take the other's edited CX. for (const [i, t] of merging.entries()) if (/− x q\[2\]/.test(t)) await page.uncheck(`#qs-review-${i}`); await page.click('#qs-review-apply'); await page.waitForFunction(() => /merged by you/.test(document.querySelector('#qs-vs-list li.here')?.textContent ?? ''), null, { timeout: LONG }); const merged = await doc(); check(/x q\[2\];/.test(merged) && /cx q\[0\], q\[2\];/.test(merged) && /, from v\d+ and v\d+/.test(await page.textContent('#qs-vs-detail')), `bringing a line of work back in is a merge of both (${other}, ${otherHead})`); check(/the assistant, then you \(scripted\): 1 gate/.test(await page.textContent('#qs-vs-share')), 'the merge keeps each line’s author'); const count = (await page.$$('#qs-vs-list li')).length; await page.reload({ waitUntil: 'domcontentloaded' }); await page.waitForSelector('#qs-vs-list li.here', { timeout: LONG }); const back = (await page.$$('#qs-vs-list li')).length; check(back === count && /merged by you/.test(await here()) && (await doc()) === merged, `the versions come back with the page (${back} of ${count}; ${await here()})`); const files = []; page.on('download', (d) => files.push(d)); await page.click('#qs-vs-download'); await page.waitForFunction(() => /program\.history\.json/.test(document.querySelector('.qs-status')?.textContent ?? ''), null, { timeout: LONG }); await page.waitForTimeout(500); const hist = files.find((d) => d.suggestedFilename() === 'program.history.json'); const saved = hist && JSON.parse(readFileSync(await hist.path(), 'utf8')); const prog = files.find((d) => d.suggestedFilename() === 'program.qasm'); check(saved?.versions?.length === count && saved.versions.every((v) => v.seal?.key?.length === 64 && v.digest.length === 64) && prog && readFileSync(await prog.path(), 'utf8') === merged, `Download saves the program and its history, every version sealed, as wai-quantum history verify reads them (${files.map((d) => d.suggestedFilename()).join(', ')})`); await page.uncheck('#qs-vs-hands'); } // Rules for proposals: held lines, a gate set and a region bind every proposal, // change by change; a change that breaks one is shown with it and cannot be taken. { const record = () => page.evaluate(() => window.quantumStudio.versions.record()); const BASE = 'OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n'; await setProgram(BASE); await record(); await page.evaluate(() => { const v = window.quantumStudio.view, l = v.state.doc.line(4); v.dispatch({ selection: { anchor: l.from, head: l.to } }); }); await page.click('#qs-rules > summary'); await page.click('#qs-rule-hold'); const HELD = 'OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\n// @lock\nh q[0];\n// @unlock\ncx q[0], q[1];\nc = measure q;\n'; await page.waitForFunction((q) => window.quantumStudio.view.state.doc.toString() === q, HELD, { timeout: LONG }); check((await page.$$('.cm-line.qs-held-line')).length === 3, 'holding the selected lines marks them in the program and shades them'); await page.waitForFunction(() => document.querySelectorAll('#qs-diagram .qc-item.qc-held').length === 1, null, { timeout: LONG }); check(true, 'the held gate is marked on the diagram'); await page.fill('#qs-rule-gates', 'h, cx, x'); await page.dispatchEvent('#qs-rule-gates', 'change'); await record(); // Three changes: a cz the gate set does not allow, the held line, and an x it does. const PROPOSED = HELD.replace('bit[2] c;\n', 'bit[2] c;\ncz q[0], q[1];\n').replace('h q[0];', 'h q[1];').replace('cx q[0], q[1];\n', 'cx q[0], q[1];\nx q[1];\n'); await page.evaluate((q) => { window.quantumStudio.propose(q, { kind: 'assistant', name: 'scripted' }, 'three changes'); }, PROPOSED); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-2', { timeout: LONG }); const rev = await page.$$eval('.qs-review-list > li', (ls) => ls.map((l) => l.textContent.replace(/\s+/g, ' '))); check(await page.isDisabled('#qs-review-0') && await page.isDisabled('#qs-review-1') && !(await page.isDisabled('#qs-review-2')) && /only cx, h, x may be used/.test(rev[0]) && /are held/.test(rev[1]), `a change that breaks a rule is shown with it and cannot be taken (${rev.map((t) => t.slice(0, 80)).join(' | ')})`); check((await page.textContent('#qs-review-apply')) === 'Take 1 of 1', 'only the change the rules allow can be taken'); await page.click('#qs-review-apply'); const KEPT = HELD.replace('cx q[0], q[1];\n', 'cx q[0], q[1];\nx q[1];\n'); await page.waitForFunction((q) => window.quantumStudio.view.state.doc.toString() === q, KEPT, { timeout: LONG }); check(await settles(page, /1 of 3 taken, 2 blocked/), `the version records what the rules blocked (${await page.textContent('#qs-vs-list li.here')})`); // A region: the next proposal keeps to the selected line. await page.evaluate(() => { const v = window.quantumStudio.view, l = v.state.doc.line(8); v.dispatch({ selection: { anchor: l.from, head: l.to } }); }); await page.click('#qs-rule-region'); check(/Only lines 8 to 8/.test(await page.textContent('#qs-rule-region-chip')), 'a region is kept to the selected lines'); const OUTSIDE = KEPT.replace('qubit[2] q;\nbit[2] c;', 'qubit[3] q;\nbit[3] c;').replace('x q[1];', 'x q[0];'); await page.evaluate((q) => { window.quantumStudio.propose(q, { kind: 'agent', name: 'test agent' }, 'two changes'); }, OUTSIDE); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-1', { timeout: LONG }); const out = await page.$$eval('.qs-review-list > li', (ls) => ls.map((l) => l.textContent.replace(/\s+/g, ' '))); check(await page.isDisabled('#qs-review-0') && /outside lines 8 to 8/.test(out[0]) && !(await page.isDisabled('#qs-review-1')), `a change outside the region is blocked (${out[0].slice(0, 90)})`); await page.click('#qs-review-apply'); await page.waitForFunction(() => document.querySelector('#qs-rule-region-chip')?.hidden, null, { timeout: LONG }); check((await doc()) === KEPT.replace('x q[1];', 'x q[0];'), 'the region holds for that proposal and is then let go'); await page.fill('#qs-rule-gates', ''); await page.dispatchEvent('#qs-rule-gates', 'change'); } // 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'); } // The compile lens: the passes with their counts and checks, the certificate // checked here, the circuit after any pass, and the compiled program offered. { const ANGLES = 'OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\nrz(0.3) q[0];\ncx q[0], q[1];\nc = measure q;\n'; await setProgram(ANGLES); await page.fill('#qs-compile-eps', '1e-4'); await page.click('#qs-compile'); await page.waitForSelector('#qs-passes tbody tr', { timeout: LONG }); const passes = await page.$$eval('#qs-passes tbody tr', (rs) => rs.map((r) => r.getAttribute('data-pass'))); const summary = await page.textContent('#qs-compile-summary'); check(passes[0] === 'simplify' && passes.includes('approximate') && passes.at(-1) === 'lower' && /^Approximate: within/.test(summary) && /verifies/.test(summary), `the program compiles through ${passes.length} checked passes to Clifford+T, the certificate checked here (${summary})`); const row = await page.textContent('#qs-passes tr[data-pass=approximate]'); check(/1→0/.test(row) && /approximate/.test(row), `the approximation writes the one rotation as T gates (${row.trim()})`); await page.waitForSelector('#qs-compile-diagram svg', { timeout: LONG }); check(/After lower \(pass \d+ of \d+\)/.test(await page.textContent('#qs-compile-caption')), 'the circuit after the last pass is drawn'); await page.click('#qs-passes tr[data-pass=simplify] button'); await page.waitForFunction(() => /After simplify \(pass 1 of/.test(document.querySelector('#qs-compile-caption')?.textContent ?? ''), null, { timeout: LONG }); const first = await page.$eval('#qs-compile-diagram svg', (g) => g.getAttribute('aria-label')); check(/2 qubits/.test(first) && await page.$('#qs-passes tr.on[data-pass=simplify]'), `choosing a pass draws the circuit after it (${first})`); await page.click('.qs-editor .cm-content'); await page.keyboard.press('ControlOrMeta+End'); await page.keyboard.type('\nh q[1];'); await page.waitForSelector('#qs-compile-panel.stale .qs-stale-note', { timeout: LONG }); check(true, 'a compile is marked stale once the program changes'); } // The estimate pane: the counts, the estimate on a chosen qubit, the frontier // plotted and listed, the seals checked and the request re-run here. { const LADDER = 'OPENQASM 3.0;\nqubit[4] q;\nh q;\nctrl(3) @ x q[0], q[1], q[2], q[3];\nrz(0.3) q[0];\nt q[1];\n'; await setProgram(LADDER); await page.selectOption('#qs-estimate-qubit', 'us-e3'); await page.fill('#qs-estimate-budget', '1e-4'); await page.click('#qs-estimate'); await page.waitForSelector('#qs-estimate-summary', { timeout: LONG }); const summary = await page.textContent('#qs-estimate-summary'); const counts = await page.textContent('#qs-estimate-counts'); check(/^On us-e3: [\d,]+ physical qubits \(\d+ T factor(y|ies), [\d.]+% of them\) for .+, at code distance \d+ \(surface code \(fit: exact matching\)\)\.$/.test(summary), `the program is estimated on the chosen qubit (${summary})`); check(/^5 logical qubits \(1 ancilla\), 4 measurements, 1 T, 3 Toffolis, 1 rotations \(\d+ T in their words\); T depth \d+, depth \d+\.$/.test(counts), `the counts take the gate under three controls through an ancilla (${counts})`); const eps = await page.textContent('#qs-estimate-eps'); check(/Each rotation within .* T apiece\. Code distance from P\(d\) .* fitted to this engine’s exact matching decoder/.test(eps), `the synthesis precision and the calibration are stated (${eps})`); const rows = await page.$$eval('#qs-frontier-table tbody tr', (rs) => rs.map((r) => r.textContent)); const dots = await page.$$eval('#qs-frontier svg .qs-frontier-point', (g) => g.length); check(rows.length >= 2 && dots === rows.length && rows.some((r) => r.includes('the estimate')), `the frontier is plotted and listed, the headline marked (${rows.length} estimates)`); await (await page.$('#qs-estimate-panel')).screenshot({ path: join(SHOTS, 'studio-estimate.png') }); await page.focus(`#qs-frontier svg .qs-frontier-point[data-i="${rows.length - 1}"]`); const read = await page.textContent('#qs-estimate-point'); check(/physical qubits \(\d+ factor(y|ies)\), .+, code distance \d+\.$/.test(read), `a point pointed at is read out (${read})`); const receipts = await page.textContent('#qs-estimate-receipts'); check(/estimates? sealed with did:key:.+: the seals verify, and the request re-run here gives every figure\./.test(receipts), `every estimate is sealed and re-run here (${receipts})`); await page.click('.qs-editor .cm-content'); await page.keyboard.press('ControlOrMeta+End'); await page.keyboard.type('\nt q[2];'); await page.waitForSelector('#qs-estimate-panel.stale .qs-stale-note', { timeout: LONG }); check(true, 'an estimate is marked stale once the program changes'); } // 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'); } // Generators: a program with integer inputs, read at the inputs bar's values. The // Workbench checks a family at every size and keeps the cheapest of each cell. const CHAIN = 'OPENQASM 3.0;\ninput uint n;\nqubit[n] q;\nh q[0];\nfor uint i in [0:n-2] {\n cx q[i], q[i + 1];\n}\n'; const TREE = 'OPENQASM 3.0;\ninput uint n;\nqubit[n] q;\nh q[0];\nfor uint k in [0:n] {\n for uint i in [0:2**k - 1] {\n if (2**k < n && i + 2**k < n) {\n cx q[i], q[i + 2**k];\n }\n }\n}\n'; const lintSays = (re) => page.waitForFunction((re) => new RegExp(re).test(document.querySelector('#qs-lint')?.textContent ?? ''), re.source, { timeout: LONG }); const wbLog = () => page.$$eval('#qs-wb-log li', (ls) => ls.map((l) => l.textContent)); const wbIdle = () => page.waitForFunction(() => !document.querySelector('#qs-wb-start').disabled, null, { timeout: LONG }); { await setProgram(CHAIN.replace('qubit[n] q;\n', 'qubit[n] q;\nbit[n] c;\n') + 'c = measure q;\n'); await page.waitForSelector('#qs-inputs:not([hidden]) #qs-input-n', { timeout: LONG }); await lintSays(/4 qubits · 4 gates at n = 4/); check(true, 'a generator shows its input under the editor, and the lint reads it at n = 4'); await page.fill('#qs-input-n', '3'); await page.dispatchEvent('#qs-input-n', 'change'); await lintSays(/3 qubits · 3 gates at n = 3/); await page.waitForFunction(() => /^The circuit: 3 qubits/.test(document.querySelector('#qs-diagram svg')?.getAttribute('aria-label') ?? ''), null, { timeout: LONG }); check(true, 'a new value re-reads it: linted and drawn at n = 3'); await page.click('#qs-run'); await done(page); const got = await counts(page); check(got.length === 2 && got.every(([k]) => k === '000' || k === '111'), `it runs at its value: a GHZ state on 3 qubits (${JSON.stringify(got)})`); await page.fill('#qs-input-n', '4'); await page.dispatchEvent('#qs-input-n', 'change'); await setProgram(CHAIN); await page.click('#qs-wb-start'); await page.waitForFunction(() => /^1 evaluated/.test(document.querySelector('#qs-wb-stats')?.textContent ?? ''), null, { timeout: LONG }); const st = await page.textContent('#qs-wb-stats'); check(/Best e0: cost 17\.3 against the reference’s 17\.3, by reference/.test(st) && (await page.$$('#qs-wb-map .qs-ring')).length === 1, `Start checks the reference at n = 3, 5 and 8 and seeds the archive with it (${st})`); await page.click('#qs-wb-search'); await page.waitForFunction(() => /The blind search: 100 edits judged/.test(document.querySelector('#qs-wb-log')?.textContent ?? ''), null, { timeout: LONG }); await wbIdle(); const after = await page.textContent('#qs-wb-stats'); check(/^101 evaluated/.test(after), `the blind search judges a hundred edits, each by the engine (${after})`); await setProgram(TREE); await page.click('#qs-wb-submit'); // What is new in it is said after the outcome, above it. await page.waitForFunction(() => [...document.querySelectorAll('#qs-wb-log li')].some((l) => /^Your program: /.test(l.textContent)), null, { timeout: LONG }); await wbIdle(); const said = (await wbLog()).find((t) => /^Your program: /.test(t)); check(/^Your program: kept as e\d+, cheaper than every generator before it \(cost 12\.3\)/.test(said) && /by person/.test(await page.textContent('#qs-wb-stats')), `the reader's log-depth tree is kept as the cheapest, and named as theirs (${said})`); const named = (await wbLog())[0]; check(/^e\d+: a known construction: GHZ, by doubling\.$/.test(named), `the tree is named for the known construction it is (${named})`); await page.click('#qs-wb-map .qs-wb-best'); await page.waitForSelector('#qs-wb-open', { timeout: LONG }); const card = await page.textContent('#qs-wb-entry'); check(/cost 12\.3/.test(card) && /checked by \w+, sealed/.test(card) && /person/.test(card) && !/null/.test(card), `an entry shows its cost, how it was checked, its seal and who wrote it (${card.slice(0, 140)})`); await page.waitForFunction(() => /a known construction/.test(document.querySelector('#qs-wb-novelty')?.textContent ?? ''), null, { timeout: LONG }); const novel = await page.textContent('#qs-wb-novelty'); check(/GHZ, by doubling/.test(novel) && /Better than the reference on what the cost does not weigh: two-qubit depth \d+ against \d+/.test(novel), `the entry says what it is and what it beats the reference on unasked (${novel})`); // Your taste: two kept generators, which the engine proves make the same state, compared. if (!(await page.$eval('#qs-taste', (d) => d.open))) await page.click('#qs-taste > summary'); // The choices the profile already holds: the alternatives taken whole above. const tasteBefore = Number((await page.textContent('#qs-taste-count')) || 0); const tasteNow = String(tasteBefore + 1); await page.click('#qs-taste-compare'); await page.waitForSelector('.qs-taste-pick', { timeout: LONG }); const pair = await page.$$eval('.qs-taste-side', (ss) => ss.map((s) => s.dataset.name)); check(pair.length === 2 && pair[0] !== pair[1] && /proves they do the same thing \(state\)/.test(await page.textContent('#qs-taste-pair')), `Compare two offers the pair the profile is least sure of, proved equal (${pair.join(', ')})`); await page.selectOption('#qs-taste-part', 'economy'); await page.click('.qs-taste-side >> nth=0 >> .qs-taste-pick'); await page.waitForSelector('#qs-taste-chosen', { timeout: LONG }); check(new RegExp(`^Kept: ${pair[0]} over ${pair[1]}, for its economy`).test(await page.textContent('#qs-taste-chosen')), `the choice is kept, with what it was about (${await page.textContent('#qs-taste-chosen')})`); await page.waitForFunction((n) => document.querySelector('#qs-taste-count')?.textContent === n, tasteNow, { timeout: LONG }); check(new RegExp(`^${tasteNow} choices \\(economy 1\\)`).test(await page.textContent('#qs-taste-summary')), `the profile counts its choices and their parts (${await page.textContent('#qs-taste-summary')})`); await page.click('#qs-taste-rank'); await page.waitForSelector('#qs-taste-list li', { timeout: LONG }); const ranked = await page.$$eval('#qs-taste-list li', (ls) => ls.map((l) => l.textContent)); const kept = await page.evaluate(() => Object.keys(JSON.parse(localStorage.getItem('wai.quantum-studio.workbench')).archive.cells).length); check(ranked.length === kept && ranked.every((t) => /typicality \d\.\d\d/.test(t) && /score -?\d/.test(t) && /cost \d/.test(t)), `Rank by my taste orders every kept generator, each with its typicality, score and cost (${ranked.length} of ${kept}: ${ranked[0]})`); check(kept <= 3 || /The first 3 are \d\.\d\d apart in how they are built; by cost, \d\.\d\d/.test(await page.textContent('#qs-taste-ranked')), 'the ranking says whether it narrows what comes first, against the cost'); await page.evaluate(() => { window.qsListBefore = document.querySelector('#qs-taste-list'); }); await page.$eval('#qs-taste-dial', (d) => { d.value = '1'; d.dispatchEvent(new Event('change')); }); await page.waitForFunction(() => document.querySelector('#qs-taste-list') && document.querySelector('#qs-taste-list') !== window.qsListBefore, null, { timeout: LONG }); check((await page.$$('#qs-taste-list li')).length === kept, 'turning the dial ranks them again'); await page.$eval('#qs-taste-dial', (d) => { d.value = '0'; d.dispatchEvent(new Event('change')); }); await page.fill('#qs-taste-profile', 'teaching'); await page.dispatchEvent('#qs-taste-profile', 'change'); await page.waitForFunction(() => /^No choices yet/.test(document.querySelector('#qs-taste-summary')?.textContent ?? ''), null, { timeout: LONG }); check((await page.textContent('#qs-taste-count')) === '', 'a second profile is kept apart: it has no choices'); await page.fill('#qs-taste-profile', 'mine'); await page.dispatchEvent('#qs-taste-profile', 'change'); await page.waitForFunction((n) => document.querySelector('#qs-taste-count')?.textContent === n, tasteNow, { timeout: LONG }); check(true, 'and the first comes back with its choices'); await setProgram(CHAIN); await page.click('#qs-wb-open'); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-apply', { timeout: LONG }); await page.click('#qs-review-apply'); await page.waitForFunction((q) => window.quantumStudio.view.state.doc.toString() === q, TREE, { timeout: LONG }); check(await settles(page, /proposed by you/), 'Open in editor offers the kept generator, recorded as its proposer’s, the reader’s'); await page.click('#qs-wb-ask'); check(/needs one/.test((await wbLog())[0]), 'Ask the model, with none connected, says it needs one'); const files = []; page.on('download', (d) => files.push(d)); await page.click('#qs-wb-save'); await page.waitForFunction(() => /^Saved workbench-/.test(document.querySelector('#qs-wb-log li')?.textContent ?? ''), null, { timeout: LONG }); await page.waitForTimeout(500); const read = async (d) => JSON.parse(readFileSync(await d.path(), 'utf8')); const [specFile, archiveFile] = [files.find((d) => !/archive/.test(d.suggestedFilename())), files.find((d) => /\.archive\.json$/.test(d.suggestedFilename()))]; const savedSpec = specFile && await read(specFile), savedArchive = archiveFile && await read(archiveFile); check(savedSpec?.reference === CHAIN && savedArchive?.spec?.length === 64 && savedArchive.entries.length >= 2 && savedArchive.entries.every((e) => e.sealed?.receipt) && archiveFile.suggestedFilename() === specFile.suggestedFilename().replace(/\.json$/, '.archive.json'), `Download saves the specification and its archive side by side, every entry sealed, as the command line reads them (${files.map((d) => d.suggestedFilename()).join(', ')})`); await page.reload({ waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => /Taken up again: 102 evaluated/.test(document.querySelector('#qs-wb-log')?.textContent ?? ''), null, { timeout: LONG }); check(/by person/.test(await page.textContent('#qs-wb-stats')) && (await page.inputValue('#qs-wb-sizes')) === 'n = 3, 5, 8', 'the archive and its specification come back with the page'); await lintSays(/qubits/); check((await page.textContent('#qs-taste-count')) === tasteNow && (await page.inputValue('#qs-taste-profile')) === 'mine', 'the taste profiles come back with the page'); check(/^\d+ flagged · 1 saved$/.test(await page.textContent('#qs-found-count')) && (await page.inputValue('#qs-found-list .qs-found-item.saved input')) === 'the ladder, read backwards', 'what Found keeps comes back with the page, with its note'); // On a line, the tree breaks only the rule that gates join neighbours: an aberration. if (!(await page.$eval('#qs-wb-spec', (d) => d.open))) await page.click('#qs-wb-spec > summary'); await page.selectOption('#qs-wb-conn', 'line'); await setProgram(CHAIN); await page.click('#qs-wb-start'); await page.waitForFunction(() => /^1 evaluated/.test(document.querySelector('#qs-wb-stats')?.textContent ?? ''), null, { timeout: LONG }); await setProgram(TREE); await page.click('#qs-wb-submit'); await page.waitForFunction(() => /an aberration/.test(document.querySelector('#qs-wb-log li')?.textContent ?? ''), null, { timeout: LONG }); await wbIdle(); const ab = (await wbLog())[0]; check(/^Your program: an aberration\. It breaks only one rule \(at n = 3: `cx` acts on qubits \[0, 2\], which are not neighbours on the line\); without it, it would be verified \(\w+\) at cost 12\.3/.test(ab) && /Try without that rule/.test(ab), `a generator that breaks only the line's rule is an aberration, with the cost it would have (${ab})`); check(/not verified/.test((await wbLog())[1]) && /\b1 kept\b/.test(await page.textContent('#qs-wb-stats')), 'an aberration is never kept'); check(await page.$('#qs-found-list .qs-found-item[data-kind=aberration]'), 'Found flags the aberration'); await page.click('#qs-wb-log .qs-wb-try'); check((await page.inputValue('#qs-wb-conn')) === 'all' && /lets a gate join any pair/.test(await page.textContent('#qs-wb-stats')), 'Try without that rule loosens the specification, to start again under it'); } // 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 STREAMED_FIRST = `root = Answer([bell, counts, check, seal])\n\`\`\`qasm bell\n${GOOD}\`\`\`\ncounts = Histogram(@Sample(bell, 500))\n`; const STREAMED_REST = 'check = Verdict(@Check(bell, {"00": 0.5, "11": 0.5}))\nseal = Receipt(@Seal([bell, counts, check]))\n'; let release; const streamHeld = new Promise((r) => { release = r; }); 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', async () => { const r = JSON.parse(body); asked.push(r); const msgs = r.messages; // An answer in the answer view layer, streamed: its first part at once, the rest // only once the test has seen the first part drawn. if (r.stream) { res.writeHead(200, { ...cors, 'content-type': 'text/event-stream' }); const send = (t) => res.write(`data: ${JSON.stringify({ choices: [{ delta: { content: t } }] })}\n\n`); for (const piece of STREAMED_FIRST.match(/[\s\S]{1,9}/g)) send(piece); await streamHeld; for (const piece of STREAMED_REST.match(/[\s\S]{1,9}/g)) send(piece); res.end('data: [DONE]\n\n'); return; } // Alternatives' calls carry no tools: candidates, one of them wrong on purpose. if (!r.tools) { const want = msgs[1].content; const content = /^Write \d+ generators/.test(want) ? `Idea: double the qubits that hold the state each layer.\n\`\`\`qasm\n${TREE}\`\`\`\nIdea: forget the Hadamard.\n\`\`\`qasm\n${CHAIN.replace('h q[0];\n', '')}\`\`\`\nIdea: use CZ.\n\`\`\`qasm\n${CHAIN.replace('cx q[i], q[i + 1];', 'h q[i + 1];\n cz q[i], q[i + 1];\n h q[i + 1];')}\`\`\`` : /^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 \[Q103\]\)/.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})`); const level = await page.$eval('.qs-ai-proposal .qs-level', (b) => ({ level: b.dataset.level, title: b.title })); check(level.level === 'executed' && /Executed on stabilizer/.test(level.title), `the proposal states the level the engine checked it to, as a badge (${JSON.stringify(level)})`); 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'); await page.waitForSelector('#qs-review:not([hidden]) #qs-review-apply', { timeout: LONG }); check(/A proposal from the assistant \(scripted\)/.test(await page.textContent('#qs-review')), 'Apply offers the checked program as the assistant’s proposal, named'); await page.click('#qs-review-apply'); await page.waitForFunction((q) => window.quantumStudio.view.state.doc.toString() === q, GOOD, { timeout: LONG }); check(true, 'taken, it is the program in the editor'); check(await page.evaluate(() => localStorage.getItem('wai.quantum-studio.telemetry')) === null && /0 answers recorded; recording is off/.test(await page.textContent('#qs-tel-count')), 'with the correctness record off, as it is by default, nothing about the model’s answer is kept, not even a key in storage'); 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. // A rule the model is told: text to avoid. if (!(await page.$eval('#qs-rules', (d) => d.open))) await page.click('#qs-rules > summary'); await page.fill('#qs-rule-text', 'measure q[5]'); await page.dispatchEvent('#qs-rule-text', 'change'); 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(ideas.some((t) => /a GHZ state on three qubits/.test(t) && /a known construction: GHZ, by a chain of CNOTs/.test(t)) && ideas.some((t) => /entangle with CZ instead/.test(t) && /a different route to GHZ, by a chain of CNOTs/.test(t)), 'each idea says what is new in it: the chain is a known construction, the CZ one a different route to it'); check(muse.length >= 2 && muse.every((m) => m.includes('Add no line holding `measure q[5]`.')), 'the model is told the reader’s rules with every request for ideas'); await page.fill('#qs-rule-text', ''); await page.dispatchEvent('#qs-rule-text', 'change'); check((await doc()) === DRAFT, 'neither changes the program until one is applied'); // The Workbench asks the model for generators, and the engine judges each at every size. await setProgram(CHAIN); await page.click('#qs-wb-start'); await page.waitForFunction(() => /^1 evaluated/.test(document.querySelector('#qs-wb-stats')?.textContent ?? ''), null, { timeout: LONG }); await page.click('#qs-wb-ask'); await page.waitForFunction(() => /^use CZ: /.test(document.querySelector('#qs-wb-log li')?.textContent ?? ''), null, { timeout: LONG }); await wbIdle(); const wlog = await wbLog(); check(wlog.some((t) => /^double the qubits that hold the state each layer: kept as e\d+, cheaper than every generator before it/.test(t)) && wlog.some((t) => /^forget the Hadamard: not verified: at n = 3: different/.test(t)) && wlog.some((t) => /^use CZ: not verified: at n = 3: it uses `cz`, which the specification does not allow/.test(t)), `the model's generators, each judged at every size: one kept, a wrong one and a forbidden gate left out (${wlog.slice(0, 3).join(' | ')})`); check(wlog.some((t) => /^use CZ: an aberration\. It breaks only one rule \(at n = 3: it uses `cz`/.test(t)) && !wlog.some((t) => /^forget the Hadamard: an aberration/.test(t)), 'the forbidden gate that would be verified without the rule is an aberration; the wrong one is not'); const brief = asked.filter((r) => !r.tools).map((r) => r.messages[1].content).find((m) => /^Write 3 generators/.test(m)) ?? ''; check(/prepare the same state from \|0…0⟩/.test(brief) && /n = 3, 5, 8/.test(brief) && /only these gates: h, cx/.test(brief) && brief.includes('cx q[i], q[i + 1];') && /The cheapest found: cost 17\.3, the reference\./.test(brief), 'the model is told the specification and shown the cheapest found, with the cost the engine measured'); check(/by model: scripted/.test(await page.textContent('#qs-wb-stats')), 'the best is the model’s, and named as its'); await setProgram(DRAFT); // 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 }); // The correctness record, turned on: the proposal recorded, "Not this" kept, the failures exported. await setProgram(GOOD); await page.click('#qs-tel > summary'); await page.check('#qs-tel-on'); await page.click('.qs-ai-quick button >> text=Explain this circuit'); await page.waitForFunction(() => document.querySelectorAll('.qs-ai-reject').length >= 2 && /^1 answer recorded/.test(document.querySelector('#qs-tel-count')?.textContent ?? ''), null, { timeout: LONG }); await page.click('#qs-ai-log > li:last-child .qs-ai-reject'); const kept = await page.evaluate(() => JSON.parse(localStorage.getItem('wai.quantum-studio.telemetry'))); const rec = kept?.records?.[0]; check(kept?.on === true && kept.records.length === 1 && rec.format === 'wai.quantum.telemetry/1' && rec.verdict?.level === 'executed' && rec.decision === 'rejected' && /^[0-9a-f]{64}$/.test(rec.receipt ?? '') && rec.program === GOOD, `turned on, the proposal is one record: the level the engine checked it to, its receipt, and the reader's "Not this" (${JSON.stringify({ level: rec?.verdict?.level, decision: rec?.decision })})`); const telFiles = []; page.on('download', (d) => telFiles.push(d)); await page.click('#qs-tel-corpus'); await page.waitForTimeout(800); const corpus = telFiles.length ? JSON.parse(readFileSync(await telFiles.at(-1).path(), 'utf8')) : null; check(corpus?.format === 'wai.quantum-intent-conformance-bundle/1' && corpus.cases.length === 1 && corpus.cases[0].covers[0] === 'the person rejected it' && corpus.cases[0].qasm === GOOD, `the failures export as an eval corpus in the conformance format (${telFiles.at(-1)?.suggestedFilename()})`); // Answer views: an answer drawn from its text, a number the model wrote refused. await page.click('#qs-answers > summary'); const PASTED = `root = Answer([bell, counts, made_up, check, seal])\n\`\`\`qasm bell\n${GOOD}\`\`\`\ncounts = Histogram(@Sample(bell, 1000))\nmade_up = Histogram(512)\ncheck = Verdict(@Check(bell, {"00": 0.5, "11": 0.5}))\nseal = Receipt(@Seal([bell, counts, check]))\n`; await page.fill('#qs-av-input', PASTED); await page.click('#qs-av-draw'); await page.waitForSelector('#qs-av[data-done="true"]', { timeout: LONG }); await page.waitForFunction(() => document.querySelector('.qs-av-card[data-name="seal"] [data-receipt]')?.dataset.receipt !== 'checking', null, { timeout: LONG }); const av = await page.evaluate(() => Object.fromEntries([...document.querySelectorAll('.qs-av-card')].map((c) => [c.dataset.name, c.dataset.state]))); const bars = await page.$$eval('.qs-av-card[data-name="counts"] .qs-bar', (bs) => bs.map((b) => Number(b.dataset.count))); check(av.counts === 'drawn' && bars.length === 2 && bars.reduce((a, b) => a + b, 0) === 1000, `the histogram's counts are the engine's, run here (${bars.join(' + ')})`); check(await page.$eval('.qs-av-card[data-name="check"] .qs-level', (b) => b.dataset.level) === 'proof', 'the verdict is the engine’s: checked as proof'); check(await page.$eval('.qs-av-card[data-name="seal"] [data-receipt]', (b) => b.dataset.receipt) === 'valid', 'the seal over the program, the counts and the verdict is checked here'); const diag = await page.$$eval('#qs-av-diags li', (ls) => ls.map((l) => [l.dataset.code, l.dataset.severity, l.textContent])); check(av.made_up === 'dropped' && diag.some(([c, s, t]) => c === 'J104' && s === 'error' && /comes from the engine/.test(t)), `a number the model wrote in a view is refused, with its code (${diag.map(([c]) => c).join(', ')})`); check((await page.evaluate(() => JSON.parse(localStorage.getItem('wai.quantum-studio.telemetry')))).records.at(-1)?.codes?.includes('J104'), 'the answer is recorded with the codes it drew'); // An answer the model streams: each view drawn as its statement arrives. await page.fill('#qs-av-q', 'Show me what this program does.'); await page.click('#qs-av-ask'); await page.waitForFunction(() => document.querySelector('.qs-av-card[data-name="counts"]')?.dataset.state === 'drawn', null, { timeout: LONG }); const before = await page.evaluate(() => ({ done: document.querySelector('#qs-av').dataset.done ?? null, seal: document.querySelector('.qs-av-card[data-name="seal"]')?.dataset.state })); check(before.done === null && before.seal === 'waiting', `the histogram is drawn while the model is still writing, the seal's card waiting for its statement (${JSON.stringify(before)})`); release(); await page.waitForSelector('#qs-av[data-done="true"]', { timeout: LONG }); await page.waitForFunction(() => document.querySelector('.qs-av-card[data-name="seal"] [data-receipt]')?.dataset.receipt === 'valid', null, { timeout: LONG }).catch(() => {}); const streamed = asked.find((r) => r.stream); check(await page.$eval('.qs-av-card[data-name="seal"] [data-receipt]', (b) => b.dataset.receipt).catch(() => null) === 'valid' && /Engine calls:/.test(streamed?.messages?.[0]?.content ?? '') && /never write a count/.test(streamed.messages[0].content), 'the rest arrives and is drawn, its seal checked here; the model was told the layer, and that it writes no numbers'); await page.screenshot({ path: join(SHOTS, 'studio-answer-views.png'), fullPage: true }); model.close(); } // Step through: the engine's debugger, forward and back, on the branch the reader chooses; // the state's views; editing by the drawing; the circuit in words; the drawing saved. { const TELE = 'OPENQASM 3.0;\nqubit[3] q;\nbit[3] c;\nh q[0];\ns q[0];\nh q[1];\ncx q[1], q[2];\ncx q[0], q[1];\nh q[0];\nc[0] = measure q[0];\nc[1] = measure q[1];\nif (c[1]) x q[2];\nif (c[0]) z q[2];\nc[2] = measure q[2];\n'; const posIs = (re) => page.waitForFunction((re) => new RegExp(re).test(document.querySelector('#qs-debug-pos')?.textContent ?? ''), re.source, { timeout: LONG }); const at = async (n) => { await page.$eval('#qs-debug-slider', (s, n) => { s.value = String(n); s.dispatchEvent(new Event('input')); }, n); await posIs(new RegExp(`^position ${n} of`)); }; const digest = async () => (await page.textContent('.qs-dbg-amps .qs-note')).match(/digest ([0-9a-f]+)/)?.[1]; await setProgram(TELE); await page.waitForFunction(() => /3 qubits · 8 gates/.test(document.querySelector('#qs-lint')?.textContent ?? ''), null, { timeout: LONG }); await page.click('#qs-debug-start'); await posIs(/^position 0 of 9$/); check(/^exact · bit-exact$/.test(await page.textContent('#qs-debug-method')), `the debugger steps the program on the bit-exact method (${await page.textContent('#qs-debug-method')})`); check((await page.$$('#qs-debug-qubits .qs-bloch')).length === 3 && (await page.$$('#qs-debug-qubits .qs-disk')).length === 3, 'each qubit on its Bloch sphere and its phase disk'); await page.waitForFunction(() => document.querySelectorAll('#qs-debug-tree .qs-dbg-leaf').length === 4, null, { timeout: LONG }); const leaves = await page.$$eval('#qs-debug-tree .qs-dbg-leaf > button', (bs) => bs.map((b) => b.textContent)); check(leaves.length === 4 && leaves.every((t) => /weight 1\/4$/.test(t)), `teleportation branches four ways, each weighing 1/4 exactly (${leaves.join(' | ')})`); await at(6); const amps = await page.$$eval('#qs-debug-amps tbody tr', (rs) => rs.map((r) => r.children[3].textContent)); check(amps.length === 8 && amps.every((p) => p === '0.1250'), `before the measurements, eight amplitudes of probability 1/8 each (${amps.join(' ')})`); check(/^Next: measure q0 into bit 0 \(line 10\)$/.test(await page.textContent('#qs-debug-next')), `the next step is named with its line (${await page.textContent('#qs-debug-next')})`); check((await page.$$('#qs-diagram .qc-step')).length === 0, 'a measurement is no gate to mark'); const d6 = await digest(); await at(4); check((await page.$$('#qs-diagram .qc-step')).length === 1, 'the next gate is marked on the diagram'); await at(8); await at(6); check(d6 && (await digest()) === d6, `forward and back again, the same state bit for bit (digest ${d6})`); // The reduced state of q0 and q1 there: as mixed as q2's, which is maximally mixed. await page.click('#qs-debug-density > summary'); check(/purity 0\.500000, entropy 1\.000000 bits/.test(await page.textContent('#qs-debug-rho-note')), `the reduced state of q0 and q1, one bit of entropy (${await page.textContent('#qs-debug-rho-note')})`); // The branch both measurements read 1: both corrections run, and q2 holds |+i⟩. await page.click('#qs-debug-tree button[data-path="11"]'); // On that path both corrections run: two steps more. await posIs(/^position 6 of 11$/); await at(10); check(/outcomes 1 1 · weight 0\.25/.test(await page.textContent('#qs-debug-path')), `the path takes the branch chosen (${await page.textContent('#qs-debug-path')})`); check(/y \+1\.000/.test(await page.textContent('#qs-debug-qubits [data-q="2"] figcaption')), `q2 then holds the state q0 held, Bloch y = 1 (${await page.textContent('#qs-debug-qubits [data-q="2"] figcaption')})`); check((await page.getAttribute('#qs-debug-tree button[data-path="11"]', 'aria-pressed')) === 'true', 'and the branch is marked as the path'); // Under noise: the density matrix, beside the noiseless state. await page.$eval('#qs-noise-depolarizing_2q', (i) => { i.value = '0.05'; i.dispatchEvent(new Event('input')); }); await page.click('#qs-debug-start'); await page.waitForFunction(() => /^density_matrix/.test(document.querySelector('#qs-debug-method')?.textContent ?? '') && /Fidelity to it/.test(document.querySelector('#qs-debug-overlay')?.textContent ?? ''), null, { timeout: LONG }); await at(6); check(/Fidelity to it 0\.9/.test(await page.textContent('#qs-debug-overlay')) && (await page.$$('#qs-debug-overlay .qs-ideal')).length === 8, `under noise, the noisy state against the noiseless one (${(await page.textContent('#qs-debug-overlay')).slice(-40)})`); await page.$eval('#qs-noise-depolarizing_2q', (i) => { i.value = ''; i.dispatchEvent(new Event('input')); }); // Editing by the drawing. const BELL = 'OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n'; const gates = (n) => page.waitForFunction((n) => document.querySelectorAll('#qs-diagram .qc-item[data-op]').length === n, n, { timeout: LONG }); const record = () => page.evaluate(() => window.quantumStudio.versions.record()); await setProgram(BELL); await record(); await gates(2); const box = await page.$eval('#qs-diagram svg', (s) => ({ w: Number(s.getAttribute('width')) })); await page.dragAndDrop('.qs-gate-chip[data-gate="x"]', '#qs-diagram svg', { targetPosition: { x: box.w - 8, y: 65 } }); await page.waitForFunction(() => window.quantumStudio.view.state.doc.toString().includes('x q[1];'), null, { timeout: LONG }); check((await doc()) === BELL.replace('cx q[0], q[1];\n', 'cx q[0], q[1];\nx q[1];\n'), `a gate dropped on a wire is a statement in the program (${JSON.stringify(await doc())})`); await record(); check(/edited by you/.test(await page.textContent('#qs-vs-list li.here')), 'recorded as the reader’s edit'); await gates(3); await page.waitForFunction(() => document.querySelector('#qs-diagram .qc-item[data-op="2"]')?.dataset.qubits === '1', null, { timeout: LONG }); const g = await page.$eval('#qs-diagram .qc-item[data-op="2"]', (e) => { const r = e.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; }); await page.mouse.move(g.x, g.y); await page.mouse.down(); await page.mouse.move(g.x, g.y - 20, { steps: 4 }); await page.mouse.move(g.x, g.y - 40, { steps: 4 }); await page.mouse.up(); await page.waitForFunction(() => window.quantumStudio.view.state.doc.toString().includes('x q[0];'), null, { timeout: LONG }); check(/^Moved x q1 onto q0/.test(await page.textContent('.qs-status')), `a gate dragged to another wire (${await page.textContent('.qs-status')})`); // The diagram is drawn again from the new text, the moved gate selected. const drawnOn = (op, qs) => page.waitForFunction(([op, qs]) => document.querySelector(`#qs-diagram .qc-item[data-op="${op}"]`)?.dataset.qubits === qs, [op, qs], { timeout: LONG }); await drawnOn(2, '0'); await page.focus('#qs-diagram .qc-item[data-op="2"]'); await page.keyboard.press('ArrowDown'); await page.waitForFunction(() => window.quantumStudio.view.state.doc.toString().includes('x q[1];'), null, { timeout: LONG }); await drawnOn(2, '1'); await page.waitForFunction(() => document.activeElement?.dataset?.op === '2', null, { timeout: LONG }); await page.keyboard.press('Delete'); await page.waitForFunction((b) => window.quantumStudio.view.state.doc.toString() === b, BELL, { timeout: LONG }); check(true, 'moved and deleted from the keyboard, the program as it was'); await setProgram('OPENQASM 3.0;\nqubit[2] q;\nh q;\n'); await drawnOn(1, '1'); await page.focus('#qs-diagram .qc-item[data-op="1"]'); await page.keyboard.press('Delete'); await page.waitForFunction(() => /which makes 2 gates/.test(document.querySelector('.qs-status')?.textContent ?? ''), null, { timeout: LONG }); check((await doc()) === 'OPENQASM 3.0;\nqubit[2] q;\nh q;\n', `a gate of a broadcast is refused with its line (${await page.textContent('.qs-status')})`); // The circuit in words, and the drawing saved as the engine writes it. await setProgram(BELL); await page.waitForFunction(() => /CNOT, control qubit 0, target qubit 1/.test(document.querySelector('#qs-describe')?.textContent ?? ''), null, { timeout: LONG }); check((await page.getAttribute('#qs-diagram', 'aria-describedby')) === 'qs-describe' && (await page.textContent('#qs-describe')).startsWith('A circuit on 2 qubits and 2 classical bits, 2 gates'), `the diagram is described in the engine's words (${await page.textContent('#qs-describe')})`); const saved = async (button) => { const [d] = await Promise.all([page.waitForEvent('download'), page.click(button)]); return [d.suggestedFilename(), readFileSync(await d.path(), 'utf8')]; }; const [svgName, svgText] = await saved('#qs-export-svg'); check(svgName === 'circuit.svg' && svgText.startsWith(' d.open)), 'the error-correction pane waits closed until it is opened'); await page.click('#qs-qec > summary'); await page.waitForFunction(() => /\[\[9, 1, 3\]\]/.test(document.querySelector('#qs-qec-summary')?.textContent ?? ''), null, { timeout: LONG }); check(/Every logical operator has weight at least 3/.test(await page.textContent('#qs-qec-cert')) && /seal verifies here/.test(await page.textContent('#qs-qec-cert')), `the surface code is shown with its certified distance, sealed (${await page.textContent('#qs-qec-cert')})`); await page.selectOption('#qs-qec-code', 'color:5'); await page.waitForFunction(() => /\[\[19, 1, 5\]\]/.test(document.querySelector('#qs-qec-summary')?.textContent ?? ''), null, { timeout: LONG }); const faces = await page.$$eval('#qs-qec-lattice .qs-qec-face', (f) => [f.length, f.filter((x) => /c[012]/.test(x.getAttribute('class'))).length]); check(faces[0] === 9 && faces[1] === 9, `the colour code's nine faces are drawn in their colours (${faces})`); await page.click('#qs-qec-lattice [data-q="3"]'); await page.click('#qs-qec-lattice [data-q="11"]'); const lit = await page.$$eval('#qs-qec-lattice .qs-qec-face.on', (f) => f.length); check(lit > 0 && /2 qubits flipped/.test(await page.textContent('#qs-qec-result')), `two flipped qubits light their syndrome (${lit} faces; ${await page.textContent('#qs-qec-result')})`); check((await page.$$eval('#qs-qec-decoder option', (o) => o.map((x) => x.value))).join(',') === 'color,relay_bp,search', 'the decoders the colour code takes are offered'); await page.click('#qs-qec-decode'); await page.waitForFunction(() => /unharmed|logical error/.test(document.querySelector('#qs-qec-result')?.textContent ?? ''), null, { timeout: LONG }); const res = await page.textContent('#qs-qec-result'); check(/explains the syndrome/.test(res) && /unharmed/.test(res) && /seal verifies here/.test(res) && (await page.$$('#qs-qec-lattice .qs-qec-qubit.fix')).length >= 1, `the colour decoder corrects them and the residual is judged (${res})`); await page.click('#qs-qec-witness'); check((await page.$$('#qs-qec-lattice .qs-qec-qubit.wit')).length === 5, 'the certificate’s witness, a logical of weight 5, is shown on the lattice'); await (await page.$('#qs-qec-pane')).screenshot({ path: join(SHOTS, 'studio-qec.png') }); await page.fill('#qs-qec-shots', '600'); await page.click('#qs-qec-measure'); await page.waitForSelector('#qs-qec-plot svg', { timeout: LONG }); const series = await page.$$eval('#qs-qec-plot svg .qs-qec-line', (l) => l.length); const reading = await page.textContent('#qs-qec-reading'); check(series === 3 && /code capacity/.test(reading) && /seal verifies here/.test(reading), `a threshold is measured and plotted, one line per distance (${series}; ${reading})`); // Pulse: a program that is not a pulse program is refused by the pulse // reader; the example's schedule is drawn on its frames, and its run // integrated, bounded and sealed. check(!(await page.$eval('#qs-pulse', (d) => d.open)), 'the pulse pane waits closed until it is opened'); await setProgram('OPENQASM 3.0;\nqubit q;\nh q;\n'); await page.click('#qs-pulse > summary'); await page.waitForFunction(() => /\[Q460\]/.test(document.querySelector('#qs-pulse-summary')?.textContent ?? ''), null, { timeout: LONG }); check((await page.$$('#qs-pulse-timeline svg')).length === 0, `a circuit with declared qubits is refused by the pulse reader, with its code (${await page.textContent('#qs-pulse-summary')})`); await page.click('#qs-pulse-example'); await page.waitForFunction(() => /seal verifies here/.test(document.querySelector('#qs-pulse-summary')?.textContent ?? ''), null, { timeout: LONG }); const lanes = await page.$$eval('#qs-pulse-timeline .qs-pulse-lane', (l) => l.map((x) => x.getAttribute('data-frame'))); const plays = await page.$$eval('#qs-pulse-timeline .qs-pulse-play', (p) => p.length); const caps = await page.$$eval('#qs-pulse-timeline .qs-pulse-capture', (p) => p.length); const calls = await page.$$eval('#qs-pulse-timeline .qs-pulse-call-label', (c) => c.map((x) => x.textContent)); check(lanes.join(',') === 'q0,r0,c0' && plays === 3 && caps === 1 && calls.join(',') === 'sx $0,x $0,measure $0', `the example's schedule is drawn: a lane per frame, its plays, its capture and its gate calls (${lanes}; ${plays} plays; ${caps} capture; ${calls})`); check(/328 samples \(164 ns\)/.test(await page.textContent('#qs-pulse-summary')), `the schedule's length is stated (${await page.textContent('#qs-pulse-summary')})`); await page.fill('#qs-pulse-shots', '500'); await page.click('#qs-pulse-run'); await page.waitForFunction(() => /seal verifies here/.test(document.querySelector('#qs-pulse-result')?.textContent ?? ''), null, { timeout: LONG }); const pr = await page.textContent('#qs-pulse-result'); const pb = await page.textContent('#qs-pulse-binds'); check(/\$0 → c: P\(1\) = /.test(pr) && /Accuracy bounded/.test(pr) && /The receipt binds the program/.test(pb), `the pulse run gives the capture's reading, its bound and what its receipt binds, sealed (${pr.slice(0, 200)}; ${pb.slice(0, 120)})`); await (await page.$('#qs-pulse-pane')).screenshot({ path: join(SHOTS, 'studio-pulse.png') }); // A run that mitigates its value under its noise. await setProgram('OPENQASM 3.0;\nqubit[2] q;\nh q[0];\ncx q[0], q[1];\nrz(0.3) q[1];\n'); await page.fill('#qs-obs', 'Z0 Z1'); await page.evaluate(() => { document.querySelector('.qs-noise').open = true; }); await page.fill('#qs-noise-depolarizing_2q', '0.03'); await page.selectOption('#qs-mitigate', 'vd'); await page.click('#qs-run'); await done(page); await page.waitForSelector('#qs-mitigation', { timeout: LONG }); const m = await page.textContent('#qs-mitigation'); check(/Mitigated: vd/.test(m) && /⟨Z0 Z1⟩/.test(m) && /Bias: Assumes the error-free state/.test(m), `the run carries its mitigation, raw and mitigated with their errors and the method's assumptions (${m.slice(0, 160)})`); await page.selectOption('#qs-mitigate', 'none'); await page.fill('#qs-noise-depolarizing_2q', ''); await page.fill('#qs-obs', ''); } 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(); // ---- the course ---- // A link to it: `#course=` opens the pane at that lesson. { const { ctx, page, seen } = await open(undefined, 'en-US', '/#course=bell'); const at = await page.waitForFunction(() => !document.querySelector('#qs-course')?.hidden && document.querySelector('#qs-course-title')?.textContent === 'Two qubits, one answer', null, { timeout: LONG }).then(() => true, () => false); check(at && (await page.getAttribute('#qs-course-open', 'aria-expanded')) === 'true', `a link opens the course at its lesson (#course=bell: ${await page.textContent('#qs-course-title')})`); check(seen.errors.length === 0, `the course link: no console error${seen.errors.length ? ': ' + seen.errors.join(' | ') : ''}`); await ctx.close(); } const COURSE = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', 'wai-quantum', 'course', 'lessons'); const exercise = (id) => JSON.parse(readFileSync(join(COURSE, id, 'exercise.json'), 'utf8')); const lessonProgram = (v) => (Array.isArray(v) ? v.join('\n') + '\n' : v); const putProgram = (page, src) => page.evaluate((s) => { const v = window.quantumStudio.view; v.dispatch({ changes: { from: 0, to: v.state.doc.length, insert: s } }); }, src); const courseChecked = (page) => page.waitForFunction(() => !document.querySelector('#qs-course-check')?.disabled && /\S/.test(document.querySelector('#qs-course-statement')?.textContent ?? ''), null, { timeout: LONG }); async function checkCourse(page) { await page.evaluate(() => { document.querySelector('#qs-course-statement').textContent = ''; }); await page.click('#qs-course-check'); await courseChecked(page); return page.textContent('#qs-course-passed'); } { const { ctx, page, seen } = await open(); await page.waitForFunction(() => /qubits/.test(document.querySelector('#qs-lint')?.textContent ?? ''), null, { timeout: LONG }); await page.click('#qs-course-open'); await page.waitForFunction(() => document.querySelectorAll('#qs-course-list [data-lesson]').length === 21 && /\S/.test(document.querySelector('#qs-course-title')?.textContent ?? ''), null, { timeout: LONG }); check((await page.textContent('#qs-course-title')) === 'A qubit, a gate, a measurement' && /Lesson 1 of 21/.test(await page.textContent('#qs-course-where')), `the course opens at its first lesson (${await page.textContent('#qs-course-title')})`); await page.click('[data-lesson="bell"]'); await page.waitForFunction(() => document.querySelector('#qs-course-title')?.textContent === 'Two qubits, one answer', null, { timeout: LONG }); const bell = exercise('bell'); const courseText = await page.textContent('#qs-course'); check(!courseText.includes('cx q[0], q[1];') && /00 with probability 0.5/.test(await page.textContent('#qs-course-must')), 'the lesson states what the program must do, and not the solution'); await page.click('#qs-course-starter'); await page.waitForFunction((s) => window.quantumStudio.view.state.doc.toString() === s, lessonProgram(bell.starter), { timeout: LONG }); check(true, 'the starter program is loaded into the editor'); check((await checkCourse(page)) === 'not yet', `the starter is checked and not passed (${await page.textContent('#qs-course-statement')})`); const levels = await page.$$eval('#qs-course-cases .qs-level', (b) => b.map((x) => x.dataset.level)); check(levels.join() === 'proof', `its verdict is the engine's, checked as proof (${levels})`); await page.click('#qs-course-why'); await page.waitForSelector('#qs-course-wrong:not([hidden]) li', { timeout: LONG }); const wrongText = await page.textContent('#qs-course-wrong'); check(/00: your program gives 1, the exercise asks for 0\.5/.test(wrongText) && /Where to look:/.test(wrongText), `What went wrong? names the gap the engine measured (${wrongText.slice(0, 160)})`); await page.click('#qs-course-hint'); await page.waitForFunction(() => document.querySelectorAll('#qs-course-hints li').length === 1 && !document.querySelector('#qs-course-pointer').hidden, null, { timeout: LONG }); check(/Hint 1 of 2/.test(await page.textContent('#qs-course-hints')) && /outcome/.test(await page.textContent('#qs-course-pointer')), `a hint, with the engine's pointer (${await page.textContent('#qs-course-pointer')})`); await page.click('#qs-course-ask'); await page.waitForFunction(() => /needs a model/.test(document.querySelector('#qs-course-asked')?.textContent ?? ''), null, { timeout: LONG }); check(true, 'the assistant is asked for a hint only with a model connected, and says so'); await putProgram(page, lessonProgram(bell.reference)); check((await checkCourse(page)) === 'passed', `the solution passes (${await page.textContent('#qs-course-statement')})`); check(await page.$eval('[data-lesson="bell"]', (b) => b.classList.contains('done')), 'and the lesson is marked passed'); check(/1 of 21 lessons passed · kept only in this browser/.test(await page.textContent('#qs-course-progress')), `the progress says where it is kept (${await page.textContent('#qs-course-progress')})`); // A rule the engine measures, and a generator's cases each with their verdict. await page.click('[data-lesson="phase"]'); await page.waitForFunction(() => /^A phase you cannot see/.test(document.querySelector('#qs-course-title')?.textContent ?? ''), null, { timeout: LONG }); await putProgram(page, lessonProgram(exercise('phase').wrong.find((w) => w.fails === 'constraint').qasm)); await checkCourse(page); const rules = await page.$$eval('#qs-course-rules li', (l) => l.map((x) => x.className + ' ' + x.textContent)); check(rules.some((r) => /^bad ✗ only these gates: h, z/.test(r)), `a gate the exercise does not allow breaks its rule (${rules})`); await page.click('[data-lesson="bit-flip-code"]'); await page.waitForFunction(() => /^Three copies/.test(document.querySelector('#qs-course-title')?.textContent ?? ''), null, { timeout: LONG }); await putProgram(page, lessonProgram(exercise('bit-flip-code').wrong[1].qasm)); await checkCourse(page); const cases = await page.$$eval('#qs-course-cases li', (l) => l.map((x) => x.className)); check(cases.join() === 'ok,bad,ok', `a generator is checked in each case, each with its verdict (${cases})`); // Kept in this browser: the course reopens where it was, the pass still marked. await page.reload({ waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => !document.querySelector('#qs-course')?.hidden && document.querySelectorAll('#qs-course-list [data-lesson]').length === 21 && /^Three copies/.test(document.querySelector('#qs-course-title')?.textContent ?? ''), null, { timeout: LONG }); check(await page.$eval('[data-lesson="bell"]', (b) => b.classList.contains('done')), 'after a reload the course reopens where it was, the pass kept'); await page.screenshot({ path: join(SHOTS, 'studio-course.png'), fullPage: true }); check(seen.errors.length === 0, `the course: no console error${seen.errors.length ? ': ' + seen.errors.join(' | ') : ''}`); check(seen.failed.length === 0, `the course: no failed request${seen.failed.length ? ': ' + seen.failed.join(' | ') : ''}`); await ctx.close(); } { // The browser's language is Chinese: so is the Studio, until the reader picks another. const { ctx, page, seen } = await open(undefined, 'zh-CN'); await page.waitForFunction(() => /量子比特/.test(document.querySelector('#qs-lint')?.textContent ?? ''), null, { timeout: LONG }); check((await page.evaluate(() => document.documentElement.lang)) === 'zh-CN' && (await page.textContent('#qs-run')) === '▶ 运行', `the Studio follows the browser's language (${await page.textContent('#qs-run')}, ${await page.textContent('#qs-lint')})`); check(/机器辅助翻译/.test(await page.textContent('#qs-translation-note')), 'and says its words are a machine-assisted translation'); await page.click('#qs-course-open'); await page.waitForFunction(() => document.querySelector('#qs-course-title')?.textContent === '一个量子比特、一个门、一次测量', null, { timeout: LONG }); check(/第 1\/21 课/.test(await page.textContent('#qs-course-where')) && /机器辅助翻译/.test(await page.textContent('#qs-course-translation')), `the lesson in Chinese (${await page.textContent('#qs-course-where')})`); await page.click('#qs-course-starter'); await page.waitForFunction((s) => window.quantumStudio.view.state.doc.toString() === s, lessonProgram(exercise('first-circuit').starter), { timeout: LONG }); check((await checkCourse(page)) === '尚未通过', `checked in Chinese (${await page.textContent('#qs-course-statement')})`); await page.click('#qs-course-why'); await page.waitForSelector('#qs-course-wrong:not([hidden]) li', { timeout: LONG }); check(/1:你的程序给出 0,练习要求 1/.test(await page.textContent('#qs-course-wrong')), `What went wrong? in Chinese (${(await page.textContent('#qs-course-wrong')).slice(0, 120)})`); // At a phone's width, the course open, nothing scrolls sideways. await page.setViewportSize({ width: 390, height: 844 }); const over = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth); check(over <= 0, `in Chinese at 390 px, the course open, nothing scrolls sideways (overflow ${over} px)`); await page.setViewportSize({ width: 1400, height: 950 }); await page.screenshot({ path: join(SHOTS, 'studio-course-zh.png'), fullPage: true }); // The switch: English, kept over the browser's language; then Chinese again. await Promise.all([page.waitForEvent('load'), page.selectOption('#qs-lang', 'en')]); await page.waitForFunction(() => document.querySelector('#qs-run')?.textContent === '▶ Run', null, { timeout: LONG }); check((await page.evaluate(() => document.documentElement.lang)) === 'en', 'the switch picks English over the browser’s language'); await page.reload({ waitUntil: 'domcontentloaded' }); await page.waitForFunction(() => document.querySelector('#qs-run')?.textContent === '▶ Run', null, { timeout: LONG }); check(true, 'and the choice is kept in this browser'); await Promise.all([page.waitForEvent('load'), page.selectOption('#qs-lang', 'zh-CN')]); await page.waitForFunction(() => document.querySelector('#qs-run')?.textContent === '▶ 运行', null, { timeout: LONG }); check(true, 'and back to Chinese'); check(seen.errors.length === 0, `in Chinese: no console error${seen.errors.length ? ': ' + seen.errors.join(' | ') : ''}`); // A switch reloads the page, which aborts what was still loading; nothing else may fail. const failed = seen.failed.filter((f) => !f.endsWith('(net::ERR_ABORTED)')); check(failed.length === 0, `in Chinese: no failed request${failed.length ? ': ' + 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);