Quantum Consumer Binding Example

How a quantum host can bind Wire circuits to PennyLane, Qiskit, Cirq, or another backend without making Cortex own quantum semantics.


On this page
  1. Minimal Shape
  2. Runtime Binding
  3. Local Qiskit Runner
  4. IBM Quantum Runtime REST Runner
  5. Iterative Phase Estimation Runner
  6. Quantum Eraser Wire Experiment
  7. Linearity Caveat
  8. Related

Quantum Consumer Binding Example

This page is a consumer binding example. It shows how a quantum host can use Wire to author circuit topology while keeping quantum semantics, simulator choice, hardware access, and job policy outside the Cortex substrate.

The important boundary is the same as every Wire executor boundary:

Cortex ownsQuantum host owns
Wire parsing, graph composition, typed ports, executor projection admission, and Pulse runs.Quantum contracts, gate vocabulary, backend binding, provider credentials, queue policy, shots, and result data.
Projection data structures and registry admission checks.Runtime implementations for @quantum.*, for example through PennyLane, Qiskit, Cirq, or a hardware provider.

Wire source can reference quantum gates exactly because they are ordinary registered executors:

contract Qubit;
contract Bit;

node hadamard_control
  <- control: Qubit;
  -> control: Qubit = @quantum.h {} (control);

node entangle
  <- control: Qubit;
  <- target: Qubit;
  -> control: Qubit;
  -> target: Qubit;
  = @quantum.cnot ({ inherit control; inherit target; });

@quantum.h and @quantum.cnot are not Wire keywords. A host registers them in the executor projection registry, binds Qubit and Bit contracts to host codecs, and supplies the runtime interpreter that turns admitted nodes into backend operations.

Minimal Shape

A useful first vocabulary is:

ExecutorPort shapeHost behavior
@quantum.prepare_zerozero inputs, one Qubit outputIntroduce a backend qubit initialized to 0.
@quantum.rzone Qubit input, one Qubit outputApply a Z-axis rotation.
@quantum.sxone Qubit input, one Qubit outputApply a square-root X gate.
@quantum.xone Qubit input, one Qubit outputApply an X gate.
@quantum.hone Qubit input, one Qubit outputApply a Hadamard gate.
@quantum.rxQubit plus RotationAngle, one Qubit outputApply a parameterized X rotation.
@quantum.czcontrol and target Qubit inputs and outputsApply a controlled-Z gate.
@quantum.rzzcontrol and target Qubit inputs and outputsApply a parameterized ZZ interaction.
@quantum.cnotcontrol and target Qubit inputs and outputsApply a controlled-not gate.
@quantum.measure_zone Qubit input, one Bit outputMeasure in the Z basis.

The example circuit at ../../examples/wire/quantum-bell-state.wire authors a Bell-state topology with this model.

Runtime Binding

A Python-backed consumer can compile Wire with strict executor projections, then bind the admitted nodes to a backend-specific circuit builder:

  1. The host registers @quantum.* executor projections and Qubit, Bit, and RotationAngle contracts.
  2. Wire compiles the source to a validated Circuit with typed node boundaries.
  3. The host interpreter walks the materialized nodes and appends operations to the selected quantum backend circuit.
  4. Measurement nodes return Bit payloads or structured result records through ordinary Wire output ports.

This keeps PennyLane/Qiskit/Cirq APIs out of Cortex. Cortex sees executor IDs, config records, ports, contracts, and run results.

Local Qiskit Runner

The repository includes a Nix-backed local simulator bridge for the example vocabulary:

nix run .#wire-quantum-qiskit -- examples/wire/quantum-bell-state.wire --shots 1024 --seed 7

The runner:

  • compiles .wire input with the flake’s wire executable;
  • carries Qubit ports as symbolic Qiskit wire indices;
  • builds one QuantumCircuit;
  • executes it on Qiskit Aer aer_simulator;
  • prints a concise run summary with label-decoded counts.

Machine-readable output is available with --json:

nix run .#wire-quantum-qiskit -- examples/wire/quantum-bell-state.wire --json

Plan inspection without simulator execution is also available:

nix run .#wire-quantum-qiskit -- examples/wire/quantum-bell-state.wire --emit-plan

The runner supports only the local Aer simulator. It refuses non-local backend names so an account or provider credentials cannot accidentally queue hardware jobs. Hardware execution should be added as a separate explicit binding with provider, credential, queue, cost, and audit policy in its own config path.

IBM Quantum Runtime REST Runner

The repository also includes an explicit hardware runner that talks to IBM Quantum Runtime through REST:

nix run .#wire-quantum-ibm-rest -- examples/wire/quantum-bell-state-ibm-rest.wire --dry-run --config examples/wire/quantum-ibm-runtime.config.example.json

That example starts with a config node:

node ibm_runtime_config
  -> config: IBMQuantumConfig = @quantum.ibm_runtime_config { path = "quantum-ibm-runtime.local.json"; } (null);

The runner treats that node as the source of the provider config path. The config path is resolved relative to the .wire file. The example threads the config token into the first prepare node only to make the Wire graph connected; the runner treats it as orchestration data, not quantum state. The hardware example authors the Bell circuit from backend primitive gates: h is decomposed as rz(pi/2) => sx => rz(pi/2), and cnot as h(target) => cz => h(target). *.local.json files are ignored by git, so real credentials should live in examples/wire/quantum-ibm-runtime.local.json. The tracked template at ../../examples/wire/quantum-ibm-runtime.config.example.json shows the expected shape:

{
  "api_key_env": "QISKIT_IBM_API_KEY",
  "instance_crn": "REPLACE_WITH_IBM_QUANTUM_RUNTIME_SERVICE_CRN",
  "backend": "least_busy"
}

api_key_env keeps the API key in the environment. A local config may instead use an api_key field, but that file should remain untracked. instance_crn is the IBM Quantum Runtime service CRN used in the REST Service-CRN header. The default API base URL is https://quantum.cloud.ibm.com/api/v1; eu-de instances should use https://eu-de.quantum.cloud.ibm.com/api/v1.

Hardware submission is opt-in:

nix run .#wire-quantum-ibm-rest -- examples/wire/quantum-bell-state-ibm-rest.wire --shots 100 --confirm-hardware

Without --confirm-hardware, the runner refuses to submit. With --dry-run or --emit-request, it builds the OpenQASM 3 sampler request locally without requesting an IAM token and without queueing a job. backend = "least_busy" asks the runner to choose an online non-simulator backend with enough qubits; a concrete backend can be selected with the config backend field or the --backend flag.

Iterative Phase Estimation Runner

The wire-quantum-ipea app demonstrates adaptive phase estimation as a sequence of fresh Wire rounds. Each round compiles a new Wire graph, executes one quantum measurement, carries that measured bit back into classical Python state, and uses the accumulated bits to choose the next round’s feedback angle. This is adaptive circuit authoring, not hidden mid-circuit backend feedback.

Local simulation is the default:

nix run .#wire-quantum-ipea -- --shots 100 --seed 7

The default target phase is 5/8 with 3 measured bits, so an ideal run estimates bitstring 101. The generated round shape is represented by ../../examples/wire/quantum-ipea-round.wire. The Wire round uses rz, sx, x, and rzz executor nodes. For IBM Runtime REST, the OpenQASM lowerer expands rzz into rz/sx/cz operations because IBM’s current standard QASM loaders do not define an rzz gate name. The Wire file binds the round fragments with graph-valued lets:

let h_control =
  h_control_rz_a
    => h_control_sx
    => h_control_rz_b;

let controlled_phase =
  (phase_control_rz <> phase_target_rz)
    => phase_rzz;

let feedback_and_readout =
  feedback_control_rz
    => readout_h
    => measure_phase;

Dry-run mode compiles every generated round and can persist the generated Wire without simulation or provider submission:

nix run .#wire-quantum-ipea -- --dry-run --emit-round-wire /tmp/wire-ipea-rounds

Hardware execution uses the same ignored IBM Runtime config file as the Bell example and still requires explicit confirmation:

nix run .#wire-quantum-ipea -- --hardware --shots 100 --confirm-hardware

Because each round is a separate provider job, this demo spends hardware budget per measured phase bit. Use --dry-run first to inspect the generated rounds and OpenQASM 3.

Quantum Eraser Wire Experiment

../../examples/wire/quantum-eraser-experiment.wire is the source of truth for the full delayed-choice quantum eraser sweep. It is an executable Wire scaffold and a circuit catalog in one file: the nine hardware circuits are exported graph values, the pure planning node builds nine std.io.command leaves that select those values with wire build --return, the topology sequences the jobs, and a final pure node parses and summarizes the run with fromJson. The final report is printed and written to ./wire-quantum-eraser-report.txt through std.io.writeFile.

The example is a gate-model analogue of the delayed-choice quantum eraser, framed as a correlation experiment rather than retrocausality: the later marker-basis choice does not change the unconditional screen statistics. Interference is recovered only after sorting the results by the marker outcome.

The full scaffold is a hardware sweep and queues nine IBM Runtime sampler jobs:

nix run .#wire-quantum-eraser -- --confirm-hardware

The app runs the Wire file through wire run and places wire-quantum-ibm-rest on PATH for the command leaves. The command leaves request JSON output, and the final pure Wire analyzer renders a compact table framed around the three-circular-polarizer analogy: path marking flattens the unconditional screen, while eraser-basis marker slices recover complementary fringes. The experiment executes these exported graph values from the same Wire source:

  • open_phase_{0,1_4,1_2}
  • which_path_phase_{0,1_4,1_2}
  • eraser_phase_{0,1_4,1_2}

The source keeps graph-valued lets for the circuit fragments. For example, each eraser graph names the screen split/recombine fragments, the marker path-marking fragments, and the delayed eraser-basis readout:

let recombine_screen =
  recombine_rz_a
    => recombine_sx
    => recombine_rz_b
    => measure_screen;

let marker_eraser_readout =
  z_marker_eraser_rz_a
    => z_marker_eraser_sx
    => z_marker_eraser_rz_b
    => z_measure_marker;

To inspect an individual circuit before spending provider time, select the graph value through the IBM REST bridge in dry-run mode. Dry-run compiles the selected graph and emits the OpenQASM 3 request without provider submission:

nix run .#wire-quantum-ibm-rest -- examples/wire/quantum-eraser-experiment.wire --return eraser_phase_0 --dry-run --config examples/wire/quantum-ibm-runtime.config.example.json

Hardware execution of an individual row submits the selected Wire-authored circuit as one provider job:

nix run .#wire-quantum-ibm-rest -- examples/wire/quantum-eraser-experiment.wire --return eraser_phase_0 --shots 100 --confirm-hardware

Linearity Caveat

Current Wire admission validates typed ports and cardinality-one inputs, but it does not make a generic “one output may have only one consumer” guarantee. A real quantum host must reject illegal Qubit fan-out in its projection/admission layer or introduce a Cortex-level linear-resource decision before treating Qubit as a no-cloning resource.

Mid-circuit measurement with adaptive classical feedback is also a separate design question. It would need to state how measurement results authorize later topology, likely through the existing rewrite and select(...) surfaces rather than through hidden backend callbacks.