Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add logic to return channels to FakeBackendV2 #8444

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion qiskit/providers/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
101 changes: 100 additions & 1 deletion qiskit/providers/fake_provider/fake_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<index>\d+)")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that regex time here will be a big deal but do you think it'll be worthwhile to move this to a class level variable so we don't have to have the compile time on each instantiation of a FakeBackendV2?


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
Expand Down Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
@@ -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`.
44 changes: 44 additions & 0 deletions test/python/providers/test_backend_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)