diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index 2690583bd575..7ed69cadfa12 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -553,7 +553,7 @@ def control_channel(self, qubits: Iterable[int]): ``(control_qubit, target_qubit)``. Returns: - List[ControlChannel]: The Qubit measurement acquisition line. + List[ControlChannel]: The multi qubit control line. Raises: NotImplementedError: if the backend doesn't support querying the diff --git a/qiskit/providers/fake_provider/fake_backend.py b/qiskit/providers/fake_provider/fake_backend.py index 5bae916970f0..7554f819dce5 100644 --- a/qiskit/providers/fake_provider/fake_backend.py +++ b/qiskit/providers/fake_provider/fake_backend.py @@ -17,10 +17,12 @@ """ import warnings +import collections import json import os +import re -from typing import List +from typing import List, Iterable from qiskit import circuit from qiskit.providers.models import BackendProperties @@ -84,6 +86,36 @@ def __init__(self): self._target = None self.sim = None + if "channels" in self._conf_dict: + self._parse_channels(self._conf_dict["channels"]) + + def _parse_channels(self, channels): + type_map = { + "acquire": pulse.AcquireChannel, + "drive": pulse.DriveChannel, + "measure": pulse.MeasureChannel, + "control": pulse.ControlChannel, + } + identifier_pattern = re.compile(r"\D+(?P\d+)") + + channels_map = { + "acquire": collections.defaultdict(list), + "drive": collections.defaultdict(list), + "measure": collections.defaultdict(list), + "control": collections.defaultdict(list), + } + for identifier, spec in channels.items(): + channel_type = spec["type"] + out = re.match(identifier_pattern, identifier) + if out is None: + # Identifier is not a valid channel name format + continue + channel_index = int(out.groupdict()["index"]) + qubit_index = tuple(spec["operates"]["qubits"]) + chan_obj = type_map[channel_type](channel_index) + channels_map[channel_type][qubit_index].append(chan_obj) + setattr(self, "channels_map", channels_map) + def _setup_sim(self): if _optionals.HAS_AER: from qiskit.providers import aer @@ -193,6 +225,73 @@ def meas_map(self) -> List[List[int]]: """ return self._conf_dict.get("meas_map") + def drive_channel(self, qubit: int): + """Return the drive channel for the given qubit. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Returns: + DriveChannel: The Qubit drive channel + """ + drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) + qubits = (qubit,) + if qubits in drive_channels_map: + return drive_channels_map[qubits][0] + return None + + def measure_channel(self, qubit: int): + """Return the measure stimulus channel for the given qubit. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Returns: + MeasureChannel: The Qubit measurement stimulus line + """ + measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) + qubits = (qubit,) + if qubits in measure_channels_map: + return measure_channels_map[qubits][0] + return None + + def acquire_channel(self, qubit: int): + """Return the acquisition channel for the given qubit. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Returns: + AcquireChannel: The Qubit measurement acquisition line. + """ + acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) + qubits = (qubit,) + if qubits in acquire_channels_map: + return acquire_channels_map[qubits][0] + return None + + def control_channel(self, qubits: Iterable[int]): + """Return the secondary drive channel for the given qubit + + This is typically utilized for controlling multiqubit interactions. + This channel is derived from other channels. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Args: + qubits: Tuple or list of qubits of the form + ``(control_qubit, target_qubit)``. + + Returns: + List[ControlChannel]: The multi qubit control line. + """ + control_channels_map = getattr(self, "channels_map", {}).get("control", {}) + qubits = tuple(qubits) + if qubits in control_channels_map: + return control_channels_map[qubits] + return [] + def run(self, run_input, **options): """Run on the fake backend using a simulator. diff --git a/releasenotes/notes/support-channels-in--fake-backend-v2-82f0650006495fbe.yaml b/releasenotes/notes/support-channels-in--fake-backend-v2-82f0650006495fbe.yaml new file mode 100644 index 000000000000..9d01cc969f05 --- /dev/null +++ b/releasenotes/notes/support-channels-in--fake-backend-v2-82f0650006495fbe.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + All fake backends in :mod:`qiskit.providers.fake_provider.backends` have been + updated to return the corresponding pulse channel objects with the method call of + :meth:`~BackendV2.drive_channel`, :meth:`~BackendV2.measure_channel`, + :meth:`~BackendV2.acquire_channel`, :meth:`~BackendV2.control_channel`. diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index 1be5c96950b3..016e6feb41b7 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -31,7 +31,9 @@ FakeBackendSimple, FakeBackendV2LegacyQubitProps, ) +from qiskit.providers.fake_provider.backends import FakeBogotaV2 from qiskit.quantum_info import Operator +from qiskit.pulse import channels @ddt @@ -190,3 +192,45 @@ def test_transpile_parse_inst_map(self): """Test that transpiler._parse_inst_map() supports BackendV2.""" inst_map = _parse_inst_map(inst_map=None, backend=self.backend) self.assertIsInstance(inst_map, InstructionScheduleMap) + + @data(0, 1, 2, 3, 4) + def test_drive_channel(self, qubit): + """Test getting drive channel with qubit index.""" + backend = FakeBogotaV2() + chan = backend.drive_channel(qubit) + ref = channels.DriveChannel(qubit) + self.assertEqual(chan, ref) + + @data(0, 1, 2, 3, 4) + def test_measure_channel(self, qubit): + """Test getting measure channel with qubit index.""" + backend = FakeBogotaV2() + chan = backend.measure_channel(qubit) + ref = channels.MeasureChannel(qubit) + self.assertEqual(chan, ref) + + @data(0, 1, 2, 3, 4) + def test_acquire_channel(self, qubit): + """Test getting acquire channel with qubit index.""" + backend = FakeBogotaV2() + chan = backend.acquire_channel(qubit) + ref = channels.AcquireChannel(qubit) + self.assertEqual(chan, ref) + + @data((4, 3), (3, 4), (3, 2), (2, 3), (1, 2), (2, 1), (1, 0), (0, 1)) + def test_control_channel(self, qubits): + """Test getting acquire channel with qubit index.""" + bogota_cr_channels_map = { + (4, 3): 7, + (3, 4): 6, + (3, 2): 5, + (2, 3): 4, + (1, 2): 2, + (2, 1): 3, + (1, 0): 1, + (0, 1): 0, + } + backend = FakeBogotaV2() + chan = backend.control_channel(qubits)[0] + ref = channels.ControlChannel(bogota_cr_channels_map[qubits]) + self.assertEqual(chan, ref)