Skip to content

Commit

Permalink
Update transpile() to convert BackendV1 inputs to BackendV2 wit…
Browse files Browse the repository at this point in the history
…h `BackendV2Converter` (#11996)

* Convert to V2 inside transpile, deal with small changes in tests.

* Fix lint

* Add reno and apply comment from Ray's review
  • Loading branch information
ElePT committed Apr 25, 2024
1 parent f15d758 commit f04eaab
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 193 deletions.
58 changes: 17 additions & 41 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from qiskit.circuit.quantumregister import Qubit
from qiskit.dagcircuit import DAGCircuit
from qiskit.providers.backend import Backend
from qiskit.providers.backend_compat import BackendV2Converter
from qiskit.providers.models import BackendProperties
from qiskit.pulse import Schedule, InstructionScheduleMap
from qiskit.transpiler import Layout, CouplingMap, PropertySet
Expand Down Expand Up @@ -316,6 +317,12 @@ def callback_func(**kwargs):
config = user_config.get_config()
optimization_level = config.get("transpile_optimization_level", 1)

if backend is not None and getattr(backend, "version", 0) <= 1:
# This is a temporary conversion step to allow for a smoother transition
# to a fully target-based transpiler pipeline while maintaining the behavior
# of `transpile` with BackendV1 inputs.
backend = BackendV2Converter(backend)

if (
scheduling_method is not None
and backend is None
Expand Down Expand Up @@ -471,14 +478,8 @@ def _check_circuits_coupling_map(circuits, cmap, backend):
if cmap is not None:
max_qubits = cmap.size()
elif backend is not None:
backend_version = getattr(backend, "version", 0)
if backend_version <= 1:
if not backend.configuration().simulator:
max_qubits = backend.configuration().n_qubits
else:
max_qubits = None
else:
max_qubits = backend.num_qubits
max_qubits = backend.num_qubits

for circuit in circuits:
# If coupling_map is not None or num_qubits == 1
num_qubits = len(circuit.qubits)
Expand All @@ -496,27 +497,15 @@ def _log_transpile_time(start_time, end_time):

def _parse_inst_map(inst_map, backend):
# try getting inst_map from user, else backend
if inst_map is None:
backend_version = getattr(backend, "version", 0)
if backend_version <= 1:
if hasattr(backend, "defaults"):
inst_map = getattr(backend.defaults(), "instruction_schedule_map", None)
else:
inst_map = backend.target.instruction_schedule_map()
if inst_map is None and backend is not None:
inst_map = backend.target.instruction_schedule_map()
return inst_map


def _parse_coupling_map(coupling_map, backend):
# try getting coupling_map from user, else backend
if coupling_map is None:
backend_version = getattr(backend, "version", 0)
if backend_version <= 1:
if getattr(backend, "configuration", None):
configuration = backend.configuration()
if hasattr(configuration, "coupling_map") and configuration.coupling_map:
coupling_map = CouplingMap(configuration.coupling_map)
else:
coupling_map = backend.coupling_map
if coupling_map is None and backend is not None:
coupling_map = backend.coupling_map

# coupling_map could be None, or a list of lists, e.g. [[0, 1], [2, 1]]
if coupling_map is None or isinstance(coupling_map, CouplingMap):
Expand Down Expand Up @@ -553,14 +542,8 @@ def _parse_instruction_durations(backend, inst_durations, dt, circuit):
take precedence over backend durations, but be superceded by ``inst_duration``s.
"""
if not inst_durations:
backend_version = getattr(backend, "version", 0)
if backend_version <= 1:
backend_durations = InstructionDurations()
try:
backend_durations = InstructionDurations.from_backend(backend)
except AttributeError:
pass
else:
backend_durations = InstructionDurations()
if backend is not None:
backend_durations = backend.instruction_durations

circ_durations = InstructionDurations()
Expand Down Expand Up @@ -629,13 +612,6 @@ def _parse_timing_constraints(backend, timing_constraints):
return timing_constraints
if backend is None and timing_constraints is None:
timing_constraints = TimingConstraints()
else:
backend_version = getattr(backend, "version", 0)
if backend_version <= 1:
if timing_constraints is None:
# get constraints from backend
timing_constraints = getattr(backend.configuration(), "timing_constraints", {})
timing_constraints = TimingConstraints(**timing_constraints)
else:
timing_constraints = backend.target.timing_constraints()
elif backend is not None:
timing_constraints = backend.target.timing_constraints()
return timing_constraints
18 changes: 8 additions & 10 deletions qiskit/providers/backend_compat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
# (C) Copyright IBM 2020, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -57,7 +57,7 @@ def convert_to_target(
A ``Target`` instance.
"""

# importing pacakges where they are needed, to avoid cyclic-import.
# importing packages where they are needed, to avoid cyclic-import.
# pylint: disable=cyclic-import
from qiskit.transpiler.target import (
Target,
Expand All @@ -82,7 +82,7 @@ def convert_to_target(
"switch_case": SwitchCaseOp,
}

in_data = {"num_qubits": configuration.n_qubits}
in_data = {"num_qubits": configuration.num_qubits}

# Parse global configuration properties
if hasattr(configuration, "dt"):
Expand All @@ -97,7 +97,6 @@ def convert_to_target(
all_instructions = set.union(
basis_gates, set(required), supported_instructions.intersection(CONTROL_FLOW_OP_NAMES)
)

inst_name_map = {} # type: Dict[str, Instruction]

faulty_ops = set()
Expand Down Expand Up @@ -244,10 +243,8 @@ def _get_value(prop_dict, prop_name):

for name in inst_sched_map.instructions:
for qubits in inst_sched_map.qubits_with_instruction(name):

if not isinstance(qubits, tuple):
qubits = (qubits,)

if (
name not in all_instructions
or name not in prop_name_map
Expand All @@ -267,17 +264,18 @@ def _get_value(prop_dict, prop_name):
continue

entry = inst_sched_map._get_calibration_entry(name, qubits)

try:
prop_name_map[name][qubits].calibration = entry
except AttributeError:
# if instruction properties are "None", add entry
prop_name_map[name].update({qubits: InstructionProperties(None, None, entry)})
logger.info(
"The PulseDefaults payload received contains an instruction %s on "
"qubits %s which is not present in the configuration or properties payload.",
"qubits %s which is not present in the configuration or properties payload."
"A new properties entry will be added to include the new calibration data.",
name,
qubits,
)

# Add parsed properties to target
target = Target(**in_data)
for inst_name in all_instructions:
Expand Down Expand Up @@ -384,7 +382,7 @@ def __init__(
super().__init__(
provider=backend.provider,
name=backend.name(),
description=self._config.description,
description=getattr(self._config, "description", None),
online_date=getattr(self._config, "online_date", None),
backend_version=self._config.backend_version,
)
Expand Down
2 changes: 1 addition & 1 deletion qiskit/providers/fake_provider/fake_1q.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self):
configuration = BackendProperties(
backend_name="fake_1q",
backend_version="0.0.0",
n_qubits=1,
num_qubits=1,
basis_gates=["u1", "u2", "u3", "cx"],
simulator=False,
local=True,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
upgrade:
- |
The :func:`.transpile` function has been upgraded to internally convert
`backend` inputs of type :class:`.BackendV1` to :class:`.BackendV2`,
which allows the transpilation pipeline to now access the backend
constraints through a :class:`.Target`. This change does not require any
user action.
9 changes: 4 additions & 5 deletions test/python/transpiler/test_1q.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2019.
# (C) Copyright IBM 2019, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -17,6 +17,7 @@
from qiskit import QuantumCircuit
from qiskit.compiler import transpile
from qiskit.providers.fake_provider import Fake1Q
from qiskit.providers.basic_provider import BasicSimulator
from qiskit.transpiler import TranspilerError
from test import combine # pylint: disable=wrong-import-order
from test import QiskitTestCase # pylint: disable=wrong-import-order
Expand Down Expand Up @@ -76,9 +77,7 @@ def test_device(self, circuit, level):
name="{circuit.__name__}_level{level}_valid",
)
def test_simulator(self, circuit, level):
"""All the levels with all the 1Q simulator backend"""
# Set fake backend config to simulator
backend = Fake1Q()
backend._configuration.simulator = True
"""All the levels with a simulator backend"""
backend = BasicSimulator()
result = transpile(circuit(), backend=backend, optimization_level=level, seed_transpiler=42)
self.assertIsInstance(result, QuantumCircuit)

0 comments on commit f04eaab

Please sign in to comment.