//! `wai-quantum`: OpenQASM programs run on this machine as the quantum job //! service runs them, or sent to the service; every result names its method //! and its accuracy and carries a signed receipt. //! //! ```text //! wai-quantum run bell.qasm --shots 1000 # here, planned as the service plans it //! wai-quantum run prog.qasm -o "Z0 Z1" -a 00 --json //! wai-quantum plan prog.qasm # the method and why, without running //! wai-quantum lint prog.qasm # each problem with its line //! wai-quantum draw prog.qasm # the circuit as text //! wai-quantum draw prog.qasm --svg > c.svg # as an SVG image (or --latex, as TikZ; --describe, in words) //! wai-quantum debug prog.qasm --line 9 # the state before line 9: amplitudes, Bloch vectors, entanglement //! wai-quantum debug prog.qasm --branches # every branch at its measurements in the middle, with its weight //! wai-quantum dap # the debugger, for editors, over stdio //! wai-quantum critique prog.qasm # its measures and smells, each with its lines //! wai-quantum equivalent a.qasm b.qasm # the same circuit? and how the engine knows //! wai-quantum improve prog.qasm > better.qasm # its smells fixed, checked the same circuit //! wai-quantum shortcuts prog.qasm # each exact classical route and its cost here //! wai-quantum compile prog.qasm --epsilon 1e-6 # Clifford+T within ε, each pass checked, a signed certificate //! wai-quantum run prog.qasm --compile 1e-6 # compiled first, then run bit-exact; the result says how far //! wai-quantum estimate prog.qasm # what a fault-tolerant machine needs: qubits, time, factories, signed //! wai-quantum run ansatz.qasm --param theta=0.3 -o Z0 # a program's parameters (input float, input angle) bound //! wai-quantum gradient ansatz.qasm --param theta=0.3 --term "Z0 Z1" # the exact gradient, with its rounding bound //! wai-quantum optimize ansatz.qasm --param theta=0.3 --term Z0 # gradient descent, Adam or SPSA, every step sealed //! wai-quantum vqe --term "X0 X1" --term "Z0 Z1" --layers 2 # the ground energy, beside exact diagonalisation //! wai-quantum qaoa --graph ring.json --layers 2 # Max-Cut by QAOA, beside the maximum cut //! wai-quantum build qpe --counting 5 --phase 0.3 # a standard algorithm's program, checked against its reference //! wai-quantum run prog.qasm --intent ref.qasm # run, and the engine's verdict: how far it checked the intent //! wai-quantum run prog.qasm --intent dist.json # an exact distribution (or intents) the program must give //! wai-quantum repair prog.qasm > fixed.qasm # the mechanical fixes, on the lines the diagnostics flag alone //! wai-quantum run ghz.qasm --input n=40 # a generator, read at n = 40 //! wai-quantum instance qft.qasm --input n=5 # a generator written out as plain OpenQASM //! wai-quantum convert prog.qasm --to qir # QIR 2.0 (or jeff, or OpenQASM 3), the conversion checked //! wai-quantum run prog.ll --shots 1000 # every verb reads QIR (.ll) and jeff (.jeff); --from names one //! wai-quantum discover new ghz.json # the workbench: an archive for a specification //! wai-quantum discover search ghz.json # cheaper generators, each checked at every size //! wai-quantum history blame prog.qasm # whose hand each line and gate is in //! wai-quantum locks check prog.qasm theirs.qasm # the rules each proposed change breaks //! wai-quantum novelty prog.qasm # its nearest known construction, and what it rediscovers //! wai-quantum run prog.qasm --history prog.history.json # the receipt binds who wrote it //! wai-quantum submit prog.qasm # on the job service, its status followed to the end //! wai-quantum submit a.qasm --program b.qasm # a set: one result per program, one receipt binding them //! wai-quantum submit p.qasm --sweep theta=0.1,0.2,0.3 # a sweep of an input, every point run as one set //! wai-quantum submit prog.qasm --dry-run # the service's plan: method, memory, time, energy, bounds //! wai-quantum task start job.json # the service as a scheduler's resource (`task --help`) //! wai-quantum verify result.json # a result's receipt, or a lab receipt //! wai-quantum key # the key this user signs with //! wai-quantum version --json # this binary's version, the engine's and each schema's //! wai-quantum methods # each method and its reach here //! wai-quantum diagnostics # the reader's diagnostic codes, each with its outcome //! wai-quantum mcp # these tools for an agent, over stdio //! wai-quantum mcp config # its entry for an agent client's configuration //! wai-quantum new my-circuit --template bell # a project: program, intent, test, CI, MCP entry, AGENTS.md //! wai-quantum test my-circuit # every intent checked by the engine, the receipts saved //! wai-quantum setup-agent # the wai-quantum block in AGENTS.md, written or refreshed //! wai-quantum docs llms # llms.txt, from the live tool lists //! wai-quantum learn # the circuits course, each exercise checked by the engine //! wai-quantum learn check bell bell.qasm # a program checked against a lesson's exercise //! wai-quantum learn --lang zh-CN # in Simplified Chinese (or as LANG says) //! wai-quantum fmt prog.qasm # the program in the one layout editors format to //! wai-quantum lsp # the language service, for editors, over stdio //! wai-quantum eval run --tasks tasks.json --model http://127.0.0.1:8080/v1 # a model scored by the engine on a task set //! wai-quantum lab … # the research tools //! wai-quantum providers … # your own quantum providers; run --provider NAME runs on one //! wai-quantum targets list | import | show # device targets; run --target NAME runs as the device would //! wai-quantum run rabi.qasm --method pulse # a pulse program (OpenPulse) integrated, its error bounded, the same bits everywhere //! wai-quantum pulse schedule rabi.qasm # its frames' timelines, plays, waveforms and gate calls //! wai-quantum pulse check job.json # a pulse job's receipt, its program, and the run again, bit for bit //! wai-quantum completions zsh # shell completion //! ``` //! //! `run`, `plan` and `mcp` use the job service's own engine //! ([`wai_quantum::engine`]), so a job gives here the result it gives there. //! `run --json` prints the job as the service returns it: the request, the //! plan, and the result with its receipt, which `verify` checks. Every //! `--json` output follows a committed schema (`wai_quantum::engine::schema`, //! and `schemas/` here for the MCP server's own tools); what each version //! promises is the policy in `wai/wai-quantum/VERSIONING.md`. //! //! A generator is a program with integer `input`s, `const`s and `for` loops //! ([`wai_quantum::qasm::parse_with`]): one program for a whole family of //! circuits. Every command reads one circuit of the family, at the values //! given with `--input`, and `instance` prints that circuit. // The MCP server's tool list is one `json!` value, deeper than the default limit. #![recursion_limit = "256"] mod compile; mod converge; mod dap; mod debug; mod estimate; mod eval; mod discover; mod guide; mod fmt; mod history; mod lab; mod learn; mod locale; mod locks; mod lsp; mod mcp; mod project; #[cfg(feature = "providers")] mod providers; mod pulse; mod qec; mod repair; mod rewrite; mod study; mod targets; mod taste; mod variational; #[cfg(feature = "service")] mod service; #[cfg(feature = "service")] mod task; use clap::{Args, CommandFactory, FromArgMatches, Parser, Subcommand}; use serde_json::{Value, json}; use std::path::{Path, PathBuf}; use wai_quantum::engine::{catalog, critique, draw, equiv, job, shortcuts}; use wai_quantum_cli::keys; #[derive(Parser)] #[command( name = "wai-quantum", version, about = "OpenQASM programs run here as the quantum job service runs them, or sent to it; every result names its method and accuracy and is signed", propagate_version = true )] struct Cli { /// A value for a generator's integer `input`, as n=5 (repeat for each /// input): the program is read as its family's circuit at those values #[arg(long = "input", value_name = "NAME=VALUE", global = true, value_parser = input_arg)] inputs: Vec<(String, i64)>, /// The language of the words the command line writes itself, en or zh-CN (by default the one LANG names); results, codes and the engine's own messages stay in English #[arg(long, global = true, value_name = "LOCALE")] lang: Option, /// The format of every program the command reads: qasm, qir (LLVM text) or jeff; by default the file's extension says (.ll or .qir is QIR, .jeff is jeff), and OpenQASM otherwise #[arg(long, global = true, value_name = "FORMAT", value_parser = format_arg)] from: Option, #[command(subcommand)] command: Command, } #[derive(Subcommand)] enum Command { /// Run a program here, planned as the job service plans it, and sign the result Run { #[command(flatten)] job: Job, #[command(flatten)] out: Output, #[command(flatten)] key: KeyArgs, #[cfg(feature = "providers")] #[command(flatten)] provider: providers::RunArgs, }, /// Show the method a program would run on and why, without running it Plan { #[command(flatten)] job: Job, /// Print the plan as JSON #[arg(long)] json: bool, }, /// Write a generator out at its `--input` values, as plain OpenQASM Instance { /// The program; `-` or nothing reads standard input file: Option, }, /// Carry a program between formats (OpenQASM 3, QIR as LLVM text, jeff), every conversion checked: what is written is read back and held to the program by the equivalence ladder; notes and the check go to standard error (exit 1 refused, 2 the check found it different) Convert { /// The program; `-` or nothing reads standard input; its format is --from, else its extension, else what its bytes look like file: Option, /// The format to write: qasm, qir or jeff #[arg(long, value_name = "FORMAT", value_parser = format_arg)] to: wai_quantum::engine::formats::Format, /// QIR's profile, base or adaptive: by default the base profile when the program's measurements all come at the end, else the adaptive one #[arg(long)] profile: Option, /// Write the program to this file (jeff is binary: give a file, or --json for its base64) #[arg(short = 'o', long, value_name = "FILE")] output: Option, /// Print the conversion as JSON: the program (base64 for jeff), the program as OpenQASM, the notes, what was read and written, and the check #[arg(long)] json: bool, /// Write it without the check #[arg(long)] no_check: bool, }, /// Check a program and print each problem with its line Lint { /// The program; `-` or nothing reads standard input file: Option, /// Print the outcome as JSON #[arg(long)] json: bool, }, /// Draw a program's circuit: as text, or as an SVG image, LaTeX (plain TikZ) or words Draw { /// The program; `-` or nothing reads standard input file: Option, /// Wrap at this many columns #[arg(long, default_value_t = 100)] width: usize, /// The drawing as a standalone SVG image, the Studio's picture #[arg(long, conflicts_with_all = ["latex", "describe"])] svg: bool, /// The drawing as LaTeX: one picture in plain TikZ, which needs only \usepackage{tikz} #[arg(long, conflicts_with = "describe")] latex: bool, /// The circuit in words, column by column, as a screen reader reads it #[arg(long)] describe: bool, }, /// Step through a program on the engine's exact methods and look into the state at a position or before a line, or list every branch at its measurements in the middle with its weight Debug(debug::Args), /// Serve the debugger to editors over standard input and output (the debug adapter protocol): launch a program, breakpoints by line, step forward and back, and the state's probes and branch tree as variables Dap, /// A program's measures (gates, two-qubit gates, depth, T count, a modelled fidelity) and its smells, each with its lines Critique { /// The program; `-` or nothing reads standard input file: Option, /// Print the critique as JSON #[arg(long)] json: bool, }, /// Whether two programs are the same circuit, up to a global phase, and how the engine knows (exit 0 the same, 1 different, 2 unchecked) Equivalent { /// The first program a: PathBuf, /// The second program b: PathBuf, /// Print the verdict as JSON #[arg(long)] json: bool, }, /// Print the program with its smells fixed, checked the same circuit; what changed goes to standard error Improve { /// The program; `-` or nothing reads standard input file: Option, /// Print every candidate (all the fixes, and each alone) with its measures and verdict, as JSON #[arg(long)] json: bool, }, /// Each exact classical route to simulating a program, with its cost on it, and the cheapest for each question Shortcuts { /// The program; `-` or nothing reads standard input file: Option, /// Also search a contraction tree for one amplitude, and search longer for a cut-rank order (seconds to minutes on a large circuit) #[arg(long)] thorough: bool, /// Print the routes as JSON #[arg(long)] json: bool, }, /// Compile a program: passes each checked where they act, Clifford+T within an ε budget (or the standard gates), and a signed certificate of every pass and the distance (`wai-quantum compile --help`) Compile(compile::Args), /// What a fault-tolerant machine would need to run a program: its logical counts (T, Toffolis, rotations at their synthesised T counts, depth, qubits with ancillas), physical qubits, runtime and factories, and the frontier between qubits and time, each estimate signed (`wai-quantum estimate --help`) Estimate(estimate::Args), /// The expectation of a Hamiltonian at a program's parameters and its exact gradient by each (adjoint differentiation or the parameter-shift rule, in f64 with a stated rounding bound), signed Gradient(variational::Gradient), /// Minimise a Hamiltonian's expectation over a program's parameters: gradient descent, Adam or SPSA from a seed, every step in a digest chain the receipt seals; with --checkpoint it stops and goes on to the same bytes (exit 2 when --slice stops it) Optimize(variational::Optimize), /// The variational eigensolver: an ansatz optimised on a Hamiltonian, the energy found set beside the exact ground energy by diagonalisation Vqe(variational::Vqe), /// Max-Cut on a graph by QAOA: the program and its cost built from the graph, optimised, and the cut set beside the maximum cut by brute force Qaoa(variational::Qaoa), /// A standard algorithm's program (qpe, amplitude_estimation, adder, qsvt) on standard output, checked by the equivalence ladder against a reference built from its definition Build(variational::Build), /// Repair a program from its diagnostics, on the lines they flag alone: the mechanical fixes, a proposed fix checked against those lines, and what needs a person (exit 0 it reads, 1 something needs a person, 2 a proposal rejected) Repair(repair::Args), /// Send a program, a set or a sweep to the job service, follow its status to the end, and print its result #[cfg(feature = "service")] Submit { #[command(flatten)] job: Job, #[command(flatten)] out: Output, /// The service's address #[arg(long, env = "WAI_QUANTUM_SERVICE", default_value = "https://wai.transaction.science/quantum-jobs")] service: String, /// Ask the service for its plan, running nothing: the method, memory, estimated time and energy with their basis, the bounds, and whether it would take the job now (exit 1 if it would not) #[arg(long)] dry_run: bool, /// Submit and print the job with its id, without following it (`wai-quantum task status ID` reads it later) #[arg(long, conflicts_with = "dry_run")] detach: bool, /// A private deployment's API key (sent as a bearer token); the public service takes none #[arg(long, env = "WAI_QUANTUM_API_KEY", hide_env_values = true)] api_key: Option, }, /// The job service as a resource an on-premises scheduler hands its batch jobs: describe it, acquire and release a lease, start, follow, read and stop tasks (`wai-quantum task --help`) #[cfg(feature = "service")] Task { #[command(subcommand)] cmd: task::Cmd, #[command(flatten)] args: task::Args, }, /// Check a receipt: a result from `run --json`, `submit --json` or the service, or a `lab` receipt Verify { /// The JSON; `-` or nothing reads standard input file: Option, /// Also check that the job carries this history's account of its program (`run --history`) #[arg(long, value_name = "FILE")] history: Option, }, /// Show the key this user signs receipts with Key { #[command(flatten)] key: KeyArgs, }, /// This binary's version and the engine's, the schema of each document it writes, the protocols it speaks, and where the policy is that says what each version promises Version { /// Print them as JSON #[arg(long)] json: bool, }, /// Each method, what it takes, and how far it reaches in a job's memory here Methods { /// Print the table as JSON #[arg(long)] json: bool, /// The memory a job may take, GiB #[arg(long, default_value_t = DEFAULT_MEMORY_GB)] memory_gb: u64, }, /// The members of the fabric a statevector job can be placed on here (`run --fabric`): the CPU and each accelerator this build reaches that is found and certified bit-exact against the CPU, each with the most qubits it holds; the devices found and not placed, and why Fabric { /// Print the list as JSON #[arg(long)] json: bool, /// The memory a job may take, GiB #[arg(long, default_value_t = DEFAULT_MEMORY_GB)] memory_gb: u64, }, /// Every component the engine plans, runs, compiles and converts through (the simulation methods, compiler passes, noise channels and program formats), each with its version and the digest of its descriptor: the identity a result is sealed with Components { /// Print the list as JSON, each component with its descriptor #[arg(long)] json: bool, }, /// The diagnostic codes: the OpenQASM reader's (each with its outcome: refused, lowered, carried, preserved or warned) and the job layer's (refused or retry), each with its meaning Diagnostics { /// Print the table as JSON #[arg(long)] json: bool, }, /// Serve run, plan, lint, critique, the equivalence check, improve, the classical shortcuts, compile, draw, the debugger and verify to an agent over standard input and output (MCP), with resources and prompts; `mcp config` and `mcp install` register it with a client Mcp { #[command(subcommand)] cmd: Option, /// The memory a job may take, GiB #[arg(long, default_value_t = DEFAULT_MEMORY_GB)] memory_gb: u64, #[command(flatten)] key: KeyArgs, }, /// Format programs in one layout: a statement to a line, blocks indented two spaces, a space around each operator, comments and blank lines kept; a program whose brackets do not match is left as it is, with its line Fmt { /// The programs; `-` or nothing reads standard input and prints the formatted program files: Vec, /// Change nothing: name each program that is not formatted, and exit 1 if any is not #[arg(long, conflicts_with = "write")] check: bool, /// Write each file back formatted #[arg(long)] write: bool, }, /// Serve the language to editors over standard input and output (the language server protocol): the engine's diagnostics, completion, hover with each gate's matrix and cost, definitions, references, renames, symbols, folding and formatting, and Run, Plan, Critique, Shortcuts and Receipt run here Lsp { /// The memory a job may take, GiB #[arg(long, default_value_t = DEFAULT_MEMORY_GB)] memory_gb: u64, #[command(flatten)] key: KeyArgs, /// Accepted for the editors that pass it: standard input and output is the server's only transport #[arg(long)] stdio: bool, }, /// The workbench: a specification, its archive, and searches for cheaper generators (`wai-quantum discover --help`) Discover { #[command(subcommand)] cmd: discover::Cmd, }, /// A program's history, attributed: its versions, who made each change and how, and whose hand each line and gate is in (`wai-quantum history --help`) History { #[command(subcommand)] cmd: history::Cmd, }, /// What is new in a program: its structural distance to the known constructions (and to programs given), each known construction the engine proves it to be, and the strengths a cost did not ask for Novelty { /// The program; `-` or nothing reads standard input file: Option, /// More of the inspiring set: programs to compare with (repeatable) #[arg(long = "against", value_name = "FILE")] against: Vec, /// The program it is meant to improve on: the measures it beats it on that --weigh does not name #[arg(long, value_name = "FILE")] reference: Option, /// The measures the cost weighs, comma-separated (as depth,two_qubit) #[arg(long, value_delimiter = ',')] weigh: Vec, #[arg(long)] json: bool, }, /// The person's rules for what a proposal may change: held blocks, gates, qubits, what to avoid, a region (`wai-quantum locks --help`) Locks { #[command(subcommand)] cmd: locks::Cmd, }, /// Measuring whether the co-design works: a protocol sealed before any data, conditions by key, records, and the cohort report (`wai-quantum study --help`) Study { #[command(subcommand)] cmd: study::Cmd, }, /// Whether your designs converge on a model's proposals, and how diverse a set of programs is (`wai-quantum converge --help`) Converge { #[command(subcommand)] cmd: converge::Cmd, }, /// Rewrites that keep a program's meaning, each checked: the ones it admits, one made, all to a fixed point, or how one program becomes another (`wai-quantum rewrite --help`) Rewrite { #[command(subcommand)] cmd: rewrite::Cmd, }, /// Your taste in circuits, learned from your own choices between programs the engine proves equal (`wai-quantum taste --help`) Taste { #[command(subcommand)] cmd: taste::Cmd, }, /// Error correction: a code and its certified distance, a syndrome or a circuit's detectors decoded, a threshold measured, each answer signed (`wai-quantum qec --help`) Qec { #[command(subcommand)] cmd: qec::Cmd, }, /// Pulse programs: a program's schedule on frames drawn as a timeline, and a pulse job checked by running it again; a pulse program runs with `run --method pulse` (`wai-quantum pulse --help`) Pulse { #[command(subcommand)] cmd: pulse::Cmd, }, /// Score a model on a task set with the engine: whether its program reads, the verdict's level and whether the intent holds, and the joules per solved task where its server's energy is metered; or write each task's answer three ways for a token count (`wai-quantum eval --help`) Eval { #[command(subcommand)] cmd: eval::Cmd, }, /// The research tools: error correction, DMRG, neural states, resource estimates and more (`wai-quantum lab help`) Lab { #[arg(trailing_var_arg = true, allow_hyphen_values = true)] args: Vec, }, /// Make a project: a starter program, what it must do (intent.json), a test that checks it with the engine and saves the receipts, CI, an MCP entry and AGENTS.md New(project::New), /// Check every intent in a project's intent.json with the engine, run the program, and save the signed receipts (exit 0 every intent holds, 1 one fails, 2 one is unchecked) Test(project::Test), /// Write or refresh the wai-quantum block in a project's AGENTS.md (between its markers, nothing outside them), for coding agents SetupAgent(guide::SetupAgent), /// What agents are told, offline: the language, the diagnostics, the methods, the examples, the AGENTS.md block, llms.txt and the Agent Skill Docs(guide::Docs), /// The circuits course: lessons from a first circuit to resource estimates, each exercise checked by the engine, your progress kept only on this machine (`wai-quantum learn --help`) Learn(learn::Args), /// Device targets (a device's qubits, pairs, gates, durations and errors from its calibration snapshot): import, list, show; `run --target NAME` runs as the device would (`wai-quantum targets --help`) Targets { #[command(subcommand)] cmd: targets::Cmd, }, /// Your own quantum providers (devices, accounts, simulators): add, list, remove, describe and test them, and what is free (`wai-quantum providers --help`) #[cfg(feature = "providers")] Providers { #[command(subcommand)] cmd: providers::Cmd, }, /// Print shell completion for bash, zsh, fish, elvish or PowerShell Completions { shell: clap_complete::Shell }, } /// The memory a job may take by default, GiB. const DEFAULT_MEMORY_GB: u64 = 16; /// A job: a program and what is asked of it, as the service reads it. #[derive(Args, Clone)] struct Job { /// The OpenQASM 3 (or 2) program; `-` or nothing reads standard input file: Option, /// Shots to sample (1024 when nothing else is asked) #[arg(long)] shots: Option, /// The seed shots are sampled with (1 if not given) #[arg(long)] seed: Option, /// A method by name, or auto (the default) for the strongest the program admits; `wai-quantum methods` lists them #[arg(long)] method: Option, /// A Pauli observable to take the expectation of, as "Z0 Z1" or "0.5 X2" (repeatable) #[arg(short = 'o', long = "observable")] observables: Vec, /// A basis state's amplitude, as a bit string with the highest qubit first (repeatable) #[arg(short = 'a', long = "amplitude")] amplitudes: Vec, /// A noise channel and its probability, as depolarizing_1q=0.01 (repeatable) #[arg(long, value_parser = noise_arg)] noise: Vec<(String, f64)>, /// A method option, as max_bond=128 (repeatable) #[arg(long = "option", value_parser = option_arg)] options: Vec<(String, Value)>, /// The memory a job may take here, GiB #[arg(long, default_value_t = DEFAULT_MEMORY_GB)] memory_gb: u64, /// The program's history (`wai-quantum history`): the request carries its account of the program (the version's digest and whose hand its gates and lines are in), and the receipt binds it #[arg(long, value_name = "FILE")] history: Option, /// Compile the program to Clifford+T within this ε first (`wai-quantum compile`): it then runs bit-exact, and the result states the distance #[arg(long, value_name = "EPSILON")] compile: Option, /// Run as the program would run on a device target (`wai-quantum targets list`, or a snapshot's file): compiled to its gates, placed where it is most likely to succeed, and simulated under its noise, its hash in the receipt #[arg(long, value_name = "NAME|FILE", conflicts_with = "noise")] target: Option, /// What the program must do, checked by the engine with the run (repeatable): a reference program (.qasm, the same circuit), or JSON: a distribution of the measured bits ({"00": 0.5, "11": 0.5}), an intent ({"prepares": "ref.qasm"}), a list of them, or a project's intent.json. The result carries the verdict, sealed by its receipt #[arg(long, value_name = "FILE")] intent: Vec, /// Ask for the verdict with no intent: how far the engine checked (parsed, executed), sealed by the receipt #[arg(long)] verdict: bool, /// Repair the program first, on the lines its diagnostics flag alone (`wai-quantum repair`): the result says what was changed, and the verdict lists each repair #[arg(long)] repair: bool, /// With --repair: the bound for an unbounded `while` on measured bits #[arg(long, value_name = "N", requires = "repair")] max_iterations: Option, /// Another program for the same set (repeatable): the job runs as a set, one result and receipt per program and one receipt binding them all, the other options shared #[arg(long = "program", value_name = "FILE", conflicts_with_all = ["sweep", "history"])] programs: Vec, /// An input of the program and the values to run it at, as theta=0.1,0.2 or n=2,3 (repeatable): every combination runs as one set; an integer input takes whole numbers, a float or angle input any number #[arg(long, value_name = "NAME=V1,V2,…", value_parser = sweep_arg, conflicts_with = "history")] sweep: Vec<(String, Vec)>, /// Mitigate the expectation values (or, for readout, the counts) under the job's noise: zne, pea, pec, cdr, vd or readout, or its options as JSON ({"method": "pea", "factors": [1, 1.5, 2]}) #[arg(long, value_name = "METHOD|JSON")] mitigate: Option, /// A pulse run's options (with --method pulse), as JSON or a file of it: dt_ns, levels, tolerance, substeps, qubits, couplings and ports (`wai-quantum pulse --help`) #[arg(long, value_name = "JSON|FILE", value_parser = pulse::options_arg)] pulse: Option, /// A parameter's value, as theta=0.3 (repeatable): each `input float` and `input angle` of the program, bound exactly (the job's "params"); in a sweep, the parameters it fixes #[arg(long = "param", value_name = "NAME=VALUE", value_parser = variational::param_arg)] params: Vec<(String, f64)>, /// Where the state vector runs (the statevector method): auto (the default: on a member only where the committed measurements show it faster than this host's CPU for the job's size and fusion, otherwise the CPU; `wai-quantum fabric` states the rule and the numbers), cpu, accelerator (the first certified accelerator member that holds the state), or a member's id or key (`wai-quantum fabric` lists them); for a pauli job, accelerator or a member's id or key runs its sum of terms on that member, with the CPU's answers; repeated, the state is split by its top qubits into one partition on each member named, a power of two of them. A member certified bit-exact gives the CPU's answers; a placement this host cannot honour is refused (J013) #[arg(long = "fabric", value_name = "PLACEMENT")] fabric: Vec, } /// `name=v1,v2,…`: each value a whole number if it reads as one, else a number. fn sweep_arg(s: &str) -> Result<(String, Vec), String> { let (k, v) = s.split_once('=').ok_or("a sweep is NAME=V1,V2,…")?; let values = v .split(',') .map(|x| { let x = x.trim(); x.parse::().map(|i| json!(i)).or_else(|_| x.parse::().ok().filter(|f| f.is_finite()).map(|f| json!(f)).ok_or(format!("{x}: not a number"))) }) .collect::, _>>()?; if k.is_empty() || values.is_empty() { return Err("a sweep is NAME=V1,V2,…".into()); } Ok((k.to_string(), values)) } #[derive(Args, Clone)] struct Output { /// Print the job as the service returns it: request, plan, result and receipt #[arg(long)] json: bool, /// The most outcomes to list #[arg(long, default_value_t = 16)] top: usize, } #[derive(Args, Clone)] struct KeyArgs { /// Sign with the key in this file (64 hex digits) #[arg(long, value_name = "FILE")] key: Option, /// Sign with the published demo key: the receipt then shows a result is unaltered, not who signed it #[arg(long, conflicts_with = "key")] demo_key: bool, } fn noise_arg(s: &str) -> Result<(String, f64), String> { let (k, v) = s.split_once('=').ok_or("give a channel and its probability, as depolarizing_1q=0.01")?; if !catalog::NOISE_CHANNELS.contains(&k) { return Err(format!("{k}: no such channel; the channels are {}", catalog::NOISE_CHANNELS.join(", "))); } let p: f64 = v.parse().map_err(|_| format!("{v}: a probability, 0 to 1"))?; Ok((k.to_string(), p)) } fn input_arg(s: &str) -> Result<(String, i64), String> { let (k, v) = s.split_once('=').ok_or("give an input and its value, as n=5")?; let v: i64 = v.trim().parse().map_err(|_| format!("{v}: an input takes a whole number"))?; Ok((k.trim().to_string(), v)) } /// The values given with `--input`, for every program the command reads. static INPUTS: std::sync::OnceLock = std::sync::OnceLock::new(); fn option_arg(s: &str) -> Result<(String, Value), String> { let (k, v) = s.split_once('=').ok_or("give an option and its value, as max_bond=128")?; // A number or true/false reads as JSON; anything else is a string. let value = serde_json::from_str::(v).ok().filter(|x| x.is_number() || x.is_boolean()).unwrap_or_else(|| json!(v)); Ok((k.to_string(), value)) } /// The components list, one line each: its kind, name, version, where it /// comes from and its digest. fn components_text(c: &Value) -> String { let mut s = format!("engine {} {}\n", c["engine"]["name"].as_str().unwrap_or(""), c["engine"]["version"].as_str().unwrap_or("")); for (kind, title) in [("methods", "methods"), ("passes", "passes"), ("channels", "noise channels"), ("front_ends", "formats")] { s.push_str(&format!("\n{title}\n")); for x in c[kind].as_array().into_iter().flatten() { s.push_str(&format!(" {:<20} {:<10} {:<10} {}\n", x["name"].as_str().unwrap_or(""), x["version"].as_str().unwrap_or(""), x["origin"].as_str().unwrap_or(""), x["digest"].as_str().unwrap_or(""))); } } s } /// The fabric's members as `wai-quantum fabric` prints them. fn fabric_text(f: &Value) -> String { let s = |v: &Value| v.as_str().unwrap_or("").to_string(); let mut o = format!("kernel {}\nauto {}\n", s(&f["kernel"]), s(&f["auto"])); let rule = &f["auto_rule"]; let places = rule["places"].as_array().cloned().unwrap_or_default(); for p in &places { o.push_str(&format!("auto places {} from {} to {} qubits at fusion {}, for a plan of at least {} launches\n", s(&p["key"]), p["from_qubits"], p["to_qubits"], p["fusion"], p["min_launches"])); } if places.is_empty() { let why = if rule["measured_here"].as_bool() == Some(true) { "no member found here is measured faster than this host's CPU" } else { "this host is not among the measured ones" }; o.push_str(&format!("auto places nothing here: {why}; every state on the CPU\n")); } for h in rule["hosts"].as_array().into_iter().flatten() { let rows = h["rows"].as_array().cloned().unwrap_or_default(); o.push_str(&format!("measured {}, {} ({}), {} rows, {} faster; discovery {} ms once per process\n", s(&h["host"]), s(&h["measured"]), s(&h["about"]), rows.len(), rows.iter().filter(|r| r["faster"] == true).count(), h["discovery_ms"])); } for m in f["members"].as_array().into_iter().flatten() { let corpus = m["corpus"].as_str().map(|c| format!(", corpus {}… over {} cases", &c[..16.min(c.len())], m["cases"])).unwrap_or_default(); o.push_str(&format!("member {:<18} {:<4} up to {} qubits, {} {}{corpus}\n", s(&m["key"]), s(&m["class"]), m["detail"]["max_qubits"], s(&m["implementation"]), s(&m["parity"]["tier"]))); o.push_str(&format!(" {}\n", s(&m["member"]))); } for x in f["refused"].as_array().into_iter().flatten() { o.push_str(&format!("not placed {} {}: {}\n", s(&x["backend"]), if x["key"].as_str().is_some_and(|k| !k.is_empty()) { s(&x["key"]) } else { s(&x["member"]) }, s(&x["why"]))); } for x in f["unavailable"].as_array().into_iter().flatten() { o.push_str(&format!("none found {}: {}\n", s(&x["backend"]), s(&x["why"]))); } if f["backends"].as_array().is_none_or(Vec::is_empty) { o.push_str("backends none in this build: the CPU alone (build with --features cuda, hip or opencl)\n"); } o } fn die(msg: &str) -> ! { eprintln!("wai-quantum: {msg}"); std::process::exit(1) } /// The program in `file`, or on standard input for `-` or none. A generator /// (a program with integer `input`s) is written out at the `--input` values, /// so every command reads one circuit of its family. fn read_input(file: Option<&Path>) -> Result { at_inputs(read_program(file)?, &INPUTS.get().cloned().unwrap_or_default()) } /// The format `--from` gives, for every program read. static FROM: std::sync::OnceLock> = std::sync::OnceLock::new(); fn format_arg(s: &str) -> Result { wai_quantum::engine::formats::Format::parse(s).ok_or_else(|| format!("{s}: the formats are qasm, qir and jeff")) } /// The bytes of `file`, or standard input for `-` or none. fn read_bytes(file: Option<&Path>) -> Result, String> { match file { Some(p) if p != Path::new("-") => std::fs::read(p).map_err(|e| format!("{}: {e}", p.display())), _ => { use std::io::Read as _; let mut b = Vec::new(); std::io::stdin().read_to_end(&mut b).map_err(|e| format!("standard input: {e}"))?; Ok(b) } } } /// The format a program is in: `--from`, else its file's extension (`.ll` /// and `.qir` are QIR, `.jeff` is jeff), else OpenQASM. fn format_of(file: Option<&Path>) -> wai_quantum::engine::formats::Format { use wai_quantum::engine::formats::Format; if let Some(Some(f)) = FROM.get() { return *f; } file.and_then(|p| Format::from_extension(&p.to_string_lossy())).unwrap_or(Format::Qasm) } /// The program in `file` as OpenQASM: one in another format is read to it, /// its notes on standard error. fn read_program(file: Option<&Path>) -> Result { use wai_quantum::engine::formats::{self, Format}; let f = format_of(file); if f == Format::Qasm { return read_text(file); } let r = formats::read(f, &read_bytes(file)?).map_err(|d| d.to_string())?; for n in &r.notes { eprintln!("wai-quantum: {}: {n}", f.name()); } Ok(r.qasm) } /// `text` as one circuit: a generator written out at `inputs`, any other /// program as it is (and refused if it was given inputs it does not take). fn at_inputs(text: String, inputs: &wai_quantum::qasm::Inputs) -> Result { if inputs.is_empty() && wai_quantum::qasm::inputs(&text).map(|v| v.is_empty()).unwrap_or(true) { return Ok(text); } wai_quantum::qasm::instantiate(&text, inputs) } /// The text of `file`, or standard input for `-` or none. fn read_text(file: Option<&Path>) -> Result { match file { Some(p) if p != Path::new("-") => std::fs::read_to_string(p).map_err(|e| format!("{}: {e}", p.display())), _ => { use std::io::Read as _; let mut s = String::new(); std::io::stdin().read_to_string(&mut s).map_err(|e| format!("standard input: {e}"))?; Ok(s) } } } /// The job's program as one circuit, and its request: with `--history`, the /// request carries the history's account of the version the file holds; /// with `--intent`, what the program must do. fn job_request(job: &Job) -> Result { let text = read_program(job.file.as_deref())?; let inputs = INPUTS.get().cloned().unwrap_or_default(); // A sweep sends the program with its inputs, which the sweep gives values. if !job.sweep.is_empty() && !inputs.is_empty() { return Err("give an input a fixed value as a sweep of one value (--sweep n=5), not with --input".into()); } let program = if job.sweep.is_empty() { at_inputs(text.clone(), &inputs)? } else { text.clone() }; let mut req = request(job, program); if !job.programs.is_empty() { let mut all = vec![req["qasm"].take()]; for p in &job.programs { all.push(json!(at_inputs(read_program(Some(p))?, &inputs)?)); } req.as_object_mut().map(|o| o.remove("qasm")); req["programs"] = Value::Array(all); } if !job.sweep.is_empty() { req["sweep"] = Value::Object(job.sweep.iter().cloned().map(|(k, v)| (k, Value::Array(v))).collect()); } if let Some(t) = &job.target { req["target"] = targets::request_value(t)?; } if let Some(p) = &job.history { req["history"] = history::attest(&history::load(p)?, &text, &inputs)?; } if !job.intent.is_empty() { let mut all = Vec::new(); for f in &job.intent { all.extend(intent_file(f)?); } req["intent"] = Value::Array(all); } Ok(req) } /// The intents a file gives, each reference read in as text and named by its /// file: a program (`.qasm`, the same circuit), or JSON (a distribution of /// the measured bits, an intent, a list of them, or a project's /// `intent.json`, whose references are read beside it). fn intent_file(path: &Path) -> Result, String> { let text = read_text(Some(path))?; let name = path.display().to_string(); if path.extension().is_some_and(|e| e == "qasm") { return Ok(vec![json!({ "equivalent": at_inputs(text, &INPUTS.get().cloned().unwrap_or_default())?, "name": name })]); } let v: Value = serde_json::from_str(&text).map_err(|e| format!("{name}: an intent file is OpenQASM (.qasm) or JSON: {e}"))?; let dir = path.parent().unwrap_or(Path::new(".")); let list = match &v { Value::Array(a) => a.clone(), Value::Object(o) if o.contains_key("intent") => o["intent"].as_array().cloned().ok_or_else(|| format!("{name}: \"intent\" is a list"))?, Value::Object(o) if !o.is_empty() && o.keys().all(|k| !k.is_empty() && k.bytes().all(|b| b == b'0' || b == b'1')) => vec![json!({ "distribution": v })], _ => vec![v.clone()], }; list.into_iter() .map(|mut i| { for k in ["equivalent", "prepares"] { if let Some(f) = i.get(k).and_then(Value::as_str).filter(|f| !f.contains(';')).map(str::to_string) { let p = dir.join(&f); i[k] = json!(at_inputs(read_text(Some(&p))?, &INPUTS.get().cloned().unwrap_or_default())?); i["name"] = json!(f); } } Ok(i) }) .collect() } /// The job's request, with only the fields given, as the service receives it. fn request(job: &Job, qasm: String) -> Value { let mut r = json!({ "qasm": qasm }); if let Some(s) = job.shots { r["shots"] = json!(s); } if let Some(s) = job.seed { r["seed"] = json!(s); } if let Some(m) = &job.method { r["method"] = json!(m); } if !job.observables.is_empty() { r["observables"] = json!(job.observables); } if !job.amplitudes.is_empty() { r["amplitudes"] = json!(job.amplitudes); } if !job.noise.is_empty() { r["noise"] = Value::Object(job.noise.iter().map(|(k, v)| (k.clone(), json!(v))).collect()); } if !job.options.is_empty() { r["options"] = Value::Object(job.options.iter().cloned().collect()); } if let Some(e) = job.compile { r["compile"] = json!({ "epsilon": e }); } if job.repair { r["repair"] = match job.max_iterations { Some(n) => json!({ "max_iterations": n }), None => json!(true), }; } if job.verdict && job.intent.is_empty() { r["verdict"] = json!(true); } if let Some(m) = &job.mitigate { r["mitigate"] = if m.trim_start().starts_with('{') { serde_json::from_str(m).unwrap_or_else(|_| json!(m)) } else { json!(m) }; } if let Some(p) = &job.pulse { r["pulse"] = p.clone(); } if !job.params.is_empty() { r["params"] = Value::Object(job.params.iter().map(|(k, v)| (k.clone(), json!(v))).collect()); } match &job.fabric[..] { [] => {} [one] => r["fabric"] = json!(one), many => r["fabric"] = json!(many), } r } /// What a result says of its energy: the command line does not meter a run. const UNMETERED: &str = "the command line does not meter a run's energy"; /// The job body `run` gives, as the service gives it: the request, the plan, /// and the result sealed with `key` ([`job::run`]). pub fn run_here(req: &Value, memory_gb: u64, key: &wai_quantum::ed25519_dalek::SigningKey) -> Result { if catalog::is_set(req) { return wai_quantum::engine::sets::run(req, memory_gb << 30, key, json!({ "unmetered": UNMETERED })); } job::run(req, memory_gb << 30, key, json!({ "unmetered": UNMETERED })) } /// What is new in a circuit as a reader takes it in. pub(crate) fn novelty_text(v: &Value) -> String { let mut out = String::new(); if let Some(n) = v["nearest"].as_object() { out.push_str(&format!("nearest {} (distance {}, of {} compared)\n", n["name"].as_str().unwrap_or(""), n["distance"], v["compared"])); } let found = v["rediscovers"].as_array().cloned().unwrap_or_default(); if found.is_empty() { out.push_str("rediscovers no known construction the engine checks\n"); } for r in &found { let what = if r["prepares"] == true { "the same state as" } else { "the same circuit as" }; let flag = if r["route"] == "a different route" { " ← a different route to a known result" } else { "" }; out.push_str(&format!("rediscovers {what} {} (by {}; distance {}){flag}\n", r["name"].as_str().unwrap_or(""), r["how"].as_str().unwrap_or(""), r["distance"])); } for s in v["unasked"].as_array().into_iter().flatten() { out.push_str(&format!("unasked {}: {} against the reference's {}\n", s["measure"].as_str().unwrap_or(""), s["candidate"], s["reference"])); } out } /// The versions as a reader takes them in. fn version_text(v: &Value) -> String { let s = |p: &str| v.pointer(p).and_then(Value::as_str).unwrap_or("").to_string(); let mut out = format!("{} {}\nengine {} {}\n", s("/host/name"), s("/host/version"), s("/engine/name"), s("/engine/version")); for (k, n) in v["schemas"].as_object().into_iter().flatten() { out.push_str(&format!("schema {:<14}{}\n", k, n.as_str().unwrap_or(""))); } let mcp: Vec<&str> = v.pointer("/protocols/mcp").and_then(Value::as_array).into_iter().flatten().filter_map(Value::as_str).collect(); out.push_str(&format!("mcp {}\n", mcp.join(", "))); if let Some(p) = v.pointer("/protocols/provider").and_then(Value::as_str) { out.push_str(&format!("providers {p}\n")); } out.push_str(&format!("policy {}\ndeprecations {}\n", s("/policy"), s("/deprecations"))); out } /// A critique as a reader takes it in. fn print_critique(name: &str, c: &Value) { let n = |k: &str| c[k].as_u64().unwrap_or(0); println!("{name}: {} qubits, {} gates ({} one-qubit, {} two-qubit, {} three-qubit), depth {}, two-qubit depth {}", n("qubits"), n("gates"), n("one_qubit"), n("two_qubit"), n("three_qubit"), n("depth"), n("two_qubit_depth")); let clifford = if c["clifford"] == true { "a Clifford circuit".to_string() } else { format!("{} gates off the Clifford angles", n("non_clifford")) }; println!(" {} T gates; {clifford}; {} qubits measured", n("t_count"), n("measured")); let f = &c["fidelity_model"]; println!(" fidelity {:.4}, modelled: {}", f["estimate"].as_f64().unwrap_or(f64::NAN), f["note"].as_str().unwrap_or("")); for s in c["smells"].as_array().into_iter().flatten() { let fix = if s["fixable"] == true { " (improve fixes it)" } else { "" }; println!(" {}: {}{fix}", s["kind"].as_str().unwrap_or(""), s["message"].as_str().unwrap_or("")); } } /// The classical routes as a reader takes them in. fn print_shortcuts(v: &Value) { println!("{}", v["summary"].as_str().unwrap_or("")); println!(); println!("{:<15} {:>10} {:>9} {:>12} why", "route", "memory", "log2 ops", "at 10^10/s"); for r in v["routes"].as_array().into_iter().flatten() { let name = r["route"].as_str().unwrap_or(""); let why = r["why"].as_str().unwrap_or(""); if r["applies"] != true || r.get("log2_work").is_none() { let mark = if r["applies"] == true { "—" } else { "no" }; println!("{name:<15} {mark:>10} {:>9} {:>12} {why}", "", ""); continue; } let bytes = r["memory_bytes"].as_f64().unwrap_or(0.0); let mem = if bytes < 1024.0 { format!("{bytes:.0} B") } else { format!("2^{:.1} B", bytes.log2()) }; let secs = r["seconds_at_reference"].as_f64().unwrap_or(f64::NAN); let t = if secs < 1.0 { format!("{:.1e} s", secs) } else if secs < 3.15e7 { format!("{secs:.3e} s") } else { format!("{:.1e} y", secs / 3.15e7) }; println!("{name:<15} {mem:>10} {:>9.2} {t:>12} {why}", r["log2_work"].as_f64().unwrap_or(f64::NAN)); } println!(); println!("{}", v["note"].as_str().unwrap_or("")); } /// A plan's or a result's `target` as a reader takes it in. fn print_target(t: &Value) { let p = &t["placement"]; let n = &t["noise"]; println!("target {} (calibration {}), hash {}", t["name"].as_str().unwrap_or(""), t["calibration"]["id"].as_str().unwrap_or("unnamed"), t["hash"].as_str().unwrap_or("")); println!( "placed on {} → {}, {} swaps, {} native gates in {}; expected success {:.4} (fewest swaps {:.4})", p["layout"], p["final_layout"], p["swaps"], p["native_gates"], p["basis"].as_str().unwrap_or(""), p["expected_success"].as_f64().unwrap_or(f64::NAN), p["fewest_swaps"]["expected_success"].as_f64().unwrap_or(f64::NAN) ); println!("noise {} channels over {:.0} ns ({:.0} ns idle); {}", n["channels"], n["duration_ns"].as_f64().unwrap_or(0.0), n["idle_ns"].as_f64().unwrap_or(0.0), n["model"].as_str().unwrap_or("")); for note in p["notes"].as_array().into_iter().flatten().chain(n["notes"].as_array().into_iter().flatten()) { println!(" {}", note.as_str().unwrap_or("")); } } /// A job body as a reader takes it in. fn print_job(body: &Value, top: usize) { let r = &body["result"]; let s = |v: &Value| v.as_str().map(str::to_string).unwrap_or_else(|| v.to_string()); if r["format"] == wai_quantum::engine::sets::FORMAT { println!("set {} items, {} done, {} failed", r["count"], r["done"], r["failed"]); for it in r["items"].as_array().into_iter().flatten() { let point = it.get("point").map(|p| format!(" {p}")).unwrap_or_default(); println!("── item {}{point}", it["index"]); match it["error"].as_str() { Some(e) => println!("failed {e}"), None => print_job(it, top), } } let v = wai_quantum::engine::sets::verify(body); println!("── the set"); println!("root {} (the Merkle root of every item's leaf)", s(&r["root"])); if let Some(key) = r.pointer("/receipt/key").and_then(Value::as_str) { println!("receipt {}, signed by {}", if v["valid"] == true { "verifies, every item sealed" } else { "DOES NOT VERIFY" }, job::did_of(key).unwrap_or_else(|| key.to_string())); } return; } println!("method {} — {}", s(&body["method"]), s(&body["why"])); if let Some(acc) = r.get("accuracy") { let mut extra: Vec = acc.as_object().map(|o| o.iter().filter(|(k, _)| *k != "class" && *k != "note").map(|(k, v)| format!("{k} {}", s(v))).collect()).unwrap_or_default(); if let Some(n) = acc.get("note").and_then(Value::as_str) { extra.push(n.to_string()); } println!("accuracy {}{}", s(&acc["class"]), if extra.is_empty() { String::new() } else { format!(" ({})", extra.join("; ")) }); } println!("circuit {} qubits, {} gates", r["qubits"], r["gates"]); if let Some(f) = r.get("fabric") { let s = |v: &Value| v.as_str().unwrap_or("").to_string(); match f.get("partitions").and_then(Value::as_array) { Some(parts) => println!("fabric {} partitions on {}, {} exchanges, {}", parts.len(), parts.iter().map(|p| s(&p["key"])).collect::>().join(", "), f["exchanges"], s(&f["parity"]["tier"])), None => println!("fabric {} ({}), {}, certificate {}", s(&f["key"]), s(&f["implementation"]), s(&f["parity"]["tier"]), s(&f["certificate"])), } if f.get("auto").is_some() { println!(" {}", s(&f["why"])); } } if let Some(w) = r.get("warning").and_then(Value::as_str) { println!("warning {w}"); } if let Some(p) = r.get("global_phase").and_then(Value::as_str) { println!("phase {p}"); } if let Some(counts) = r.get("counts").and_then(Value::as_object) { let total: u64 = counts.values().filter_map(Value::as_u64).sum::() + r["omitted_shots"].as_u64().unwrap_or(0); println!("counts {total} shots, {} outcomes", r["distinct_outcomes"].as_u64().unwrap_or(counts.len() as u64)); let mut v: Vec<(&String, u64)> = counts.iter().map(|(k, c)| (k, c.as_u64().unwrap_or(0))).collect(); v.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0))); for (k, c) in v.iter().take(top) { println!(" {k} {c:>8} {:.4}", *c as f64 / total.max(1) as f64); } if v.len() > top { println!(" … {} more (--top N)", v.len() - top); } } // A bound that holds with a stated probability says so. let held = |e: &Value| e.get("confidence").map(|c| format!(" (with probability {})", s(c))).unwrap_or_default(); for e in r.get("expectations").and_then(Value::as_array).into_iter().flatten() { let bound = e.get("bound").map(|b| format!(" ± {}{}", s(b), held(e))).unwrap_or_default(); println!("⟨{}⟩ {}{bound}", s(&e["observable"]), s(&e["value"])); } for a in r.get("amplitudes").and_then(Value::as_array).into_iter().flatten() { let (re, im) = (a["re"].as_f64().unwrap_or(f64::NAN), a["im"].as_f64().unwrap_or(f64::NAN)); let bound = a.get("bound").map(|b| format!(" ± {}{}", s(b), held(a))).unwrap_or_default(); println!("|{}⟩ {re:+.12} {}{:.12}i{bound}", s(&a["bits"]), if im < 0.0 { '-' } else { '+' }, im.abs()); } if let Some(d) = r.get("dynamic") { println!("dynamic {d}"); } if let Some(m) = r.get("mitigation") { println!("mitigation {}: {} circuits, {} shots", s(&m["method"]), m["cost"]["circuits"], m["cost"]["shots"]); for o in m["observables"].as_array().into_iter().flatten() { let f = |k: &str| o[k].as_f64().unwrap_or(f64::NAN); println!(" ⟨{}⟩ raw {:+.5} ± {:.5} mitigated {:+.5} ± {:.5} (ideal {:+.5}, known in simulation)", s(&o["observable"]), f("raw"), f("raw_stderr"), f("mitigated"), f("stderr"), f("ideal")); } for d in m["distribution"].as_array().into_iter().flatten().take(top) { println!(" {} raw {:.4} mitigated {:+.4} ± {:.4}", s(&d["bits"]), d["raw"].as_f64().unwrap_or(0.0), d["mitigated"].as_f64().unwrap_or(0.0), d["stderr"].as_f64().unwrap_or(0.0)); } println!(" bias {}", s(&m["assumptions"]["bias"])); } if let Some(c) = r.get("compiled") { println!("compiled {} at level {}: within {} of the program (budget {}), {} T gates; certificate {}", s(&c["basis"]), c["level"], s(&c["distance"]), s(&c["epsilon"]), c["output"]["t_count"], s(&c["certificate"])); } if let Some(t) = r.get("target") { print_target(t); if let Some(d) = r.pointer("/ideal/total_variation_distance").and_then(Value::as_f64) { println!("from ideal total variation distance {d:.6}, Hellinger fidelity {:.6}", r["ideal"]["hellinger_fidelity"].as_f64().unwrap_or(f64::NAN)); } } if r.get("pulse").is_some_and(Value::is_object) { print!("{}", pulse::report_run(r)); } for x in r.pointer("/repaired/repairs").and_then(Value::as_array).into_iter().flatten() { let lines: Vec = x["edits"].as_array().into_iter().flatten().filter_map(|e| e["line"].as_u64()).map(|l| l.to_string()).collect(); println!("repaired {}: {} (line {})", x["code"].as_str().unwrap_or("—"), x["rule"].as_str().unwrap_or(""), lines.join(", ")); } if let Some(v) = r.get("verdict") { for (i, x) in v["intent"].as_array().into_iter().flatten().enumerate() { let d = x["distance"].as_f64().map(|d| format!(", distance {d:.3e}")).unwrap_or_default(); println!("intent {:<4} {} by {} ({}{d}): {}", i + 1, x["outcome"].as_str().unwrap_or(""), x["how"].as_str().unwrap_or(""), x["strength"].as_str().unwrap_or(""), x["why"].as_str().unwrap_or("")); } println!("verdict {}: {}", v["level"].as_str().unwrap_or(""), v["statement"].as_str().unwrap_or("")); } if let Some(ms) = r.get("elapsed_ms") { println!("time {ms} ms"); } if let Some(key) = r.pointer("/receipt/key").and_then(Value::as_str) { let ok = job::verify(body).map(|(ok, _)| ok).unwrap_or(false); println!("receipt {}, signed by {}", if ok { "verifies" } else { "DOES NOT VERIFY" }, job::did_of(key).unwrap_or_else(|| key.to_string())); } } fn load_key(k: &KeyArgs) -> keys::Key { let key = keys::load(k.key.as_deref(), k.demo_key).unwrap_or_else(|e| die(&e)); keys::notes(&key); key } /// A dry run as a person reads it. #[cfg(feature = "service")] fn print_dry_run(p: &Value) { let s = |v: &Value| v.as_str().map(str::to_string).unwrap_or_else(|| v.to_string()); match p["accepted"].as_bool() { Some(true) => println!("would run yes, now"), _ => println!("would run no: {}", s(&p["refusal"]["message"])), } if let Some(m) = p["method"].as_str() { println!("method {m} — {}", s(&p["why"])); println!("memory {:.1} MiB at most", p["memory_bytes"].as_f64().unwrap_or(0.0) / (1u64 << 20) as f64); match p["time"]["seconds"].as_f64() { Some(t) => println!("time about {t:.3} s ({})", s(&p["time"]["basis"])), None => println!("time not estimated: {} (at most {} s)", s(&p["time"]["why"]), p["time"]["at_most"]), } match p["energy"]["joules_micro_estimate"].as_u64() { Some(u) => println!("energy about {:.3} J, an estimate ({})", u as f64 / 1e6, s(&p["energy"]["basis"])), None => println!("energy not estimated: {}", s(&p["energy"]["unestimated"])), } } for it in p["items"].as_array().into_iter().flatten() { let point = it.get("point").map(|x| format!(" {x}")).unwrap_or_default(); println!("item {:<6} {}{point}, about {} s", it["index"], s(&it["method"]), it["time"]["seconds"]); } let b = &p["bounds"]; println!("bounds {} MiB a job, {} s a job, {} at once, {} submissions in {} s", b["job_memory_bytes"].as_u64().unwrap_or(0) >> 20, b["job_seconds"], b["active_jobs_per_client"], b["submissions_per_window"], b["window_seconds"]); println!("you {} active, {} submissions left in this window", p["client"]["active_jobs"], p["client"]["submissions_left"]); } /// `convert`: the program in `file` written as `to`, to `output` or standard /// output, its notes and the check on standard error (or all of it as JSON). fn convert(file: Option<&Path>, to: wai_quantum::engine::formats::Format, profile: Option<&str>, output: Option<&Path>, as_json: bool, no_check: bool) { use std::io::{IsTerminal, Write as _}; use wai_quantum::engine::formats::{self, Format, Profile}; let bytes = read_bytes(file).unwrap_or_else(|e| die(&e)); let from = match FROM.get() { Some(Some(f)) => *f, _ => file.and_then(|p| Format::from_extension(&p.to_string_lossy())).unwrap_or_else(|| Format::sniff(&bytes)), }; let profile = profile.map(|p| Profile::parse(p).unwrap_or_else(|| die(&format!("{p}: the QIR profiles are base and adaptive")))); let c = formats::convert(from, to, &bytes, profile, !no_check).unwrap_or_else(|d| die(&d.to_string())); let verdict = c.check["verdict"]["verdict"].as_str().unwrap_or(""); if as_json { let mut v = json!({ "ok": true, "format": formats::FORMAT, "from": from.name(), "to": to.name(), "qasm": c.qasm, "notes": c.notes.iter().map(|d| d.to_json()).collect::>(), "read": c.read, "written": c.written, "check": c.check, }); if to.is_binary() { v["program_base64"] = json!(formats::encode_base64(&c.output)); } else { v["program"] = json!(String::from_utf8_lossy(&c.output)); } if let Some(o) = output { std::fs::write(o, &c.output).unwrap_or_else(|e| die(&format!("{}: {e}", o.display()))); } println!("{v}"); } else { for n in &c.notes { eprintln!("note: {n}"); } if !c.check.is_null() { eprintln!("check: {} ({}): {}", verdict, c.check["verdict"]["how"].as_str().unwrap_or(""), c.check["what"].as_str().unwrap_or("")); } match output { Some(o) => std::fs::write(o, &c.output).unwrap_or_else(|e| die(&format!("{}: {e}", o.display()))), None if to.is_binary() && std::io::stdout().is_terminal() => die("jeff is binary: give -o FILE, or --json for its base64"), None => { let mut out = std::io::stdout(); out.write_all(&c.output).and_then(|_| out.flush()).unwrap_or_else(|e| die(&format!("standard output: {e}"))); } } } if verdict == "different" { std::process::exit(2); } } fn main() { // The help a parse prints is in the person's language, so the language is read first. let args: Vec = std::env::args().collect(); let loc = locale::resolve(locale::arg_lang(&args).as_deref()); locale::set(loc); let cli = Cli::from_arg_matches(&locale::localize(Cli::command(), loc).get_matches_from(&args)).unwrap_or_else(|e| e.exit()); let _ = INPUTS.set(cli.inputs.iter().cloned().collect()); let _ = FROM.set(cli.from); match cli.command { #[cfg(feature = "providers")] Command::Run { job, out, key, provider } if provider.provider.is_some() => providers::run(&job, &out, &key, &provider), Command::Run { job, out, key, .. } => { let req = job_request(&job).unwrap_or_else(|e| die(&e)); let k = load_key(&key); let body = match run_here(&req, job.memory_gb, &k.key) { Ok(b) => b, // A job that asked for a verdict is refused with one. Err(e) if wai_quantum::engine::verdict::asked(&req) || job.repair => { let v = wai_quantum::engine::verdict::of_refusal(&req, &e); if out.json { println!("{}", json!({ "error": e, "verdict": v })); } else { eprintln!("verdict {}: {}", v["level"].as_str().unwrap_or(""), v["statement"].as_str().unwrap_or("")); } die(&e) } Err(e) => die(&e), }; if out.json { println!("{body}"); } else { print_job(&body, out.top); } // With an intent: exit 0 it holds, 1 it fails, 2 it is unchecked; // for a set, every item's intent. if !job.intent.is_empty() { let holds: Vec> = match body.pointer("/result/items").and_then(Value::as_array) { Some(items) => items.iter().map(|i| i.pointer("/result/verdict/holds").and_then(Value::as_bool)).collect(), None => vec![body.pointer("/result/verdict/holds").and_then(Value::as_bool)], }; std::process::exit(if holds.contains(&Some(false)) { 1 } else if holds.iter().all(|h| *h == Some(true)) { 0 } else { 2 }); } } Command::Plan { job, json } if !job.programs.is_empty() || !job.sweep.is_empty() => { let req = job_request(&job).unwrap_or_else(|e| die(&e)); let items = wai_quantum::engine::sets::expand(&req).unwrap_or_else(|e| die(&e)); let plans = wai_quantum::engine::sets::plan(&items, job.memory_gb << 30).unwrap_or_else(|e| die(&e)); if json { println!("{}", json!({ "set": serde_json::to_value(wai_quantum::engine::sets::summary(&plans)).unwrap_or(Value::Null), "items": items.iter().zip(&plans).map(|(i, p)| json!({ "point": i.point, "plan": p })).collect::>() })); } else { let sum = wai_quantum::engine::sets::summary(&plans); println!("set {} items: {}", plans.len(), sum.why); println!("memory {:.1} MiB at most (the largest item)", sum.memory_bytes as f64 / (1u64 << 20) as f64); for (k, (i, p)) in items.iter().zip(&plans).enumerate() { let point = i.point.as_ref().map(|p| format!(" {p}")).unwrap_or_default(); println!("item {k:<6} {}{point} — {}", p.method, p.why); } } } Command::Plan { job, json } => { let req = job_request(&job).unwrap_or_else(|e| die(&e)); let p = catalog::plan(&req, job.memory_gb << 30).unwrap_or_else(|e| die(&e)); if json { println!("{}", serde_json::to_value(&p).unwrap_or(Value::Null)); } else { println!("method {}", p.method); println!("why {}", p.why); println!("qubits {}", p.qubits); println!("memory {:.1} MiB at most", p.memory_bytes as f64 / (1u64 << 20) as f64); println!("work {}", p.work); match catalog::nominal_rate(&p.method) { Some(rate) => println!("time about {:.3} s at the planner's nominal rate (an estimate, not measured here)", p.work as f64 / rate), None => println!("time not estimated: {}'s work does not predict its time", p.method), } if let Some(t) = &p.target { print_target(t); } } } Command::Instance { file } => { print!("{}", read_input(file.as_deref()).unwrap_or_else(|e| die(&e))); } Command::Convert { file, to, profile, output, json, no_check } => convert(file.as_deref(), to, profile.as_deref(), output.as_deref(), json, no_check), Command::Lint { file, json } => { let name = file.as_deref().filter(|p| *p != Path::new("-")).map(|p| p.display().to_string()).unwrap_or_else(|| "".into()); let src = read_input(file.as_deref()).unwrap_or_else(|e| die(&e)); let v = job::lint(&src); if json { println!("{v}"); } else if v["ok"] == true { let dynamic = v["dynamic_steps"].as_u64().map(|s| format!(", dynamic: {s} steps")).unwrap_or_default(); println!("{name}: ok — {} qubits, {} gates, {} bits measured{dynamic}", v["qubits"], v["gates"], v["measured"]); } else { println!("{name}:{}", v["error"].as_str().unwrap_or("").trim_start_matches("line ")); } if v["ok"] != true { std::process::exit(1); } } Command::Critique { file, json } => { let name = file.as_deref().filter(|p| *p != Path::new("-")).map(|p| p.display().to_string()).unwrap_or_else(|| "".into()); let src = read_input(file.as_deref()).unwrap_or_else(|e| die(&e)); let c = critique::critique(&src); if json { println!("{c}"); } else if c["ok"] == true { print_critique(&name, &c); } else { die(c["error"].as_str().unwrap_or("the program does not read")); } if c["ok"] != true { std::process::exit(1); } } Command::Equivalent { a, b, json } => { let (x, y) = (read_input(Some(&a)).unwrap_or_else(|e| die(&e)), read_input(Some(&b)).unwrap_or_else(|e| die(&e))); let v = equiv::equivalent(&x, &y); if json { println!("{v}"); } else { println!("{}: {} ({})", v["verdict"].as_str().unwrap_or(""), v["why"].as_str().unwrap_or(""), v["how"].as_str().unwrap_or("")); } std::process::exit(match v["verdict"].as_str() { Some("equivalent") => 0, Some("different") => 1, _ => 2, }); } Command::Improve { file, json } => { let src = read_input(file.as_deref()).unwrap_or_else(|e| die(&e)); let v = critique::variants(&src); if v["ok"] != true { die(v["error"].as_str().unwrap_or("the program does not read")); } if json { println!("{v}"); return; } match v["candidates"].as_array().and_then(|c| c.first()) { Some(c) => { for ch in c["changes"].as_array().into_iter().flatten() { eprintln!("fixed: {}", ch.as_str().unwrap_or("")); } let (b, m) = (&v["base"], &c["measures"]); eprintln!("gates {} → {}, two-qubit {} → {}, depth {} → {}", b["gates"], m["gates"], b["two_qubit"], m["two_qubit"], b["depth"], m["depth"]); eprintln!("checked: {}", c["equivalence"]["why"].as_str().unwrap_or("")); print!("{}", c["qasm"].as_str().unwrap_or("")); } None => { eprintln!("nothing to improve that the engine can check: no gates cancel or merge; the program is unchanged"); print!("{src}"); } } } Command::Shortcuts { file, thorough, json } => { let src = read_input(file.as_deref()).unwrap_or_else(|e| die(&e)); let v = shortcuts::shortcuts(&src, thorough); if v["ok"] != true { die(v["error"].as_str().unwrap_or("the program does not read")); } if json { println!("{v}"); } else { print_shortcuts(&v); } } Command::Draw { file, width, svg, latex, describe } => { let src = read_input(file.as_deref()).unwrap_or_else(|e| die(&e)); let p = wai_quantum::qasm::parse(&src).unwrap_or_else(|e| die(&e)); if svg { print!("{}", wai_quantum::engine::svg::circuit::<&str>(&p, &[])); } else if latex { print!("{}", wai_quantum::engine::latex::circuit(&p)); } else if describe { println!("{}", wai_quantum::engine::describe::describe(&p)); } else { print!("{}", draw::draw(&p, width)); } } Command::Debug(a) => debug::main(&a).unwrap_or_else(|e| die(&e)), Command::Dap => std::process::exit(dap::serve()), #[cfg(feature = "service")] Command::Submit { job, out, service, dry_run, detach, api_key } => { let req = job_request(&job).unwrap_or_else(|e| die(&e)); let c = service::Client::new(&service, api_key); if dry_run { let plan = c.dry_run(&req).unwrap_or_else(|e| die(&e)); if out.json { println!("{plan}"); } else { print_dry_run(&plan); } if plan["accepted"] != true { std::process::exit(1); } return; } let admitted = c.submit(&req).unwrap_or_else(|e| die(&e)); let id = admitted["id"].as_str().unwrap_or_else(|| die("the service gave no job id")).to_string(); if detach { println!("{admitted}"); return; } let body = c.follow(&id, |s| eprintln!("job {id}: {s}")).unwrap_or_else(|e| die(&e)); if body["status"] != "done" { die(&format!("the job {}: {}", body["status"].as_str().unwrap_or("failed"), body["error"].as_str().unwrap_or(""))); } if out.json { println!("{body}"); } else { print_job(&body, out.top); } } #[cfg(feature = "service")] Command::Task { cmd, args } => task::main(cmd, &args), Command::Verify { file, history: hist } => { let src = read_text(file.as_deref()).unwrap_or_else(|e| die(&e)); let v: Value = serde_json::from_str(&src).unwrap_or_else(|e| die(&format!("not JSON: {e}"))); if v["result"]["format"] == wai_quantum::engine::estimate::FORMAT { if !estimate::verify(&v) { std::process::exit(1); } } else if v["result"]["format"] == wai_quantum::engine::sets::FORMAT { let c = wai_quantum::engine::sets::verify(&v); let yes = |b: &Value| if *b == true { "yes" } else { "NO" }; println!("signature {}", if c["valid"] == true { "VALID" } else { "INVALID" }); println!("binds the set's request and head, its root over {} items", c["items"]); println!("items as the request gives them: {}; each sealed by its own receipt and its path: {}; the root: {}", yes(&c["derived"]), yes(&c["items_sealed"]), yes(&c["root"])); if let Some(k) = c["key"].as_str() { println!("signer {}", job::did_of(k).unwrap_or_else(|| k.to_string())); } if c["valid"] != true { std::process::exit(1); } } else if v["result"]["schema"] == wai_quantum::engine::schema::ALGORITHMS { if !variational::verify(&v) { std::process::exit(1); } } else if v.get("request").is_some() && v.get("result").is_some() { let (mut ok, key) = job::verify(&v).unwrap_or_else(|e| die(&e)); println!("signature {}", if ok { "VALID" } else { "INVALID" }); println!("binds the request and the result as given"); if let Some(k) = key.as_str() { println!("signer {}", job::did_of(k).unwrap_or_else(|| k.to_string())); } if let Some(c) = v["request"].get("history") { let hands: Vec = c["gates"].as_object().into_iter().flatten().map(|(h, n)| format!("{n} {}", h.replace('_', " "))).collect(); println!("history {}: {} of {} gates", c["version"].as_str().unwrap_or("?"), hands.join(", "), c["of"]["gates"]); } if let Some(p) = hist { let h = history::load(&p).unwrap_or_else(|e| die(&e)); let r = h.check_job(&v).unwrap_or_else(|e| die(&e)); println!("account {}", if r["ok"] == true { "the history's own, recomputed from it".to_string() } else { format!("NOT the history's: {}", r["problems"]) }); ok &= r["ok"] == true; } if !ok { std::process::exit(1); } } else { lab::verify_receipt(&src); } } Command::Key { key } => { let k = load_key(&key); println!("{}", wai_quantum::quantum_receipt::did_key(&k.key.verifying_key())); match &k.path { Some(p) => eprintln!("key file {}", p.display()), None => eprintln!("the published demo key"), } } Command::Version { json } => { let v = mcp::versions(); if json { println!("{v}"); } else { print!("{}", version_text(&v)); } } Command::Methods { json, memory_gb } => { let m = catalog::describe(memory_gb << 30); if json { println!("{m}"); } else { for x in m.as_array().into_iter().flatten() { let reach = if x.get("available") == Some(&Value::Bool(false)) { "not in this build".to_string() } else { format!("up to {} qubits", x["max_qubits"]) }; println!("{:<20} {reach:<20} {}", x["method"].as_str().unwrap_or(""), x["accuracy"].as_str().unwrap_or("")); println!("{:<20} {}", "", x["circuits"].as_str().unwrap_or("")); } } } Command::Fabric { json, memory_gb } => { let f = wai_quantum::engine::fabric::list(memory_gb << 30); if json { println!("{f}"); } else { print!("{}", fabric_text(&f)); } } Command::Components { json } => { let c = wai_quantum::engine::registry::Registry::builtins().components(); if json { println!("{c}"); } else { print!("{}", components_text(&c)); } } Command::Diagnostics { json } => { if json { println!("{}", job::diagnostic_codes()); } else { for c in wai_quantum::qasm::diag::codes() { println!("{} {:<9} {}", c.code, c.outcome.as_str(), c.meaning); } for c in wai_quantum::engine::refusal::codes() { println!("{} {:<9} {}", c.code, c.outcome.as_str(), c.meaning); } } } Command::Mcp { cmd: Some(cmd), .. } => guide::mcp(&cmd).unwrap_or_else(|e| die(&e)), Command::Mcp { cmd: None, memory_gb, key } => { let k = load_key(&key); mcp::serve(memory_gb, &k.key); } Command::New(a) => print!("{}", project::new(&a).unwrap_or_else(|e| die(&e))), Command::Test(t) => std::process::exit(project::main(&t)), Command::SetupAgent(a) => std::process::exit(guide::setup_agent(&a).unwrap_or_else(|e| die(&e))), Command::Docs(d) => guide::docs(&d).unwrap_or_else(|e| die(&e)), Command::Learn(a) => std::process::exit(learn::main(a).unwrap_or_else(|e| die(&e))), Command::Fmt { files, check, write } => std::process::exit(fmt::main(&files, check, write)), Command::Lsp { memory_gb, key, stdio: _ } => { let k = load_key(&key); std::process::exit(lsp::serve(memory_gb, k.key)); } Command::Compile(a) => compile::main(a), Command::Estimate(a) => estimate::main(a), c @ (Command::Gradient(_) | Command::Optimize(_) | Command::Vqe(_) | Command::Qaoa(_) | Command::Build(_)) => variational::main(&c), Command::Repair(a) => repair::main(a), Command::Eval { cmd } => eval::main(&cmd).unwrap_or_else(|e| die(&e)), Command::Discover { cmd } => discover::main(cmd), Command::History { cmd } => history::main(cmd), Command::Locks { cmd } => locks::main(cmd), Command::Taste { cmd } => taste::main(cmd), Command::Rewrite { cmd } => rewrite::main(cmd), Command::Converge { cmd } => converge::main(cmd), Command::Study { cmd } => study::main(cmd), Command::Novelty { file, against, reference, weigh, json } => { let inputs = INPUTS.get().cloned().unwrap_or_default(); let text = read_text(file.as_deref()).unwrap_or_else(|e| die(&e)); let others: Vec<(String, String)> = against.iter().map(|p| (p.display().to_string(), read_text(Some(p)).unwrap_or_else(|e| die(&e)))).collect(); let reference = reference.map(|p| read_text(Some(&p)).unwrap_or_else(|e| die(&e))); let v = wai_quantum::engine::novelty::assess(&text, &inputs, &others, reference.as_deref(), &weigh); if v["ok"] != true { die(v["error"].as_str().unwrap_or("the program does not read")); } if json { println!("{v}"); } else { print!("{}", novelty_text(&v)); } } Command::Qec { cmd } => qec::main(cmd), Command::Pulse { cmd } => pulse::main(cmd), Command::Lab { args } => lab::main(args), Command::Targets { cmd } => targets::main(cmd), #[cfg(feature = "providers")] Command::Providers { cmd } => providers::main(cmd), Command::Completions { shell } => { clap_complete::generate(shell, &mut Cli::command(), "wai-quantum", &mut std::io::stdout()); } } } #[cfg(test)] mod tests { use super::*; #[test] fn a_generator_is_read_at_its_inputs_and_a_plain_program_as_it_is() { let ghz = "OPENQASM 3.0;\ninput uint n;\nqubit[n] q;\nh q[0];\nfor uint i in [0:n-2] { cx q[i], q[i + 1]; }\n"; let at = |text: &str, pairs: &[(&str, i64)]| at_inputs(text.to_string(), &pairs.iter().map(|(k, v)| (k.to_string(), *v)).collect()); let three = at(ghz, &[("n", 3)]).unwrap(); assert_eq!(three, "OPENQASM 3.0;\nqubit[3] q;\nh q[0];\ncx q[0], q[1];\ncx q[1], q[2];\n"); // The instance runs as the service would run it: twelve qubits, one // GHZ state, every shot all zeros or all ones. let (mut j, _) = job(""); j.shots = Some(200); let key = wai_quantum::ed25519_dalek::SigningKey::from_bytes(&[3; 32]); let body = run_here(&request(&j, at(ghz, &[("n", 12)]).unwrap()), 1, &key).unwrap(); let counts = body["result"]["counts"].as_object().unwrap(); assert!(counts.keys().all(|k| k == &"0".repeat(12) || k == &"1".repeat(12)), "{counts:?}"); let plain = "OPENQASM 3.0;\nqubit q;\nh q;\n"; assert_eq!(at(plain, &[]).unwrap(), plain); assert!(at(ghz, &[]).unwrap_err().contains("give n a value")); assert!(at(plain, &[("n", 2)]).unwrap_err().contains("no `input n`")); } /// A set's request is the program and the others in order, the options /// shared; a sweep's carries the program as written, inputs and all. #[test] fn a_set_and_a_sweep_are_requested_as_the_service_reads_them() { let dir = std::env::temp_dir().join(format!("wai-q-cli-set-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let (a, b) = (dir.join("a.qasm"), dir.join("b.qasm")); std::fs::write(&a, "OPENQASM 3.0; qubit q; h q;").unwrap(); std::fs::write(&b, "OPENQASM 3.0;\ninput float theta;\nqubit q;\nrx(theta) q;\n").unwrap(); let (mut j, _) = job(""); j.file = Some(a.clone()); j.shots = Some(8); j.programs = vec![a.clone()]; let r = job_request(&j).unwrap(); assert_eq!(r, json!({ "programs": ["OPENQASM 3.0; qubit q; h q;", "OPENQASM 3.0; qubit q; h q;"], "shots": 8 })); let body = run_here(&r, 1, &wai_quantum::ed25519_dalek::SigningKey::from_bytes(&[2; 32])).unwrap(); assert_eq!(wai_quantum::engine::sets::verify(&body)["valid"], true); let (mut j, _) = job(""); j.file = Some(b.clone()); j.observables = vec!["Z0".into()]; j.sweep = vec![("theta".into(), vec![json!(0.0), json!(1.0)])]; let r = job_request(&j).unwrap(); assert_eq!(r["sweep"], json!({ "theta": [0.0, 1.0] })); assert!(r["qasm"].as_str().unwrap().contains("input float theta;")); let body = run_here(&r, 1, &wai_quantum::ed25519_dalek::SigningKey::from_bytes(&[2; 32])).unwrap(); assert!((body["result"]["items"][1]["result"]["expectations"][0]["value"].as_f64().unwrap() - 1f64.cos()).abs() < 1e-12); let _ = std::fs::remove_dir_all(&dir); } fn job(qasm: &str) -> (Job, String) { ( Job { file: None, shots: None, seed: None, method: None, observables: vec![], amplitudes: vec![], noise: vec![], options: vec![], memory_gb: 1, history: None, compile: None, target: None, intent: vec![], verdict: false, repair: false, max_iterations: None, programs: vec![], sweep: vec![], mitigate: None, pulse: None, params: vec![], fabric: vec![] }, qasm.to_string(), ) } /// `--intent` takes a reference program, a distribution, an intent, a /// list, or a project's intent.json, each reference read beside it. #[test] fn an_intent_file_is_a_reference_a_distribution_or_intents() { let d = std::env::temp_dir().join(format!("wai-quantum-intent-{}", std::process::id())); std::fs::create_dir_all(&d).unwrap(); let bell = "OPENQASM 3.0;\nqubit[2] q;\nbit[2] c;\nh q[0];\ncx q[0], q[1];\nc = measure q;\n"; std::fs::write(d.join("ref.qasm"), bell).unwrap(); std::fs::write(d.join("dist.json"), r#"{"00": 0.5, "11": 0.5}"#).unwrap(); std::fs::write(d.join("list.json"), r#"[{"prepares": "ref.qasm", "note": "the pair"}, {"distribution": {"00": 0.5, "11": 0.5}, "tolerance": 1e-6}]"#).unwrap(); std::fs::write(d.join("intent.json"), r#"{"program": "x.qasm", "intent": [{"equivalent": "ref.qasm"}]}"#).unwrap(); let r = intent_file(&d.join("ref.qasm")).unwrap(); assert_eq!(r[0]["equivalent"], bell); assert_eq!(intent_file(&d.join("dist.json")).unwrap()[0]["distribution"]["11"], 0.5); let l = intent_file(&d.join("list.json")).unwrap(); assert_eq!((l[0]["prepares"].as_str(), l[0]["name"].as_str(), l.len()), (Some(bell), Some("ref.qasm"), 2)); assert_eq!(intent_file(&d.join("intent.json")).unwrap()[0]["equivalent"], bell); // Run with all of them: the verdict, sealed. let (mut j, q) = job(bell); j.shots = Some(20); let mut req = request(&j, q); req["intent"] = Value::Array(["ref.qasm", "dist.json", "list.json"].iter().flat_map(|f| intent_file(&d.join(f)).unwrap()).collect()); let key = wai_quantum::ed25519_dalek::SigningKey::from_bytes(&[3; 32]); let body = run_here(&req, 1, &key).unwrap(); assert_eq!((body["result"]["verdict"]["level"].as_str(), body["result"]["verdict"]["intent"].as_array().map(Vec::len)), (Some("proof"), Some(4)), "{body}"); assert!(job::verify(&body).unwrap().0); assert!(intent_file(&d.join("missing.json")).is_err()); let _ = std::fs::remove_dir_all(&d); } #[test] fn a_run_here_is_the_services_job_and_its_receipt_verifies() { let (mut j, q) = job("OPENQASM 3.0; qubit[2] q; h q[0]; cx q[0], q[1];"); j.shots = Some(1000); j.observables = vec!["Z0 Z1".into()]; let req = request(&j, q); // Only what was given is in the request. assert_eq!(req.as_object().unwrap().keys().collect::>(), ["qasm", "shots", "observables"]); let key = wai_quantum::ed25519_dalek::SigningKey::from_bytes(&[3; 32]); let body = run_here(&req, 1, &key).unwrap(); assert_eq!(body["method"], "stabilizer"); assert_eq!(body["result"]["expectations"][0]["value"], 1.0); let counts = body["result"]["counts"].as_object().unwrap(); assert!(counts.keys().all(|k| k == "00" || k == "11"), "{counts:?}"); assert!(job::verify(&body).unwrap().0); // The result changed, or the request, and the receipt no longer holds. let mut forged = body.clone(); forged["result"]["expectations"][0]["value"] = json!(0.5); assert!(!job::verify(&forged).unwrap().0); let mut forged = body.clone(); forged["request"]["shots"] = json!(999); assert!(!job::verify(&forged).unwrap().0); // A JSON writer's round trip leaves it verifying. let again: Value = serde_json::from_str(&body.to_string()).unwrap(); assert!(job::verify(&again).unwrap().0); } #[test] fn noise_and_options_are_read_and_a_misspelt_channel_is_refused() { assert_eq!(noise_arg("depolarizing_1q=0.01").unwrap(), ("depolarizing_1q".into(), 0.01)); assert!(noise_arg("depolarizing=0.01").unwrap_err().contains("no such channel")); assert!(noise_arg("readout").is_err()); assert_eq!(option_arg("max_bond=128").unwrap().1, json!(128)); assert_eq!(option_arg("x=true").unwrap().1, json!(true)); assert_eq!(option_arg("kind=fast").unwrap().1, json!("fast")); } #[cfg(feature = "service")] #[test] fn the_service_s_commands_parse_as_documented() { // A set, a dry run and a detached submission, and the task commands. match Cli::try_parse_from(["wai-quantum", "submit", "a.qasm", "--program", "b.qasm", "--program", "c.qasm", "--shots", "10", "--dry-run"]).unwrap().command { Command::Submit { job, dry_run, detach, .. } => assert!(job.programs.len() == 2 && dry_run && !detach), _ => panic!("not submit"), } assert!(Cli::try_parse_from(["wai-quantum", "submit", "p.qasm", "--dry-run", "--detach"]).is_err()); for t in [vec!["task", "target"], vec!["task", "acquire", "--lease", "l.json"], vec!["task", "start", "job.json"], vec!["task", "wait", "abc"], vec!["task", "result", "abc", "--service", "http://127.0.0.1:1"], vec!["task", "release"]] { let argv: Vec<&str> = std::iter::once("wai-quantum").chain(t.iter().copied()).collect(); assert!(matches!(Cli::try_parse_from(&argv).unwrap().command, Command::Task { .. }), "{t:?}"); } } #[test] fn the_command_line_parses_as_documented() { Cli::command().debug_assert(); let c = Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--shots", "10", "-o", "Z0", "-o", "X1 X2", "--noise", "readout=0.02", "--option", "max_bond=8", "--json"]).unwrap(); match c.command { Command::Run { job, out, .. } => { assert_eq!(job.shots, Some(10)); assert_eq!(job.observables, ["Z0", "X1 X2"]); assert_eq!(job.noise, [("readout".to_string(), 0.02)]); assert!(out.json); } _ => panic!("not run"), } let c = Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--intent", "ref.qasm", "--intent", "dist.json", "--repair", "--max-iterations", "8"]).unwrap(); match c.command { Command::Run { job, .. } => assert!(job.intent.len() == 2 && job.repair && job.max_iterations == Some(8)), _ => panic!("not run"), } assert!(Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--max-iterations", "8"]).is_err(), "a bound needs --repair"); // Parameters on a job, and the variational and algorithm commands. match Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--param", "theta=0.3", "--param", "phi=-1e-3"]).unwrap().command { Command::Run { job, .. } => { assert_eq!(job.params, [("theta".to_string(), 0.3), ("phi".to_string(), -1e-3)]); assert_eq!(request(&job, "x".into())["params"], json!({ "theta": 0.3, "phi": -1e-3 })); } _ => panic!("not run"), } assert!(Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--param", "theta"]).is_err()); assert!(matches!(Cli::try_parse_from(["wai-quantum", "gradient", "p.qasm", "--param", "t=1", "--term", "Z0", "--gradient", "parameter_shift"]).unwrap().command, Command::Gradient(_))); assert!(matches!(Cli::try_parse_from(["wai-quantum", "optimize", "p.qasm", "--param", "t=1", "--term", "Z0", "--optimizer", "spsa", "--steps", "5", "--seed", "2"]).unwrap().command, Command::Optimize(_))); assert!(matches!(Cli::try_parse_from(["wai-quantum", "vqe", "--term", "Z0", "--layers", "2"]).unwrap().command, Command::Vqe(_))); assert!(matches!(Cli::try_parse_from(["wai-quantum", "qaoa", "--graph", "g.json", "--layers", "2"]).unwrap().command, Command::Qaoa(_))); assert!(matches!(Cli::try_parse_from(["wai-quantum", "build", "qsvt", "--phases", "0.1,-0.2", "--x", "-0.4"]).unwrap().command, Command::Build(_))); assert!(matches!(Cli::try_parse_from(["wai-quantum", "repair", "p.qasm", "--size", "q=3", "--max-iterations", "4", "--json"]).unwrap().command, Command::Repair(_))); // An unknown flag is an error, not ignored. assert!(Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--shot", "10"]).is_err()); assert!(matches!(Cli::try_parse_from(["wai-quantum", "equivalent", "a.qasm", "b.qasm", "--json"]).unwrap().command, Command::Equivalent { json: true, .. })); assert!(matches!(Cli::try_parse_from(["wai-quantum", "improve", "-"]).unwrap().command, Command::Improve { json: false, .. })); assert!(matches!(Cli::try_parse_from(["wai-quantum", "shortcuts", "p.qasm", "--thorough"]).unwrap().command, Command::Shortcuts { thorough: true, json: false, .. })); assert!(matches!(Cli::try_parse_from(["wai-quantum", "fmt", "a.qasm", "b.qasm", "--check"]).unwrap().command, Command::Fmt { check: true, write: false, .. })); assert!(Cli::try_parse_from(["wai-quantum", "fmt", "a.qasm", "--check", "--write"]).is_err()); assert!(matches!(Cli::try_parse_from(["wai-quantum", "lsp", "--stdio", "--demo-key"]).unwrap().command, Command::Lsp { stdio: true, .. })); assert!(matches!(Cli::try_parse_from(["wai-quantum", "draw", "p.qasm", "--svg"]).unwrap().command, Command::Draw { svg: true, latex: false, .. })); assert!(Cli::try_parse_from(["wai-quantum", "draw", "p.qasm", "--svg", "--latex"]).is_err()); match Cli::try_parse_from(["wai-quantum", "debug", "p.qasm", "--line", "9", "--outcomes", "0,1", "--qubits", "0,2", "--noise", "readout=0.01"]).unwrap().command { Command::Debug(a) => assert_eq!((a.line, a.outcomes.as_deref(), a.qubits, a.noise.len()), (Some(9), Some("0,1"), vec![0, 2], 1)), _ => panic!("not debug"), } assert!(Cli::try_parse_from(["wai-quantum", "debug", "p.qasm", "--branches", "--to", "3"]).is_err()); assert!(matches!(Cli::try_parse_from(["wai-quantum", "dap"]).unwrap().command, Command::Dap)); match Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--target", "synthetic-line-5"]).unwrap().command { Command::Run { job, .. } => assert_eq!(job.target.as_deref(), Some("synthetic-line-5")), _ => panic!("not run"), } assert!(Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--target", "x", "--noise", "readout=0.1"]).is_err(), "a target's noise is its own"); // A set and a sweep (the service's submit and task commands are in their own test). match Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--sweep", "theta=0.1,0.25,-1", "--sweep", "n=2,3"]).unwrap().command { Command::Run { job, .. } => assert_eq!(job.sweep, vec![("theta".to_string(), vec![json!(0.1), json!(0.25), json!(-1)]), ("n".to_string(), vec![json!(2), json!(3)])]), _ => panic!("not run"), } assert!(Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--sweep", "theta=x"]).is_err()); assert!(Cli::try_parse_from(["wai-quantum", "run", "p.qasm", "--sweep", "n=2", "--program", "b.qasm"]).is_err(), "a set or a sweep, not both"); assert!(matches!(Cli::try_parse_from(["wai-quantum", "targets", "show", "synthetic-line-5", "--view"]).unwrap().command, Command::Targets { .. })); assert!(matches!(Cli::try_parse_from(["wai-quantum", "version", "--json"]).unwrap().command, Command::Version { json: true })); let text = version_text(&mcp::versions()); assert!(text.starts_with(&format!("wai-quantum-cli {}\nengine wai-quantum {}\n", env!("CARGO_PKG_VERSION"), wai_quantum::engine::schema::ENGINE_VERSION)) && text.contains("wai.quantum.job-result/1") && text.contains(wai_quantum::engine::schema::POLICY), "{text}"); // The course, in either language. match Cli::try_parse_from(["wai-quantum", "learn", "check", "bell", "b.qasm", "--lang", "zh-CN"]).unwrap() { Cli { command: Command::Learn(a), lang, .. } => assert!(matches!(a.cmd, Some(learn::Cmd::Check { .. })) && lang.as_deref() == Some("zh-CN")), _ => panic!("not learn"), } assert!(matches!(Cli::try_parse_from(["wai-quantum", "learn"]).unwrap().command, Command::Learn(learn::Args { cmd: None, .. }))); // The research tools take their own words. match Cli::try_parse_from(["wai-quantum", "lab", "qec", "-d", "5", "--p", "0.003"]).unwrap().command { Command::Lab { args } => assert_eq!(args, ["qec", "-d", "5", "--p", "0.003"]), _ => panic!("not lab"), } } }