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

InstructionDurations from BackendV2 #11528

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 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
52 changes: 30 additions & 22 deletions qiskit/transpiler/instruction_durations.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from qiskit.circuit import Barrier, Delay
from qiskit.circuit import Instruction, ParameterExpression
from qiskit.circuit.duration import duration_in_dt
from qiskit.providers import Backend
from qiskit.providers import Backend, BackendV1, BackendV2
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.utils.units import apply_prefix

Expand Down Expand Up @@ -71,30 +71,38 @@ def from_backend(cls, backend: Backend):

Returns:
InstructionDurations: The InstructionDurations constructed from backend.

Raises:
TranspilerError: If dt and dtm is different in the backend.
"""

# All durations in seconds in gate_length
instruction_durations = []
backend_properties = backend.properties()
if hasattr(backend_properties, "_gates"):
for gate, insts in backend_properties._gates.items():
for qubits, props in insts.items():
if "gate_length" in props:
gate_length = props["gate_length"][0] # Throw away datetime at index 1
instruction_durations.append((gate, qubits, gate_length, "s"))
for q, props in backend.properties()._qubits.items():
if "readout_length" in props:
readout_length = props["readout_length"][0] # Throw away datetime at index 1
instruction_durations.append(("measure", [q], readout_length, "s"))

try:
dt = backend.configuration().dt
except AttributeError:
dt = None

return cls(instruction_durations, dt=dt)
return_durations = None

# Logic to handle if backend is sub-class of old BackendV1
if isinstance(backend, BackendV1):
backend_properties = backend.properties()
if hasattr(backend_properties, "_gates"):
for gate, insts in backend_properties._gates.items():
for qubits, props in insts.items():
if "gate_length" in props:
gate_length = props["gate_length"][0] # Ignore datetime at index 1
instruction_durations.append((gate, qubits, gate_length, "s"))
for q, props in backend.properties()._qubits.items():
if "readout_length" in props:
readout_length = props["readout_length"][0] # Ignore datetime at index 1
instruction_durations.append(("measure", [q], readout_length, "s"))

try:
dt = backend.configuration().dt
except AttributeError:
dt = None

return_durations = cls(instruction_durations, dt=dt)

# Logic to handle if backend is sub-class is BackendV2
elif isinstance(backend, BackendV2):
return_durations = backend.target.durations()

return return_durations

def update(self, inst_durations: "InstructionDurationsType" | None, dt: float = None):
"""Update self with inst_durations (inst_durations overwrite self).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
features:
- |
Support for :class:`.BackendV2` in :meth:`.InstructionDurations.from_backend` is added.

Users can have an object of :class:`.InstructionDurations` using :class:`.BackendV2`
from :meth:`.from_backend` with followig code.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
from :meth:`.from_backend` with followig code.
from :meth:`.InstructionDurations.from_backend` with followig code.

to fix doc error


.. code-block:: python

from qiskit.providers.fake_provider import FakePerth
from qiskit.transpiler import InstructionDurations
backendV2 = FakePerth()

inst_dur = InstructionDurations.from_backend(backendV2)
139 changes: 108 additions & 31 deletions test/python/transpiler/test_instruction_durations.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
# pylint: disable=missing-function-docstring

"""Test InstructionDurations class."""

from copy import deepcopy
from qiskit.circuit import Delay, Parameter
from qiskit.providers.fake_provider import Fake27QPulseV1
from qiskit.providers.fake_provider import Fake7QPulseV1, GenericBackendV2
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.instruction_durations import InstructionDurations
from test import QiskitTestCase # pylint: disable=wrong-import-order
Expand All @@ -35,42 +35,13 @@ def test_fail_if_invalid_dict_is_supplied_when_construction(self):
with self.assertRaises(TranspilerError):
InstructionDurations(invalid_dic)

def test_from_backend_for_backend_with_dt(self):
backend = Fake27QPulseV1()
gate = self._find_gate_with_length(backend)
durations = InstructionDurations.from_backend(backend)
self.assertGreater(durations.dt, 0)
self.assertGreater(durations.get(gate, 0), 0)

def test_from_backend_for_backend_without_dt(self):
backend = Fake27QPulseV1()
delattr(backend.configuration(), "dt")
gate = self._find_gate_with_length(backend)
durations = InstructionDurations.from_backend(backend)
self.assertIsNone(durations.dt)
self.assertGreater(durations.get(gate, 0, "s"), 0)
with self.assertRaises(TranspilerError):
durations.get(gate, 0)

def test_update_with_parameters(self):
durations = InstructionDurations(
[("rzx", (0, 1), 150, (0.5,)), ("rzx", (0, 1), 300, (1.0,))]
)

self.assertEqual(durations.get("rzx", [0, 1], parameters=[0.5]), 150)
self.assertEqual(durations.get("rzx", [0, 1], parameters=[1.0]), 300)

def _find_gate_with_length(self, backend):
"""Find a gate that has gate length."""
props = backend.properties()
for gate in props.gates:
try:
if props.gate_length(gate.gate, 0):
return gate.gate
except Exception: # pylint: disable=broad-except
pass
raise ValueError("Unable to find a gate with gate length.")

def test_can_get_unbounded_duration_without_unit_conversion(self):
param = Parameter("t")
parameterized_delay = Delay(param, "dt")
Expand All @@ -88,3 +59,109 @@ def test_fail_if_get_unbounded_duration_with_unit_conversion_when_dt_is_not_prov
parameterized_delay = Delay(param, "s")
with self.assertRaises(TranspilerError):
InstructionDurations().get(parameterized_delay, 0)


class TestInstrctionDurationsFromBackendV1(QiskitTestCase):
"""Test :meth:`~.from_backend` of :class:`.InstructionDurations` with
:class:`.BackendV1`"""

def setUp(self):
super().setUp()

self.backend = Fake7QPulseV1()
self.example_qubit = (0,)
self.example_gate = "x"

# Setting dt for the copy of backend to be None
self.backend_cpy = deepcopy(self.backend)
self.backend_cpy.configuration().dt = None

def test_backend_dt_equals_inst_dur_dt(self):
durations = InstructionDurations.from_backend(self.backend)
self.assertEqual(durations.dt, self.backend.configuration().dt)

def test_backend_gate_length_equals_inst_dur(self):
durations = InstructionDurations.from_backend(self.backend)
inst_dur_duration = durations.get(self.example_gate, self.example_qubit[0], "s")
backend_inst_dur = self.backend.properties().gate_length(
gate=self.example_gate, qubits=self.example_qubit
)
self.assertEqual(inst_dur_duration, backend_inst_dur)

def test_backend_without_dt_sets_inst_dur_None(self):
durations = InstructionDurations.from_backend(self.backend_cpy)
self.assertIsNone(durations.dt)

def test_get_dur_s_with_dt_None(self):
durations = InstructionDurations.from_backend(self.backend_cpy)
self.assertEqual(
durations.get(self.example_gate, self.example_qubit[0], "s"), 3.5555555555555554e-08
)

def test_raise_dur_get_dt_with_backend_dt_None(self):
durations = InstructionDurations.from_backend(self.backend_cpy)
with self.assertRaises(TranspilerError):
durations.get(self.example_gate, self.example_qubit[0])

def test_works_unequal_dt_dtm(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of this test? InstructionDuration is the object used for scheduling, and dtm doesn't matter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nkanazawa1989 , This line was the motivation:

TranspilerError: If dt and dtm is different in the backend.

Am I missing something

self.backend_cpy.configuration().dt = 1.0

# This is expcted to fail
InstructionDurations.from_backend(self.backend_cpy)

# Check if dt and dtm were indeed unequal
self.assertNotEqual(self.backend_cpy.configuration().dtm, 1.0)


class TestInstrctionDurationsFromBackendV2(QiskitTestCase):
"""Test :meth:`~.from_backend` of :class:`.InstructionDurations` with
:class:`.BackendV2`"""

def setUp(self):
super().setUp()

self.backend = GenericBackendV2(num_qubits=7, calibrate_instructions=True, seed=1450)
self.example_gate = "x"
self.example_qubit = (0,)

# Setting dt for the copy for BackendV2 to None
self.backend_cpy = deepcopy(self.backend)
self.backend_cpy.target.dt = None

def test_backend_dt_equals_inst_dur_dt(self):
durations = InstructionDurations.from_backend(self.backend)
self.assertEqual(durations.dt, self.backend.dt)

def test_backend_gate_length_equals_inst_dur(self):
durations = InstructionDurations.from_backend(self.backend)
inst_dur_duration = durations.get(
inst=self.example_gate, qubits=self.example_qubit[0], unit="s"
)
backend_inst_dur = self.backend.target._gate_map[self.example_gate][
self.example_qubit
].duration
self.assertEqual(inst_dur_duration, backend_inst_dur)

def test_backend_without_dt_sets_inst_dur_None(self):
durations = InstructionDurations.from_backend(self.backend_cpy)
self.assertIsNone(durations.dt)

def test_get_dur_s_with_dt_None(self):
durations = InstructionDurations.from_backend(self.backend_cpy)
self.assertEqual(
durations.get(self.example_gate, self.example_qubit[0], "s"), 4.0147038772116484e-08
)

def test_raise_dur_get_dt_with_backend_dt_None(self):
durations = InstructionDurations.from_backend(self.backend_cpy)
with self.assertRaises(TranspilerError):
durations.get(self.example_gate, self.example_qubit[0])

def test_works_unequal_dt_dtm(self):
self.backend_cpy.target.dt = 1.0

# This is expcted to fail
InstructionDurations.from_backend(self.backend_cpy)

# Check if dt and dtm were indeed unequal
self.assertNotEqual(self.backend_cpy.dtm, 1.0)
Loading