Source code for qiskit_qm_provider.backend.measurement_field
# Copyright 2026 Arthur Strauss
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Compiler-owned measurement output fields wired from qm-qasm compilation results."""
from __future__ import annotations
import weakref
from typing import TYPE_CHECKING, Any, Optional
from qm.qua import assign, declare, declare_stream
from qm.qua.type_hints import QuaScalar
from ..parameter_table._scope import require_qua_program
from .backend_utils import pack_register_to_int
if TYPE_CHECKING:
from qm_qasm import CompilationResult
[docs]
class MeasurementRegisterField:
"""One classical register (or loose bit) wired from a qm-qasm compilation result.
Measurement fields live in a **separate namespace** from runtime
:class:`~qiskit_qm_provider.parameter_table.Parameter` objects registered in
:class:`~qiskit_qm_provider.parameter_table.ParameterPool`. The same string name
(e.g. matching a creg and an input struct field) may refer to both a runtime knob
and a measurement output; they are distinct Python objects with distinct QUA variables.
"""
def __init__(
self,
name: str,
length: int,
*,
parent: Optional[Any] = None,
):
"""Create a measurement field handle before wiring from a compilation result.
Mirrors the :class:`~qiskit_qm_provider.parameter_table.Parameter` shape convention:
a multi-/single-bit classical register is an array (``length`` = register width),
while a loose clbit is a scalar (``length == 0``).
Args:
name: Output key in ``result_program`` (creg name, ``_bitN``, or other
compiler output key).
length: Number of elements — ``0`` for a scalar output (loose clbit),
otherwise the classical register width. Drives :attr:`is_array`.
parent: Optional owning :class:`~qiskit_qm_provider.backend.qua_circuit_compilation.QuaCircuitCompilation` (stored as weakref).
"""
self.name = name
self._length = length
self._parent = weakref.ref(parent) if parent is not None else None
self._var = None
self._is_declared = False
self._state_int_var: Optional[QuaScalar[int]] = None
self._should_reset_state_int = False
self._stream = None
self._wired_program_key: Optional[str] = None
self._wired_compilation_uuid: Optional[str] = None
@property
def is_measurement_output(self) -> bool:
"""``True`` — sentinel used to reject attachment to runtime ``ParameterTable``\\ s."""
return True
@property
def is_declared(self) -> bool:
"""Whether :meth:`_wire_from_result` has bound a compiler-owned QUA variable."""
return self._is_declared
@property
def is_array(self) -> bool:
"""``True`` if this field holds a QUA array (``length > 0``), ``False`` for a scalar.
Same convention as :attr:`~qiskit_qm_provider.parameter_table.Parameter.is_array`;
part of the shared :class:`~qiskit_qm_provider.parameter_table._mixins.TableFieldProtocol`.
"""
return self._length > 0
@property
def length(self) -> int:
"""Number of elements, ``Parameter`` convention: ``0`` for a scalar output (loose
clbit), otherwise the classical register width."""
return self._length
@property
def var(self):
"""Compiler-owned QUA bool scalar or array for this measurement output.
Must be accessed inside ``with program():``. Use
:meth:`~qiskit_qm_provider.backend.qua_circuit_compilation.MeasurementOutcomeTable.get_parameter`
for the Python handle.
"""
require_qua_program("MeasurementRegisterField.var")
if not self._is_declared:
raise ValueError(
f"Measurement field {self.name!r} is not wired yet. "
f"Compile the circuit with quantum_circuit_to_qua first."
)
return self._var
def _wire_from_result(
self,
result: "CompilationResult",
program_key: str,
*,
length: Optional[int] = None,
) -> None:
"""Bind compiler-owned QUA variables from ``result.result_program``.
Called during compilation wiring, not by user code. Invalidates cached
:attr:`state_int` and :attr:`stream` handles when :attr:`length` or compilation
identity changes.
Args:
result: qm-qasm compilation result exposing ``result_program[key]``.
program_key: Key in ``result.result_program`` (creg name or ``_bitN``).
length: Override element count; defaults to the value from ``__init__``.
"""
new_length = length if length is not None else self._length
compilation_uuid = getattr(result, "uuid", None)
if new_length != self._length:
self._length = new_length
self._state_int_var = None
self._stream = None
elif self._wired_program_key != program_key or self._wired_compilation_uuid != compilation_uuid:
self._state_int_var = None
self._stream = None
self._var = result.result_program[program_key]
self._is_declared = True
self._wired_program_key = program_key
self._wired_compilation_uuid = compilation_uuid
if self._state_int_var is not None:
self._should_reset_state_int = True
@property
def state_int(self) -> QuaScalar[int]:
"""Lazy-packed ``int`` QUA variable (LSB = bit index 0).
Must be accessed inside ``with program():``. Declared (and cached) on first
access; nothing is materialized unless you read it. Prefer bulk access via
:attr:`~qiskit_qm_provider.backend.qua_circuit_compilation.MeasurementOutcomeTable.state_ints`
when wiring multiple registers.
"""
require_qua_program("MeasurementRegisterField.state_int")
return self._materialize_state_int()
@property
def stream(self):
"""Output stream for ``save(..., stream)`` / ``stream_processing()`` on the host.
Declared lazily on first access inside ``with program():``.
"""
require_qua_program("MeasurementRegisterField.stream")
if self._stream is None:
self._stream = declare_stream()
return self._stream
def _materialize_state_int(self) -> QuaScalar[int]:
"""Declare (once) and assign the packed integer from :attr:`var`.
``pack_register_to_int`` inspects the wired QUA variable to choose array vs scalar
packing; a scalar output (``length == 0``) packs its single bit.
"""
packed = pack_register_to_int(self._var, max(self._length, 1))
if self._state_int_var is None:
self._state_int_var = declare(int)
assign(self._state_int_var, packed)
elif self._should_reset_state_int:
assign(self._state_int_var, packed)
self._should_reset_state_int = False
return self._state_int_var
def __deepcopy__(self, memo):
"""Refuse duplication — handles are tied to a single compilation wiring."""
raise TypeError("MeasurementRegisterField handles are compilation-owned and cannot be deepcopied.")