Source code for qiskit_qm_provider.backend.qm_instruction_properties

# 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.

"""QMInstructionProperties: Qiskit instruction properties from QuAM pulse macros.

Author: Arthur Strauss
Date: 2026-02-08
"""

from __future__ import annotations

from typing import Callable
from copy import deepcopy

from qiskit.transpiler import InstructionProperties
from quam.core.macro import QuamMacro


[docs] class QMInstructionProperties(InstructionProperties): """Qiskit instruction properties enriched with a QUA pulse macro. ``qua_pulse_macro`` accepts two forms: * A :class:`~quam.core.macro.QuamMacro` instance -- a structured QuAM component whose attributes (``duration``, ``fidelity``, ``pulse``, …) are declared following the `QuAM documentation <https://qua-platform.github.io/quam/>`_. When this form is used, ``duration`` and ``error`` are automatically inferred from the macro's attributes unless explicitly provided. * A plain **callable** -- a bare QUA macro function with no QuAM attributes. In this case ``duration`` and ``error`` must be supplied explicitly if they are needed. The two forms are transparently unified through the ``qua_pulse_macro`` and ``quam_macro`` properties. """ def __new__( cls, duration=None, error=None, qua_pulse_macro=None, quam_macro=None, *args, **kwargs, ): macro = quam_macro if quam_macro is not None else qua_pulse_macro if macro is not None: if duration is None: duration = cls._infer_duration(macro) if error is None: fidelity = getattr(macro, "fidelity", None) if isinstance(fidelity, float): error = 1.0 - fidelity self = super().__new__(cls, duration=duration, error=error, *args, **kwargs) self._qua_pulse_macro = macro return self @staticmethod def _infer_duration(macro) -> float | None: """Try to derive a duration (in seconds) from macro attributes. Resolution order: 1. ``macro.duration`` -- used directly if it is numeric. 2. If ``macro.duration`` is a QuAM string reference (e.g. ``"#./inferred_duration"``), resolve via ``macro.inferred_duration``. 3. Fall back to ``macro.pulse.length * 1e-9`` when available. """ duration = getattr(macro, "duration", None) if isinstance(duration, str): try: duration = macro.inferred_duration except (ValueError, AttributeError): duration = None if duration is None: pulse = getattr(macro, "pulse", None) if pulse is not None: if isinstance(pulse, str): pulse = macro.qubit.get_pulse(pulse) duration = pulse.length * 1e-9 return duration def __init__( self, duration: float | None = None, error: float | None = None, qua_pulse_macro: Callable | QuamMacro | None = None, quam_macro: QuamMacro | None = None, ): """Create a new ``QMInstructionProperties``. Args: duration: Gate duration in seconds. Inferred from ``qua_pulse_macro`` when a :class:`~quam.core.macro.QuamMacro` is provided and this is left as ``None``. error: Gate error rate. Inferred as ``1 - fidelity`` from a :class:`~quam.core.macro.QuamMacro` when left as ``None``. qua_pulse_macro: Either a :class:`~quam.core.macro.QuamMacro` (structured QuAM component with declarative attributes) or a plain callable (bare QUA macro function). quam_macro: Explicit :class:`~quam.core.macro.QuamMacro` input. When both ``quam_macro`` and ``qua_pulse_macro`` are supplied, ``quam_macro`` takes priority and ``qua_pulse_macro`` is ignored. """ super().__init__() @property def qua_pulse_macro(self) -> Callable | None: """The QUA macro as a callable. If the underlying object is a :class:`~quam.core.macro.QuamMacro`, its ``.apply`` method is returned so that callers always receive a uniform callable interface. For a plain callable, it is returned as-is. """ if isinstance(self._qua_pulse_macro, QuamMacro): return self._qua_pulse_macro.apply return self._qua_pulse_macro @qua_pulse_macro.setter def qua_pulse_macro(self, value: Callable | QuamMacro | None): self._qua_pulse_macro = value @property def quam_macro(self) -> QuamMacro | None: """The underlying :class:`~quam.core.macro.QuamMacro`, or ``None``. Returns the original :class:`~quam.core.macro.QuamMacro` instance when one was provided, giving access to its full attribute set (``duration``, ``fidelity``, ``pulse``, etc.). Returns ``None`` when the macro was supplied as a plain callable. """ if isinstance(self._qua_pulse_macro, QuamMacro): return self._qua_pulse_macro return None @quam_macro.setter def quam_macro(self, value: QuamMacro | None): self._qua_pulse_macro = value def __repr__(self): return ( f"QMInstructionProperties(duration={self.duration}, " f"error={self.error}, " f"qua_pulse_macro={self._qua_pulse_macro})" ) def __getstate__(self): return (super().__getstate__(), self._qua_pulse_macro) def __setstate__(self, state: tuple): super().__setstate__(state[0]) self._qua_pulse_macro = state[1] def __deepcopy__(self, memo): """ Custom deepcopy that mirrors Qiskit's InstructionProperties semantics while keeping the same reference to the underlying macro. - The numeric fields (duration, error) are copied like a normal InstructionProperties instance. - The `_qua_pulse_macro` / `qua_pulse_macro` attribute is **not** deep-copied; the reference is shared between copies. This avoids attempting to deepcopy or pickle potentially non-picklable QuAM macros, while still allowing Qiskit to deepcopy Targets and their instruction-property maps. """ if id(self) in memo: return memo[id(self)] # Recreate via __new__ so that the Rust-side base state is initialized correctly, # but pass through the same macro object by reference. cls = self.__class__ result = cls.__new__( cls, duration=deepcopy(self.duration, memo), error=deepcopy(self.error, memo), qua_pulse_macro=self._qua_pulse_macro, ) memo[id(self)] = result return result