Skip to content

Commit d9b8a18

Browse files
authored
Remove use of BackendProperties (BackendV1) in transpiler pipeline (Qiskit#13722)
* Remove `BackendProperties` from transpilation pipeline, remove unit tests that depend on BackendV1. * Remove coupling_map input in VF2PostLayout pass, as it would only be used if backend properties were present. After the removal of the latter, the coupling_map would always be overwritten by the target.
1 parent 3aa8cc1 commit d9b8a18

File tree

15 files changed

+120
-680
lines changed

15 files changed

+120
-680
lines changed

qiskit/compiler/transpiler.py

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from qiskit.dagcircuit import DAGCircuit
2424
from qiskit.providers.backend import Backend
2525
from qiskit.providers.backend_compat import BackendV2Converter
26-
from qiskit.providers.models.backendproperties import BackendProperties
2726
from qiskit.pulse import Schedule, InstructionScheduleMap
2827
from qiskit.transpiler import Layout, CouplingMap, PropertySet
2928
from qiskit.transpiler.basepasses import BasePass
@@ -58,22 +57,13 @@
5857
"with defined timing constraints with "
5958
"`Target.from_configuration(..., timing_constraints=...)`",
6059
)
61-
@deprecate_arg(
62-
name="backend_properties",
63-
since="1.3",
64-
package_name="Qiskit",
65-
removal_timeline="in Qiskit 2.0",
66-
additional_msg="The `target` parameter should be used instead. You can build a `Target` instance "
67-
"with defined properties with Target.from_configuration(..., backend_properties=...)",
68-
)
6960
@deprecate_pulse_arg("inst_map", predicate=lambda inst_map: inst_map is not None)
7061
def transpile( # pylint: disable=too-many-return-statements
7162
circuits: _CircuitT,
7263
backend: Optional[Backend] = None,
7364
basis_gates: Optional[List[str]] = None,
7465
inst_map: Optional[List[InstructionScheduleMap]] = None,
7566
coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None,
76-
backend_properties: Optional[BackendProperties] = None,
7767
initial_layout: Optional[Union[Layout, Dict, List]] = None,
7868
layout_method: Optional[str] = None,
7969
routing_method: Optional[str] = None,
@@ -105,7 +95,7 @@ def transpile( # pylint: disable=too-many-return-statements
10595
10696
The prioritization of transpilation target constraints works as follows: if a ``target``
10797
input is provided, it will take priority over any ``backend`` input or loose constraints
108-
(``basis_gates``, ``inst_map``, ``coupling_map``, ``backend_properties``, ``instruction_durations``,
98+
(``basis_gates``, ``inst_map``, ``coupling_map``, ``instruction_durations``,
10999
``dt`` or ``timing_constraints``). If a ``backend`` is provided together with any loose constraint
110100
from the list above, the loose constraint will take priority over the corresponding backend
111101
constraint. This behavior is independent of whether the ``backend`` instance is of type
@@ -123,7 +113,6 @@ def transpile( # pylint: disable=too-many-return-statements
123113
**inst_map** target inst_map inst_map
124114
**dt** target dt dt
125115
**timing_constraints** target timing_constraints timing_constraints
126-
**backend_properties** target backend_properties backend_properties
127116
============================ ========= ======================== =======================
128117
129118
Args:
@@ -148,10 +137,6 @@ def transpile( # pylint: disable=too-many-return-statements
148137
#. List, must be given as an adjacency matrix, where each entry
149138
specifies all directed two-qubit interactions supported by backend,
150139
e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]``
151-
152-
backend_properties: properties returned by a backend, including information on gate
153-
errors, readout errors, qubit coherence times, etc. Find a backend
154-
that provides this information with: ``backend.properties()``
155140
initial_layout: Initial position of virtual qubits on physical qubits.
156141
If this layout makes the circuit compatible with the coupling_map
157142
constraints, it will be used. The final layout is not guaranteed to be the same,
@@ -394,7 +379,7 @@ def callback_func(**kwargs):
394379
# Edge cases require using the old model (loose constraints) instead of building a target,
395380
# but we don't populate the passmanager config with loose constraints unless it's one of
396381
# the known edge cases to control the execution path.
397-
# Filter instruction_durations, timing_constraints, backend_properties and inst_map deprecation
382+
# Filter instruction_durations, timing_constraints and inst_map deprecation
398383
with warnings.catch_warnings():
399384
warnings.filterwarnings(
400385
"ignore",
@@ -414,20 +399,13 @@ def callback_func(**kwargs):
414399
message=".*``instruction_durations`` is deprecated as of Qiskit 1.3.*",
415400
module="qiskit",
416401
)
417-
warnings.filterwarnings(
418-
"ignore",
419-
category=DeprecationWarning,
420-
message=".*``backend_properties`` is deprecated as of Qiskit 1.3.*",
421-
module="qiskit",
422-
)
423402
pm = generate_preset_pass_manager(
424403
optimization_level,
425404
target=target,
426405
backend=backend,
427406
basis_gates=basis_gates,
428407
coupling_map=coupling_map,
429408
instruction_durations=instruction_durations,
430-
backend_properties=backend_properties,
431409
timing_constraints=timing_constraints,
432410
inst_map=inst_map,
433411
initial_layout=initial_layout,

qiskit/transpiler/passes/layout/dense_layout.py

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,15 @@ class DenseLayout(AnalysisPass):
3636
by being set in ``property_set``.
3737
"""
3838

39-
def __init__(self, coupling_map=None, backend_prop=None, target=None):
39+
def __init__(self, coupling_map=None, target=None):
4040
"""DenseLayout initializer.
4141
4242
Args:
4343
coupling_map (Coupling): directed graph representing a coupling map.
44-
backend_prop (BackendProperties): backend properties object
4544
target (Target): A target representing the target backend.
4645
"""
4746
super().__init__()
4847
self.coupling_map = coupling_map
49-
self.backend_prop = backend_prop
5048
self.target = target
5149
self.adjacency_matrix = None
5250
if target is not None:
@@ -127,8 +125,6 @@ def _best_subset(self, num_qubits, num_meas, num_cx, coupling_map):
127125
error_mat, use_error = _build_error_matrix(
128126
coupling_map.size(),
129127
reverse_index_map,
130-
backend_prop=self.backend_prop,
131-
coupling_map=self.coupling_map,
132128
target=self.target,
133129
)
134130

@@ -148,7 +144,7 @@ def _best_subset(self, num_qubits, num_meas, num_cx, coupling_map):
148144
return best_map
149145

150146

151-
def _build_error_matrix(num_qubits, qubit_map, target=None, coupling_map=None, backend_prop=None):
147+
def _build_error_matrix(num_qubits, qubit_map, target=None):
152148
error_mat = np.zeros((num_qubits, num_qubits))
153149
use_error = False
154150
if target is not None and target.qargs is not None:
@@ -178,25 +174,4 @@ def _build_error_matrix(num_qubits, qubit_map, target=None, coupling_map=None, b
178174
elif len(qargs) == 2:
179175
error_mat[qubit_map[qargs[0]]][qubit_map[qargs[1]]] = max_error
180176
use_error = True
181-
elif backend_prop and coupling_map:
182-
error_dict = {
183-
tuple(gate.qubits): gate.parameters[0].value
184-
for gate in backend_prop.gates
185-
if len(gate.qubits) == 2
186-
}
187-
for edge in coupling_map.get_edges():
188-
gate_error = error_dict.get(edge)
189-
if gate_error is not None:
190-
if edge[0] not in qubit_map or edge[1] not in qubit_map:
191-
continue
192-
error_mat[qubit_map[edge[0]]][qubit_map[edge[1]]] = gate_error
193-
use_error = True
194-
for index, qubit_data in enumerate(backend_prop.qubits):
195-
if index not in qubit_map:
196-
continue
197-
for item in qubit_data:
198-
if item.name == "readout_error":
199-
mapped_index = qubit_map[index]
200-
error_mat[mapped_index][mapped_index] = item.value
201-
use_error = True
202177
return error_mat, use_error

qiskit/transpiler/passes/layout/vf2_layout.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ def __init__(
8080
seed=None,
8181
call_limit=None,
8282
time_limit=None,
83-
properties=None,
8483
max_trials=None,
8584
target=None,
8685
):
@@ -94,16 +93,13 @@ def __init__(
9493
call_limit (int): The number of state visits to attempt in each execution of
9594
VF2.
9695
time_limit (float): The total time limit in seconds to run ``VF2Layout``
97-
properties (BackendProperties): The backend properties for the backend. If
98-
:meth:`~qiskit.providers.models.BackendProperties.readout_error` is available
99-
it is used to score the layout.
10096
max_trials (int): The maximum number of trials to run VF2 to find
10197
a layout. If this is not specified the number of trials will be limited
10298
based on the number of edges in the interaction graph or the coupling graph
10399
(whichever is larger) if no other limits are set. If set to a value <= 0 no
104100
limit on the number of trials will be set.
105101
target (Target): A target representing the backend device to run ``VF2Layout`` on.
106-
If specified it will supersede a set value for ``properties`` and
102+
If specified it will supersede a set value for
107103
``coupling_map`` if the :class:`.Target` contains connectivity constraints. If the value
108104
of ``target`` models an ideal backend without any constraints then the value of
109105
``coupling_map``
@@ -121,7 +117,6 @@ def __init__(
121117
self.coupling_map = target_coupling_map
122118
else:
123119
self.coupling_map = coupling_map
124-
self.properties = properties
125120
self.strict_direction = strict_direction
126121
self.seed = seed
127122
self.call_limit = call_limit
@@ -135,9 +130,7 @@ def run(self, dag):
135130
raise TranspilerError("coupling_map or target must be specified.")
136131
self.avg_error_map = self.property_set["vf2_avg_error_map"]
137132
if self.avg_error_map is None:
138-
self.avg_error_map = vf2_utils.build_average_error_map(
139-
self.target, self.properties, self.coupling_map
140-
)
133+
self.avg_error_map = vf2_utils.build_average_error_map(self.target, self.coupling_map)
141134

142135
result = vf2_utils.build_interaction_graph(dag, self.strict_direction)
143136
if result is None:

qiskit/transpiler/passes/layout/vf2_post_layout.py

Lines changed: 60 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,7 @@ class VF2PostLayout(AnalysisPass):
7878
* ``">2q gates in basis"``: If VF2PostLayout can't work with the basis of the circuit.
7979
8080
By default, this pass will construct a heuristic scoring map based on
81-
the error rates in the provided ``target`` (or ``properties`` if ``target``
82-
is not provided). However, analysis passes can be run prior to this pass
81+
the error rates in the provided ``target``. However, analysis passes can be run prior to this pass
8382
and set ``vf2_avg_error_map`` in the property set with a :class:`~.ErrorMap`
8483
instance. If a value is ``NaN`` that is treated as an ideal edge
8584
For example if an error map is created as::
@@ -102,8 +101,6 @@ class VF2PostLayout(AnalysisPass):
102101
def __init__(
103102
self,
104103
target=None,
105-
coupling_map=None,
106-
properties=None,
107104
seed=None,
108105
call_limit=None,
109106
time_limit=None,
@@ -114,12 +111,6 @@ def __init__(
114111
115112
Args:
116113
target (Target): A target representing the backend device to run ``VF2PostLayout`` on.
117-
If specified it will supersede a set value for ``properties`` and
118-
``coupling_map``.
119-
coupling_map (CouplingMap): Directed graph representing a coupling map.
120-
properties (BackendProperties): The backend properties for the backend. If
121-
:meth:`~qiskit.providers.models.BackendProperties.readout_error` is available
122-
it is used to score the layout.
123114
seed (int): Sets the seed of the PRNG. -1 Means no node shuffling.
124115
call_limit (int): The number of state visits to attempt in each execution of
125116
VF2.
@@ -138,12 +129,10 @@ def __init__(
138129
a layout. A value of ``0`` (the default) means 'unlimited'.
139130
140131
Raises:
141-
TypeError: At runtime, if neither ``coupling_map`` or ``target`` are provided.
132+
TypeError: At runtime, if ``target`` isn't provided.
142133
"""
143134
super().__init__()
144135
self.target = target
145-
self.coupling_map = coupling_map
146-
self.properties = properties
147136
self.call_limit = call_limit
148137
self.time_limit = time_limit
149138
self.max_trials = max_trials
@@ -153,16 +142,12 @@ def __init__(
153142

154143
def run(self, dag):
155144
"""run the layout method"""
156-
if self.target is None and (self.coupling_map is None or self.properties is None):
157-
raise TranspilerError(
158-
"A target must be specified or a coupling map and properties must be provided"
159-
)
145+
if self.target is None:
146+
raise TranspilerError("A target must be specified or a coupling map must be provided")
160147
if not self.strict_direction:
161148
self.avg_error_map = self.property_set["vf2_avg_error_map"]
162149
if self.avg_error_map is None:
163-
self.avg_error_map = vf2_utils.build_average_error_map(
164-
self.target, self.properties, self.coupling_map
165-
)
150+
self.avg_error_map = vf2_utils.build_average_error_map(self.target, None)
166151

167152
result = vf2_utils.build_interaction_graph(dag, self.strict_direction)
168153
if result is None:
@@ -172,67 +157,62 @@ def run(self, dag):
172157
scoring_bit_list = vf2_utils.build_bit_list(im_graph, im_graph_node_map)
173158
scoring_edge_list = vf2_utils.build_edge_list(im_graph)
174159

175-
if self.target is not None:
176-
# If qargs is None then target is global and ideal so no
177-
# scoring is needed
178-
if self.target.qargs is None:
179-
return
180-
if self.strict_direction:
181-
cm_graph = PyDiGraph(multigraph=False)
182-
else:
183-
cm_graph = PyGraph(multigraph=False)
184-
# If None is present in qargs there are globally defined ideal operations
185-
# we should add these to all entries based on the number of qubits, so we
186-
# treat that as a valid operation even if there is no scoring for the
187-
# strict direction case
188-
global_ops = None
189-
if None in self.target.qargs:
190-
global_ops = {1: [], 2: []}
191-
for op in self.target.operation_names_for_qargs(None):
192-
operation = self.target.operation_for_name(op)
193-
# If operation is a class this is a variable width ideal instruction
194-
# so we treat it as available on both 1 and 2 qubits
195-
if inspect.isclass(operation):
196-
global_ops[1].append(op)
197-
global_ops[2].append(op)
198-
else:
199-
num_qubits = operation.num_qubits
200-
if num_qubits in global_ops:
201-
global_ops[num_qubits].append(op)
202-
op_names = []
203-
for i in range(self.target.num_qubits):
204-
try:
205-
entry = set(self.target.operation_names_for_qargs((i,)))
206-
except KeyError:
207-
entry = set()
208-
if global_ops is not None:
209-
entry.update(global_ops[1])
210-
op_names.append(entry)
211-
cm_graph.add_nodes_from(op_names)
212-
for qargs in self.target.qargs:
213-
len_args = len(qargs)
214-
# If qargs == 1 we already populated it and if qargs > 2 there are no instructions
215-
# using those in the circuit because we'd have already returned by this point
216-
if len_args == 2:
217-
ops = set(self.target.operation_names_for_qargs(qargs))
218-
if global_ops is not None:
219-
ops.update(global_ops[2])
220-
cm_graph.add_edge(qargs[0], qargs[1], ops)
221-
cm_nodes = list(cm_graph.node_indexes())
222-
# Filter qubits without any supported operations. If they
223-
# don't support any operations, they're not valid for layout selection.
224-
# This is only needed in the undirected case because in strict direction
225-
# mode the node matcher will not match since none of the circuit ops
226-
# will match the cmap ops.
227-
if not self.strict_direction:
228-
has_operations = set(itertools.chain.from_iterable(self.target.qargs))
229-
to_remove = set(cm_graph.node_indices()).difference(has_operations)
230-
if to_remove:
231-
cm_graph.remove_nodes_from(list(to_remove))
160+
# If qargs is None then target is global and ideal so no
161+
# scoring is needed
162+
if self.target.qargs is None:
163+
return
164+
if self.strict_direction:
165+
cm_graph = PyDiGraph(multigraph=False)
232166
else:
233-
cm_graph, cm_nodes = vf2_utils.shuffle_coupling_graph(
234-
self.coupling_map, self.seed, self.strict_direction
235-
)
167+
cm_graph = PyGraph(multigraph=False)
168+
# If None is present in qargs there are globally defined ideal operations
169+
# we should add these to all entries based on the number of qubits, so we
170+
# treat that as a valid operation even if there is no scoring for the
171+
# strict direction case
172+
global_ops = None
173+
if None in self.target.qargs:
174+
global_ops = {1: [], 2: []}
175+
for op in self.target.operation_names_for_qargs(None):
176+
operation = self.target.operation_for_name(op)
177+
# If operation is a class this is a variable width ideal instruction
178+
# so we treat it as available on both 1 and 2 qubits
179+
if inspect.isclass(operation):
180+
global_ops[1].append(op)
181+
global_ops[2].append(op)
182+
else:
183+
num_qubits = operation.num_qubits
184+
if num_qubits in global_ops:
185+
global_ops[num_qubits].append(op)
186+
op_names = []
187+
for i in range(self.target.num_qubits):
188+
try:
189+
entry = set(self.target.operation_names_for_qargs((i,)))
190+
except KeyError:
191+
entry = set()
192+
if global_ops is not None:
193+
entry.update(global_ops[1])
194+
op_names.append(entry)
195+
cm_graph.add_nodes_from(op_names)
196+
for qargs in self.target.qargs:
197+
len_args = len(qargs)
198+
# If qargs == 1 we already populated it and if qargs > 2 there are no instructions
199+
# using those in the circuit because we'd have already returned by this point
200+
if len_args == 2:
201+
ops = set(self.target.operation_names_for_qargs(qargs))
202+
if global_ops is not None:
203+
ops.update(global_ops[2])
204+
cm_graph.add_edge(qargs[0], qargs[1], ops)
205+
cm_nodes = list(cm_graph.node_indexes())
206+
# Filter qubits without any supported operations. If they
207+
# don't support any operations, they're not valid for layout selection.
208+
# This is only needed in the undirected case because in strict direction
209+
# mode the node matcher will not match since none of the circuit ops
210+
# will match the cmap ops.
211+
if not self.strict_direction:
212+
has_operations = set(itertools.chain.from_iterable(self.target.qargs))
213+
to_remove = set(cm_graph.node_indices()).difference(has_operations)
214+
if to_remove:
215+
cm_graph.remove_nodes_from(list(to_remove))
236216

237217
logger.debug("Running VF2 to find post transpile mappings")
238218
if self.target and self.strict_direction:

0 commit comments

Comments
 (0)