Measurement outputs (comp.outputs)¶
Compiled-circuit measurement handles are compilation-local: they wire classical register (and loose clbit) outcomes from qm-qasm’s result_program into QUA variables. They are not runtime/OPNIC deployment parameters.
API reference (autodoc): QuaCircuitCompilation, MeasurementOutcomeTable, MeasurementRegisterField, QuaFieldTable, and scope guards.
See also: Workflows — hybrid programs.
Locality model¶
Concept |
Runtime |
|
|---|---|---|
Scope |
Process/session; registered in |
Per |
Purpose |
Host↔OPX knobs, OPNIC structs, input streams |
Compiler output handles wired from |
Typical classical path |
|
|
Name keys |
User-chosen struct/table/field names |
Circuit output keys (creg names + |
Key guidance: matching an OPNIC struct field name to a creg name is optional and usually unnecessary. Same string is allowed (dual namespace) but does not imply the same QUA variable. Use ParameterPool.lookup_runtime_parameter(name) for runtime knobs; use comp.outputs.get_parameter(name) for measurement fields.
Accessor contract (aligned with ParameterTable)¶
All QUA variable accessors require with program():.
Access |
Returns |
|---|---|
|
QUA bool var or array (measurement outcome) |
|
|
|
QUA var (same as |
|
Lazy-packed |
|
Per-field |
Breaking change: comp.outputs.c.state_int is invalid — comp.outputs.c is the measurement bool var. Use comp.outputs.state_ints["c"] or comp.outputs.get_parameter("c").state_int.
Worked examples¶
RL reward stream¶
from qm.qua import program, save
with program() as prog:
comp = backend.quantum_circuit_to_qua(reward_circuit)
save(comp.outputs.state_ints["meas"], comp.outputs.streams["meas"])
Host-side RL typically consumes the packed integer stream, not raw bool arrays. Input struct fields (actions) and output streams (rewards) are separate pipelines.
QEC — in-QUA processing first¶
For error correction, the OPNIC struct you stream_back() is usually not the same object as the raw measurement register from the circuit. Compiler outputs (comp.outputs) hold discriminated bits from result_program; runtime ParameterTable fields hold whatever derived classical data the host decoder needs — detection events, packed syndrome integers, histogram bins, etc. See the Error-correction guide for full walkthroughs.
Minimal pattern — derive before streaming:
from qm.qua import program, assign, declare
with program() as prog:
comp = backend.quantum_circuit_to_qua(syndrome_circuit)
syndrome = comp.outputs["syndrome"] # bool array var — raw readout
detection_events = declare(int)
# ... QUA logic combining syndrome bits into detection_events ...
save(detection_events, comp.outputs.streams["syndrome"])
Recommended pattern — detection events via consecutive-round XOR:
Many decoders expect per-bit changes between rounds, not absolute stabilizer values. Declare runtime tables for staging and streaming, XOR in QUA, and stream only the derived table:
from qm.qua import declare, for_, assign
def update_syndrome_streams(
circuit,
comp,
previous_measurement_outcomes: ParameterTable, # local history table
syndrome_data: ParameterTable, # OPNIC / stream transport
):
"""Update syndrome streams for a given circuit."""
j = declare(int)
for creg in circuit.cregs:
meas_reg = comp.outputs[creg.name]
syndrome_param = syndrome_data[creg.name]
prev_meas = previous_measurement_outcomes[creg.name]
with for_(j, 0, j < creg.size, j + 1):
assign(
syndrome_param[j],
prev_meas[j] ^ meas_reg[j],
)
assign(prev_meas[j], meas_reg[j])
syndrome_data.stream_back(reset=True)
After each comp = backend.quantum_circuit_to_qua(syndrome_circuit), pass comp to update_syndrome_streams(...). The host receives detection events from syndrome_data, not raw comp.outputs bits.
Large registers — prefer state_int for streaming:
For registers with many bits, avoid buffering full bool chains on the stream path. Use the lazy-packed integer instead:
from qm.qua import program, assign
with program() as prog:
comp = backend.quantum_circuit_to_qua(syndrome_circuit)
ancilla = syndrome_circuit.cregs[0].name
assign(syndrome_data.var, comp.outputs.state_ints[ancilla])
syndrome_data.stream_back(reset=True)
state_int collapses creg.size bits into one scalar — useful for host decoders, lookup tables, and stream_processing() buffers of size 2**creg.size. Keep per-bit bool access when you need XOR or single-stabilizer feedback; switch to state_int when the outcome is consumed as a single label. Details: Error-correction — detection events and state_int.
QEC workflows process syndrome bits in QUA before streaming a derived quantity — no 1:1 creg→OPNIC struct mapping is required.
Optional bridge to OPNIC (explicit only)¶
When the host must receive data via an OPNIC struct rather than a raw stream:
with program() as prog:
comp = backend.quantum_circuit_to_qua(qc)
reward_table.assign({"detection_events": comp.outputs.state_ints["syndrome"]})
reward_table.stream_back()
Never automatic by name — always an explicit assign.
Re-compile and lifecycle¶
Each
quantum_circuit_to_quacall creates a newQuaCircuitCompilationwith freshMeasurementRegisterFieldobjects.comp.rewire_outputs(qc, new_result)refreshes wiring on the same wrapper; size or compilation identity changes invalidate cachedstate_int/streamhandles.
Future extensibility¶
Today, output keys mirror classical registers (and loose clbits) because that is what Qiskit’s OpenQASM 3 exporter emits — it can only export classical bits as output declarations. qm-qasm already supports the OpenQASM 3 output command more broadly (including non-creg types), so the bottleneck is on the Qiskit side. Once Qiskit’s exporter gains support for richer output types, comp.outputs will surface whatever keys qm-qasm exposes — the user will need to know the expected QUA type for each output key. No changes to the compiler or this provider will be required at that point.
Legacy get_measurement_outcomes remains available; it uses get_parameter() internally and accepts QuaCircuitCompilation or raw CompilationResult.