wai-quantum for JavaScript: the engine as the npm package @transaction-science/wai-quantum 0.1.0 #534
9 changed files with 467 additions and 0 deletions
77
.github/workflows/wai-quantum-js-release.yml
vendored
Normal file
77
.github/workflows/wai-quantum-js-release.yml
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
name: wai-quantum for JavaScript, release tarball
|
||||
|
||||
# On a tag wai-quantum-cli-v<version> (the command line's release), builds the npm
|
||||
# package @transaction-science/wai-quantum (wai/wai-quantum-js) and attaches its
|
||||
# tarball to the same forge release, with its SHA-256. ci/check.sh builds it, holds
|
||||
# the tarball to the package's allow-list, runs the smoke test on it unpacked and the
|
||||
# README's Node example on it installed, before it is attached. Nothing is published
|
||||
# to a package registry: the tarball is published from the release by hand.
|
||||
#
|
||||
# Uploads go to the forge directly over the private network (FORGE_API), never through
|
||||
# the public tunnel.
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['wai-quantum-cli-v*']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FORGE_API: http://100.64.224.0:3400/api/v1
|
||||
FORGE_REPO: Transaction-Science/open-standards
|
||||
|
||||
jobs:
|
||||
version:
|
||||
name: the tag is the command line's version
|
||||
runs-on: linux
|
||||
outputs:
|
||||
version: ${{ steps.v.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- id: v
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
v="${GITHUB_REF_NAME#wai-quantum-cli-v}"
|
||||
crate="$(sed -n 's/^version = "\(.*\)"$/\1/p' wai/wai-quantum-cli/Cargo.toml | head -1)"
|
||||
[ "$v" = "$crate" ] || { echo "::error::the tag says $v, wai-quantum-cli/Cargo.toml says $crate"; exit 1; }
|
||||
echo "the npm package: $(python3 -I -c 'import json; print(json.load(open("wai/wai-quantum-js/package.json"))["version"])')"
|
||||
echo "version=$v" >> "$GITHUB_OUTPUT"
|
||||
|
||||
tarball:
|
||||
name: the npm tarball · linux
|
||||
needs: version
|
||||
runs-on: linux
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust 1.98.0 with the wasm32 target
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
command -v rustup >/dev/null 2>&1 || \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
|
||||
rustup toolchain install 1.98.0 --profile minimal
|
||||
rustup default 1.98.0
|
||||
rustup target add wasm32-unknown-unknown
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
- name: wasm-bindgen CLI 0.2.127
|
||||
shell: bash
|
||||
run: cargo install --locked wasm-bindgen-cli --version 0.2.127
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
- name: Build, check and hash
|
||||
shell: bash
|
||||
run: bash wai/wai-quantum-js/ci/check.sh "$RUNNER_TEMP/dist"
|
||||
- name: Attach to the release
|
||||
shell: bash
|
||||
env:
|
||||
FORGE_TOKEN: ${{ github.token }}
|
||||
run: bash wai/wai-quantum-cli/release/publish.sh "$GITHUB_REF_NAME" "wai-quantum ${{ needs.version.outputs.version }}" "$RUNNER_TEMP"/dist/*.tgz "$RUNNER_TEMP"/dist/*.tgz.sha256
|
||||
81
.github/workflows/wai-quantum-js.yml
vendored
Normal file
81
.github/workflows/wai-quantum-js.yml
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
name: wai-quantum for JavaScript
|
||||
|
||||
# The npm package @transaction-science/wai-quantum (wai/wai-quantum-js): the job
|
||||
# service's engine for WebAssembly (wai-web, feature quantum_engine) with its
|
||||
# bindings. ci/check.sh builds and packs it; the tarball, unpacked, holds the
|
||||
# package's allow-list and nothing else; the smoke test runs against what was
|
||||
# unpacked (a job run with a fresh key and its receipt verified, a forged result
|
||||
# refused, a program linted and drawn, a refusal with its diagnostic code); and the
|
||||
# tarball installed into an empty app runs the README's Node example by name.
|
||||
# Nothing is published.
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'wai/wai-quantum-js/**'
|
||||
- 'wai/wai-web/**'
|
||||
- 'wai/wai-quantum/**'
|
||||
- 'wai/wai-rs/**'
|
||||
- 'rust-toolchain.toml'
|
||||
- '.github/workflows/wai-quantum-js.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'wai/wai-quantum-js/**'
|
||||
- 'wai/wai-web/**'
|
||||
- 'wai/wai-quantum/**'
|
||||
- 'wai/wai-rs/**'
|
||||
- 'rust-toolchain.toml'
|
||||
- '.github/workflows/wai-quantum-js.yml'
|
||||
|
||||
jobs:
|
||||
relevant:
|
||||
name: does this change touch it
|
||||
runs-on: linux-arm64
|
||||
outputs:
|
||||
run: ${{ steps.gate.outputs.run }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: gate
|
||||
run: |
|
||||
set -euo pipefail
|
||||
r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-quantum-js.yml \
|
||||
"${{ github.event.pull_request.base.sha }}" \
|
||||
"${{ github.event.pull_request.head.sha }}")
|
||||
echo "this change touches the npm package: $r"
|
||||
echo "run=$r" >> "$GITHUB_OUTPUT"
|
||||
|
||||
js-linux:
|
||||
name: javascript · linux
|
||||
needs: relevant
|
||||
if: needs.relevant.outputs.run == 'true'
|
||||
runs-on: linux
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- name: Install Rust 1.98.0 with the wasm32 target
|
||||
shell: bash
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
command -v rustup >/dev/null 2>&1 || \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
|
||||
rustup toolchain install 1.98.0 --profile minimal
|
||||
rustup default 1.98.0
|
||||
rustup target add wasm32-unknown-unknown
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
# Pinned to the wasm-bindgen version in wai-web/Cargo.lock; build.sh refuses another.
|
||||
- name: wasm-bindgen CLI 0.2.127
|
||||
shell: bash
|
||||
run: cargo install --locked wasm-bindgen-cli --version 0.2.127
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
- name: Build, pack, unpack, smoke-test and install the package
|
||||
shell: bash
|
||||
run: bash wai/wai-quantum-js/ci/check.sh "$RUNNER_TEMP/npm"
|
||||
|
|
@ -6,6 +6,19 @@ wire format may change and such changes are recorded here.
|
|||
|
||||
## Unreleased
|
||||
|
||||
### wai-quantum for JavaScript: the engine as an npm package
|
||||
|
||||
- **`@transaction-science/wai-quantum` 0.1.0** (`wai/wai-quantum-js/`): the job service's engine for WebAssembly
|
||||
(`wai-web` with its `quantum_engine` feature, carrying the engine of `wai-quantum` 0.7.0) and its bindings, an ES
|
||||
module with its TypeScript declarations and no dependencies, for a page or for Node. `build.sh` builds it with the
|
||||
paths remapped and refuses a local path left in what it ships, and packs it. The package keeps its own version line,
|
||||
as the Python package does.
|
||||
- **CI:** `wai-quantum-js.yml` builds and packs it, holds the tarball to the package's allow-list, runs
|
||||
`test/smoke.mjs` on it unpacked (a job run with a fresh key and its receipt verified, a forged result refused, a
|
||||
program linted and drawn, a refusal with its code) and the README's Node example on it installed by name.
|
||||
`wai-quantum-js-release.yml` does the same on a `wai-quantum-cli-v*` tag and attaches the tarball and its SHA-256
|
||||
to the release. Nothing is published to a registry from CI.
|
||||
|
||||
### wai-quantum-cli 0.2.2: the registry's line shown as text
|
||||
|
||||
This release changes no code. 0.2.1 put the `mcp-name` line in an HTML comment, but the MCP Registry reads the
|
||||
|
|
|
|||
87
wai/wai-quantum-js/README.md
Normal file
87
wai/wai-quantum-js/README.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# @transaction-science/wai-quantum
|
||||
|
||||
The WAI quantum job service's engine in WebAssembly, for a page or for Node. A job is planned, run and sealed here exactly as the service plans, runs and seals it. The answer is the service's JSON, signed with a key you hold, and any job's receipt, the service's included, is checked here. Programs are linted, drawn and critiqued before they run.
|
||||
|
||||
This is version 0.1.0 of the package. It carries the engine of `wai-quantum` 0.7.0, built from `wai-web` with its `quantum_engine` feature. The package keeps its own version line, as the Python package does. The module also holds the rest of `wai-web`'s browser surface (reading WAI envelopes among it); this page is about the quantum engine.
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install @transaction-science/wai-quantum
|
||||
```
|
||||
|
||||
It is an ES module with its TypeScript declarations, and has no dependencies.
|
||||
|
||||
## In a page
|
||||
|
||||
`init()` fetches `wai_quantum_bg.wasm` from beside the module. With a bundler that does not serve it there, pass its URL: `init({ module_or_path: url })`.
|
||||
|
||||
```js
|
||||
import init, { QuantumEngine, engine_verify } from '@transaction-science/wai-quantum';
|
||||
|
||||
await init();
|
||||
const engine = new QuantumEngine(crypto.getRandomValues(new Uint8Array(32)), 256);
|
||||
const job = JSON.parse(engine.run(JSON.stringify({
|
||||
qasm: 'OPENQASM 3.0; qubit[2] q; bit[2] c; h q[0]; cx q[0], q[1]; c = measure q;',
|
||||
shots: 1000,
|
||||
})));
|
||||
console.log(job.method, job.result.counts);
|
||||
console.log(JSON.parse(engine_verify(JSON.stringify(job))).valid); // true
|
||||
```
|
||||
|
||||
## In Node
|
||||
|
||||
Node is given the WebAssembly's bytes:
|
||||
|
||||
```js
|
||||
import init, { QuantumEngine, engine_verify } from '@transaction-science/wai-quantum';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
const wasm = createRequire(import.meta.url).resolve('@transaction-science/wai-quantum/wai_quantum_bg.wasm');
|
||||
await init({ module_or_path: await readFile(wasm) });
|
||||
const engine = new QuantumEngine(randomBytes(32), 256);
|
||||
const job = JSON.parse(engine.run(JSON.stringify({ qasm: 'OPENQASM 3.0; qubit[1] q; bit[1] c; h q[0]; c = measure q;', shots: 100 })));
|
||||
console.log(job.result.counts, JSON.parse(engine_verify(JSON.stringify(job))).signer);
|
||||
```
|
||||
|
||||
## What it offers
|
||||
|
||||
Requests and answers are JSON strings, the job service's own. A call that cannot be answered returns `{"error": …}`; a program the engine refuses also gives its diagnostic `code` (such as `Q310`) and its `meaning`.
|
||||
|
||||
**`new QuantumEngine(seed, memory_mb)`**: an engine that signs with the Ed25519 key whose 32-byte seed is `seed`, and admits a job of at most `memory_mb` MiB. Keep the seed to keep the key.
|
||||
|
||||
- `signer_did()`: the key the receipts name, as a `did:key`.
|
||||
- `run(request)`: a job run and sealed. The answer holds `status`, `method`, `why`, `request` and `result`, with the result's `receipt`. The request is the service's (`qasm`, `shots`, `seed`, `method`, `observables`, `amplitudes`, `noise`, `intent`, `verdict`, `target`, `compile`, `mitigate` and more).
|
||||
- `plan(request)`: the method a job would run on and why, its memory and work, without running it.
|
||||
- `compile(request)`: a program compiled (`{"qasm", "epsilon", "level", "basis"}`), with a certificate of every pass, sealed.
|
||||
- `estimate(request)`: what a fault-tolerant machine would need to run a program, each estimate sealed.
|
||||
- `qec(op, args)`: error correction (`code`, `decode` or `threshold`), the answer sealed.
|
||||
- `methods()`: each method, its accuracy, and the most qubits it holds here.
|
||||
|
||||
**Functions:**
|
||||
|
||||
- `engine_verify(job)`: whether a job's receipt seals its request and result: `{"valid", "key", "signer"}`. Any change to either after sealing makes it false.
|
||||
- `engine_verdict(job, intent, memory_mb)`: a job's verdict against its intent (or the one given).
|
||||
- `engine_estimate_verify(answer)`: an estimate's seal, its receipts, and its request re-run to the same figures.
|
||||
- `engine_lint(src)`: a program read as the engine reads it: `{"ok": true, "qubits", "gates", …}`, or `{"ok": false, "error", "line", "code"}`.
|
||||
- `engine_draw(src, width)`: the circuit as text (`{"text"}`); `engine_layout(src)` gives it as data for a page to draw.
|
||||
- `engine_critique(src)`: what the engine can say about a program before it runs: its measures and its smells, each with its lines.
|
||||
- `engine_equivalent(a, b)`: whether two programs apply the same gates up to a global phase, and how that was decided.
|
||||
- `engine_repair(src, options)`: a program repaired on the lines its diagnostics flag.
|
||||
- `engine_shortcuts(src, thorough)`: every exact classical route to simulating a program, with its cost.
|
||||
- `engine_diagnostic_codes()`: every diagnostic code the reader uses, with its meaning.
|
||||
- `engine_course(locale)`, and the course's lessons, checks and hints: the circuits course, checked by the engine.
|
||||
|
||||
The declarations (`wai_quantum.d.ts`) list every export.
|
||||
|
||||
A run in a page or in Node does not meter its energy, and its result says so.
|
||||
|
||||
## Elsewhere
|
||||
|
||||
The same engine runs as the command line (`wai-quantum`), as the Python package `wai-quantum`, and as the job service, and a job's receipt verifies in each. The source, its schemas and the versioning policy are at <https://git.transaction.science/Transaction-Science/open-standards> under `wai/`; Quantum Studio, an editor built on this package, is at <https://wai.transaction.science/quantum-studio/>.
|
||||
|
||||
## Licence
|
||||
|
||||
Apache-2.0.
|
||||
54
wai/wai-quantum-js/build.sh
Executable file
54
wai/wai-quantum-js/build.sh
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build the npm package @transaction-science/wai-quantum: the quantum job service's
|
||||
# engine for WebAssembly (wai/wai-web with its quantum_engine feature), its JavaScript
|
||||
# bindings, and this folder's package.json and README, packed as one tarball:
|
||||
#
|
||||
# bash wai/wai-quantum-js/build.sh OUT_DIR
|
||||
#
|
||||
# Writes OUT_DIR/package/ (the package's files) and OUT_DIR/<name>-<version>.tgz, and
|
||||
# prints the tarball's SHA-256. It needs Rust with the wasm32-unknown-unknown target,
|
||||
# wasm-bindgen 0.2.127 (the version wai-web's lock file pins) and npm. Paths are
|
||||
# remapped out of the binary, and the build is refused if a local path is left in the
|
||||
# files it ships.
|
||||
set -euo pipefail
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "usage: bash wai/wai-quantum-js/build.sh OUT_DIR" >&2
|
||||
exit 2
|
||||
fi
|
||||
here=$(cd "$(dirname "$0")" && pwd)
|
||||
root=$(cd "$here/../.." && pwd)
|
||||
web="$root/wai/wai-web"
|
||||
mkdir -p "$1"
|
||||
out=$(cd "$1" && pwd)
|
||||
sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1"; else shasum -a 256 "$1"; fi; }
|
||||
|
||||
want=$(sed -n '/^name = "wasm-bindgen"$/{n;s/^version = "\(.*\)"$/\1/p;}' "$web/Cargo.lock")
|
||||
have=$(wasm-bindgen --version | cut -d' ' -f2)
|
||||
[ "$have" = "$want" ] || { echo "build.sh: wasm-bindgen is $have; wai-web's lock file pins $want" >&2; exit 1; }
|
||||
|
||||
target=${CARGO_TARGET_DIR:-"$web/target"}
|
||||
sysroot=$(cd "$web" && rustc --print sysroot)
|
||||
commit=$(cd "$web" && rustc -vV | sed -n 's/^commit-hash: //p')
|
||||
export RUSTFLAGS="--remap-path-prefix=$root=/open-standards --remap-path-prefix=${CARGO_HOME:-$HOME/.cargo}=/cargo --remap-path-prefix=$sysroot/lib/rustlib/src/rust=/rustc/$commit"
|
||||
(cd "$web" && cargo build --locked --release --target wasm32-unknown-unknown --features quantum_engine)
|
||||
|
||||
pkg="$out/package"
|
||||
rm -rf "$pkg"
|
||||
mkdir -p "$pkg"
|
||||
wasm-bindgen --target web --out-dir "$pkg" --out-name wai_quantum "$target/wasm32-unknown-unknown/release/wai_web.wasm"
|
||||
cp "$here/package.json" "$here/README.md" "$pkg/"
|
||||
cp "$root/wai/LICENSE" "$pkg/LICENSE"
|
||||
|
||||
# Nothing the package ships names the machine that built it.
|
||||
for leak in "$HOME" /Users/ /home/ /Volumes/ /private/; do
|
||||
if grep -r -q -a -F "$leak" "$pkg"/wai_quantum*; then
|
||||
echo "build.sh: '$leak' is in the package's bindings or binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
rm -f "$out"/*.tgz
|
||||
npm pack "$pkg" --pack-destination "$out" --silent > /dev/null
|
||||
tgz=$(ls "$out"/*.tgz)
|
||||
echo "wasm: $(wc -c < "$pkg/wai_quantum_bg.wasm" | tr -d ' ') bytes"
|
||||
sha256 "$tgz"
|
||||
39
wai/wai-quantum-js/ci/check.sh
Executable file
39
wai/wai-quantum-js/ci/check.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env bash
|
||||
# The npm package, built and checked as an installer would meet it:
|
||||
#
|
||||
# bash wai/wai-quantum-js/ci/check.sh OUT_DIR
|
||||
#
|
||||
# 1. build.sh builds and packs it into OUT_DIR;
|
||||
# 2. the tarball, unpacked, holds the package's allow-list and nothing else;
|
||||
# 3. test/smoke.mjs runs against the unpacked package;
|
||||
# 4. the tarball is installed into an empty app (npm install <tgz>, nothing else to
|
||||
# fetch), and test/install.mjs, the README's Node example, runs there by name.
|
||||
# Leaves the tarball in OUT_DIR, with OUT_DIR/<tarball>.sha256.
|
||||
set -euo pipefail
|
||||
here=$(cd "$(dirname "$0")/.." && pwd)
|
||||
mkdir -p "$1"
|
||||
out=$(cd "$1" && pwd)
|
||||
bash "$here/build.sh" "$out"
|
||||
tgz=$(ls "$out"/*.tgz)
|
||||
work=$(mktemp -d)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
|
||||
mkdir "$work/unpacked"
|
||||
tar -xzf "$tgz" -C "$work/unpacked"
|
||||
have=$(cd "$work/unpacked/package" && find . -type f | sed 's|^\./||' | sort)
|
||||
want=$(printf '%s\n' LICENSE README.md package.json wai_quantum.d.ts wai_quantum.js wai_quantum_bg.wasm wai_quantum_bg.wasm.d.ts | sort)
|
||||
if [ "$have" != "$want" ]; then
|
||||
echo "check.sh: the tarball holds other files than the package's allow-list:" >&2
|
||||
diff <(echo "$want") <(echo "$have") >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
echo "the tarball: $(echo "$have" | wc -l | tr -d ' ') files, the allow-list exactly"
|
||||
node "$here/test/smoke.mjs" "$work/unpacked/package"
|
||||
|
||||
mkdir "$work/app"
|
||||
printf '{ "name": "app", "private": true, "type": "module" }\n' > "$work/app/package.json"
|
||||
(cd "$work/app" && npm install "$tgz" --no-audit --no-fund --silent)
|
||||
cp "$here/test/install.mjs" "$work/app/"
|
||||
(cd "$work/app" && node install.mjs)
|
||||
|
||||
(cd "$out" && n=$(basename "$tgz") && { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$n"; else shasum -a 256 "$n"; fi; } > "$n.sha256" && cat "$n.sha256")
|
||||
40
wai/wai-quantum-js/package.json
Normal file
40
wai/wai-quantum-js/package.json
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "@transaction-science/wai-quantum",
|
||||
"version": "0.1.0",
|
||||
"description": "The WAI quantum engine in WebAssembly: OpenQASM programs planned, run and signed in a page or in Node as the WAI quantum job service runs them, receipts checked, programs linted and drawn.",
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"main": "./wai_quantum.js",
|
||||
"module": "./wai_quantum.js",
|
||||
"types": "./wai_quantum.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./wai_quantum.d.ts",
|
||||
"default": "./wai_quantum.js"
|
||||
},
|
||||
"./wai_quantum_bg.wasm": "./wai_quantum_bg.wasm",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"wai_quantum.js",
|
||||
"wai_quantum.d.ts",
|
||||
"wai_quantum_bg.wasm",
|
||||
"wai_quantum_bg.wasm.d.ts",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"homepage": "https://wai.transaction.science/quantum-studio/",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.transaction.science/Transaction-Science/open-standards.git",
|
||||
"directory": "wai/wai-quantum-js"
|
||||
},
|
||||
"keywords": ["quantum", "openqasm", "webassembly", "simulator", "receipts"],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
20
wai/wai-quantum-js/test/install.mjs
Normal file
20
wai/wai-quantum-js/test/install.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// The README's Node example, run from an app that installed the tarball
|
||||
// (npm install <tgz>): the package found by its name, its WebAssembly by the
|
||||
// subpath it exports, a job run and its receipt verified.
|
||||
//
|
||||
// cd APP && node install.mjs
|
||||
import init, { QuantumEngine, engine_verify } from '@transaction-science/wai-quantum';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { createRequire } from 'node:module';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
const wasm = createRequire(import.meta.url).resolve('@transaction-science/wai-quantum/wai_quantum_bg.wasm');
|
||||
await init({ module_or_path: await readFile(wasm) });
|
||||
const engine = new QuantumEngine(randomBytes(32), 256);
|
||||
const job = JSON.parse(engine.run(JSON.stringify({ qasm: 'OPENQASM 3.0; qubit[1] q; bit[1] c; h q[0]; c = measure q;', shots: 100 })));
|
||||
const v = JSON.parse(engine_verify(JSON.stringify(job)));
|
||||
assert.equal(v.valid, true, JSON.stringify(v));
|
||||
assert.equal(v.signer, engine.signer_did());
|
||||
assert.equal(Object.values(job.result.counts).reduce((a, b) => a + b, 0), 100, JSON.stringify(job.result.counts));
|
||||
console.log('installed by name: a job run and its receipt verified', JSON.stringify(job.result.counts));
|
||||
56
wai/wai-quantum-js/test/smoke.mjs
Normal file
56
wai/wai-quantum-js/test/smoke.mjs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// The package as an installer leaves it: its bindings and its WebAssembly, loaded in
|
||||
// Node with nothing else installed.
|
||||
//
|
||||
// node wai/wai-quantum-js/test/smoke.mjs PACKAGE_DIR
|
||||
//
|
||||
// Prints one line per check and exits non-zero on the first that fails.
|
||||
import assert from 'node:assert/strict';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
|
||||
const dir = resolve(process.argv[2] ?? '.');
|
||||
const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'));
|
||||
const wq = await import(pathToFileURL(join(dir, pkg.main)).href);
|
||||
// In Node the module is given its WebAssembly's bytes; a page lets it fetch them.
|
||||
await wq.default({ module_or_path: await readFile(join(dir, 'wai_quantum_bg.wasm')) });
|
||||
console.log('node', process.versions.node, pkg.name, pkg.version);
|
||||
|
||||
const BELL = 'OPENQASM 3.0; qubit[2] q; bit[2] c; h q[0]; cx q[0], q[1]; c = measure q;';
|
||||
|
||||
// A job run with a fresh key, and its receipt checked.
|
||||
const engine = new wq.QuantumEngine(randomBytes(32), 256);
|
||||
const signer = engine.signer_did();
|
||||
assert.match(signer, /^did:key:z6Mk/, signer);
|
||||
const job = JSON.parse(engine.run(JSON.stringify({ qasm: BELL, shots: 1000, seed: 7 })));
|
||||
assert.equal(job.status, 'done', JSON.stringify(job).slice(0, 400));
|
||||
const counts = job.result.counts;
|
||||
assert.ok(Object.keys(counts).length > 0 && Object.keys(counts).every((k) => k === '00' || k === '11'), JSON.stringify(counts));
|
||||
const v = JSON.parse(wq.engine_verify(JSON.stringify(job)));
|
||||
assert.equal(v.valid, true, JSON.stringify(v));
|
||||
assert.equal(v.signer, signer, JSON.stringify(v));
|
||||
console.log(`run and verify: ${job.method}, ${Object.values(counts).reduce((a, b) => a + b, 0)} shots, signed by this engine's key`);
|
||||
|
||||
// A result changed after it was sealed does not verify.
|
||||
const forged = structuredClone(job);
|
||||
const k = Object.keys(forged.result.counts)[0];
|
||||
forged.result.counts[k] += 1;
|
||||
assert.notEqual(JSON.parse(wq.engine_verify(JSON.stringify(forged))).valid, true);
|
||||
console.log('a forged result does not verify');
|
||||
|
||||
// A program read and drawn.
|
||||
const lint = JSON.parse(wq.engine_lint(BELL));
|
||||
assert.equal(lint.ok, true, JSON.stringify(lint));
|
||||
assert.equal(lint.qubits, 2, JSON.stringify(lint));
|
||||
const drawn = JSON.parse(wq.engine_draw(BELL, 80));
|
||||
assert.ok(typeof drawn.text === 'string' && drawn.text.trim().length > 0, JSON.stringify(drawn));
|
||||
console.log(`lint and draw: ${lint.qubits} qubits, ${lint.gates} gates; drawn in ${drawn.text.trimEnd().split('\n').length} lines`);
|
||||
|
||||
// A program the engine refuses says why, with its diagnostic code.
|
||||
const WHILE = 'OPENQASM 3.0; qubit[1] q; bit[1] c; c[0] = measure q[0]; while (c[0]) { x q[0]; c[0] = measure q[0]; }';
|
||||
const refused = JSON.parse(engine.run(JSON.stringify({ qasm: WHILE, shots: 10 })));
|
||||
assert.ok(typeof refused.error === 'string' && /^Q\d{3}$/.test(refused.code ?? ''), JSON.stringify(refused));
|
||||
console.log(`a refused program says why: [${refused.code}] ${refused.meaning}`);
|
||||
|
||||
console.log('smoke: every check passed');
|
||||
Loading…
Reference in a new issue