Source code for qiskit_qm_provider.backend.flux_tunable_transmon_backend

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

"""Flux-tunable transmon backend: QMBackend built from QuAM (flux-tunable) machine config.

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

from __future__ import annotations

from .qm_backend import QMBackend, requires_qiskit_pulse
from typing import Iterable, Optional, List, Union, TYPE_CHECKING, Tuple

if TYPE_CHECKING:
    from qm_qasm import QubitsMapping
    from quam_builder.architecture.superconducting.qpu.flux_tunable_quam import (
        FluxTunableQuam as Quam,
    )
    from quam_builder.architecture.superconducting.qubit.flux_tunable_transmon import (
        FluxTunableTransmon as Transmon,
    )
    from quam_builder.architecture.superconducting.qubit_pair.flux_tunable_transmon_pair import (
        FluxTunableTransmonPair as TransmonPair,
    )
    from qm import QuantumMachinesManager
    from qiskit.pulse import ControlChannel


[docs] class FluxTunableTransmonBackend(QMBackend): """Reference :class:`~.QMBackend` for flux-tunable transmon QuAM machines. Maps QuAM qubits and pairs to Qiskit Pulse channels when Pulse is available and is the default backend type returned by :class:`~qiskit_qm_provider.providers.IQCCProvider`. """ def __init__( self, machine: Quam, qmm: Optional[QuantumMachinesManager] = None, name: Optional[str] = None, **fields, ): """Initialize the flux-tunable transmon backend. Args: machine: QuAM instance with ``qubits`` and ``qubit_pairs``. qmm: Optional ``QuantumMachinesManager`` (cloud simulator or IQCC). name: Optional backend name. fields: Optional keyword overrides for default run options (same keys as :class:`~.QMBackend`), including ``max_circuits`` (see :attr:`~.QMBackend.max_circuits`). """ if not hasattr(machine, "qubits") or not hasattr(machine, "qubit_pairs"): raise ValueError("Invalid QuAM instance provided, should have qubits and qubit_pairs attributes") try: from qiskit.pulse import DriveChannel, MeasureChannel, ControlChannel drive_channel_mapping = {DriveChannel(i): qubit.xy for i, qubit in enumerate(machine.active_qubits)} flux_channel_mapping = {ControlChannel(i): qubit.z for i, qubit in enumerate(machine.active_qubits)} readout_channel_mapping = { MeasureChannel(i): qubit.resonator for i, qubit in enumerate(machine.active_qubits) } control_channel_mapping = { ControlChannel(i + len(machine.active_qubits)): qubit_pair.coupler for i, qubit_pair in enumerate(machine.active_qubit_pairs) if qubit_pair.coupler is not None } channel_mapping = { **drive_channel_mapping, **flux_channel_mapping, **control_channel_mapping, **readout_channel_mapping, } except ImportError: import logging logging.info("qiskit.pulse is not available, channel mapping will not be set.") channel_mapping = {} super().__init__( machine, channel_mapping=channel_mapping, init_macro=machine.initialize_qpu, qmm=qmm, name=name, **fields, ) @property def qubit_mapping(self) -> QubitsMapping: """ Retrieve the qubit to quantum elements mapping for the backend. """ mapping = {} for i, qubit in enumerate(self.machine.active_qubits): channels = [ch.name for ch in qubit.channels.values()] name = qubit.name line_number = name[1] if hasattr(self.machine, "twpas"): for twpa in self.machine.twpas.values(): if hasattr(twpa, "pump") and twpa.name[-1] == line_number: channels.append(twpa.pump.name) mapping[i] = tuple(channels) return mapping @property def meas_map(self) -> List[List[int]]: """ Retrieve the measurement map for the backend. """ return [[i] for i in range(len(self.machine.active_qubits))] @requires_qiskit_pulse def control_channel(self, qubits: Iterable[int]) -> List[ControlChannel]: """ Return the secondary drive channel for the given qubit/qubit pair. If the qubits are a qubit pair, return the control channel for the qubit pair (tunable coupler). If the qubits are a single qubit, return the control channel for the qubit (flux) Args: qubits: Tuple or list of qubits of the form ``(control_qubit, target_qubit)``. Returns: List[ControlChannel]: The multi qubit control line. Raises: NotImplementedError: if the backend doesn't support querying the measurement mapping """ qubits = tuple(qubits) if len(qubits) > 2 or len(qubits) < 1: raise ValueError("Control channel should be defined for a qubit pair or a single qubit.") if len(qubits) == 2: qubit_pair = self.get_qubit_pair(qubits) if qubit_pair.coupler is not None: return [self.get_pulse_channel(qubit_pair.coupler)] else: return [] # no control channel for the qubit pair elif len(qubits) == 1: qubit = self.get_qubit(qubits[0]) if qubit.z is not None: return [self.get_pulse_channel(qubit.z)] else: return [] # no control channel for the qubit @property def qubits(self) -> List[Transmon]: """ Retrieve the list of Transmon qubits in the backend. """ return super().qubits
[docs] def get_qubit(self, qubit: Union[int, str]) -> Transmon: """ Retrieve a Transmon qubit by its index or name. Args: qubit: The index or name of the qubit to retrieve. Returns: Transmon: The Transmon qubit object. """ return super().get_qubit(qubit)
[docs] def get_qubit_pair(self, qubits: Tuple[int | str | Transmon, int | str | Transmon]) -> TransmonPair: """ Retrieve a Transmon pair by its indices or names. Args: qubits: A tuple containing the indices or names of the qubits in the pair. Returns: QubitPair: The Transmon pair object. """ return super().get_qubit_pair(qubits)