diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 73e6ea055165..ebf095b54edc 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -45,7 +45,6 @@ LookaheadSwap StochasticSwap SabreSwap - BIPMapping Commuting2qGateRouter Basis Change @@ -199,7 +198,6 @@ from .routing import LookaheadSwap from .routing import StochasticSwap from .routing import SabreSwap -from .routing import BIPMapping from .routing import Commuting2qGateRouter # basis change diff --git a/qiskit/transpiler/passes/routing/__init__.py b/qiskit/transpiler/passes/routing/__init__.py index 618b2dc6a9f1..2316705b4a1a 100644 --- a/qiskit/transpiler/passes/routing/__init__.py +++ b/qiskit/transpiler/passes/routing/__init__.py @@ -17,6 +17,5 @@ from .lookahead_swap import LookaheadSwap from .stochastic_swap import StochasticSwap from .sabre_swap import SabreSwap -from .bip_mapping import BIPMapping from .commuting_2q_gate_routing.commuting_2q_gate_router import Commuting2qGateRouter from .commuting_2q_gate_routing.swap_strategy import SwapStrategy diff --git a/qiskit/transpiler/passes/routing/algorithms/bip_model.py b/qiskit/transpiler/passes/routing/algorithms/bip_model.py deleted file mode 100644 index 972cf8242918..000000000000 --- a/qiskit/transpiler/passes/routing/algorithms/bip_model.py +++ /dev/null @@ -1,497 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -"""Integer programming model for quantum circuit compilation.""" -import copy -import logging -from functools import lru_cache - -import numpy as np - -from qiskit.transpiler.exceptions import TranspilerError, CouplingError -from qiskit.transpiler.layout import Layout -from qiskit.circuit.library.standard_gates import SwapGate -from qiskit.providers.models import BackendProperties -from qiskit.quantum_info import two_qubit_cnot_decompose -from qiskit.quantum_info.synthesis.two_qubit_decompose import ( - TwoQubitWeylDecomposition, - trace_to_fid, -) -from qiskit.utils import optionals as _optionals -from qiskit.utils.deprecation import deprecate_func - -logger = logging.getLogger(__name__) - - -@_optionals.HAS_DOCPLEX.require_in_instance -class BIPMappingModel: - """Internal model to create and solve a BIP problem for mapping. - - Attributes: - problem (Model): - A CPLEX problem model object, which is set by calling - :method:`create_cpx_problem`. After calling :method:`solve_cpx_problem`, - the solution will be stored in :attr:`solution`). None if it's not yet set. - """ - - @deprecate_func( - since="0.24.0", - additional_msg="This has been replaced by a new transpiler plugin package: " - "qiskit-bip-mapper. More details can be found here: " - "https://github.com/qiskit-community/qiskit-bip-mapper", - ) # pylint: disable=bad-docstring-quotes - def __init__(self, dag, coupling_map, qubit_subset, dummy_timesteps=None): - """ - Args: - dag (DAGCircuit): DAG circuit to be mapped - coupling_map (CouplingMap): Coupling map of the device on which the `dag` is mapped. - qubit_subset (list[int]): Sublist of physical qubits to be used in the mapping. - dummy_timesteps (int): - Number of dummy time steps, after each real layer of gates, to - allow arbitrary swaps between neighbors. - - Raises: - MissingOptionalLibraryError: If docplex is not installed - TranspilerError: If size of virtual qubits and physical qubits differ, or - if coupling_map is not symmetric (bidirectional). - """ - - self._dag = dag - self._coupling = copy.deepcopy(coupling_map) # reduced coupling map - try: - self._coupling = self._coupling.reduce(qubit_subset) - except CouplingError as err: - raise TranspilerError( - "The 'coupling_map' reduced by 'qubit_subset' must be connected." - ) from err - self._coupling.make_symmetric() - self.global_qubit = qubit_subset # the map from reduced qubit index to global qubit index - - self.problem = None - self.solution = None - self.num_vqubits = len(self._dag.qubits) - self.num_pqubits = self._coupling.size() - self._arcs = self._coupling.get_edges() - - if self.num_vqubits != self.num_pqubits: - raise TranspilerError( - "BIPMappingModel assumes the same size of virtual and physical qubits." - ) - - self._index_to_virtual = dict(enumerate(dag.qubits)) - - # Construct internal circuit model - # Extract layers with 2-qubit gates - self._to_su4layer = [] - self.su4layers = [] - for lay in dag.layers(): - laygates = [] - for node in lay["graph"].two_qubit_ops(): - i1 = self._dag.find_bit(node.qargs[0]).index - i2 = self._dag.find_bit(node.qargs[1]).index - laygates.append(((i1, i2), node)) - if laygates: - self._to_su4layer.append(len(self.su4layers)) - self.su4layers.append(laygates) - else: - self._to_su4layer.append(-1) - # Add dummy time steps inbetween su4layers. Dummy time steps can only contain SWAPs. - self.gates = [] # layered 2q-gates with dummy steps - for k, lay in enumerate(self.su4layers): - self.gates.append(lay) - if k == len(self.su4layers) - 1: # do not add dummy steps after the last layer - break - self.gates.extend([[]] * dummy_timesteps) - - self.bprop = None # Backend properties to compute cx fidelities (set later if necessary) - self.default_cx_error_rate = ( - None # Default cx error rate in case backend properties are not available - ) - - logger.info("Num virtual qubits: %d", self.num_vqubits) - logger.info("Num physical qubits: %d", self.num_pqubits) - logger.info("Model depth: %d", self.depth) - logger.info("Dummy steps: %d", dummy_timesteps) - - @property - def depth(self): - """Number of time-steps (including dummy steps).""" - return len(self.gates) - - def is_su4layer(self, depth: int) -> bool: - """Check if the depth-th layer is su4layer (layer containing 2q-gates) or not. - - Args: - depth: Depth of the ordinary layer - - Returns: - True if the depth-th layer is su4layer, otherwise False - """ - return self._to_su4layer[depth] >= 0 - - def to_su4layer_depth(self, depth: int) -> int: - """Return the depth as a su4layer. If the depth-th layer is not a su4layer, return -1. - - Args: - depth: Depth of the ordinary layer - - Returns: - su4layer depth if the depth-th layer is a su4layer, otherwise -1 - """ - return self._to_su4layer[depth] - - # pylint: disable=invalid-name - def _is_dummy_step(self, t: int): - """Check if the time-step t is a dummy step or not.""" - return len(self.gates[t]) == 0 - - @_optionals.HAS_DOCPLEX.require_in_call - def create_cpx_problem( - self, - objective: str, - backend_prop: BackendProperties = None, - line_symm: bool = False, - depth_obj_weight: float = 0.1, - default_cx_error_rate: float = 5e-3, - ): - """Create integer programming model to compile a circuit. - - Args: - objective: - Type of objective function to be minimized: - - * ``'gate_error'``: Approximate gate error of the circuit, which is given as the sum of - negative logarithm of CNOT gate fidelities in the circuit. It takes into account - only the CNOT gate errors reported in ``backend_prop``. - * ``'depth'``: Depth (number of timesteps) of the circuit - * ``'balanced'``: Weighted sum of gate_error and depth - - backend_prop: - Backend properties storing gate errors, which are required in computing certain - types of objective function such as ``'gate_error'`` or ``'balanced'``. - If this is not available, default_cx_error_rate is used instead. - - line_symm: - Use symmetry breaking constrainst for line topology. Should - only be True if the hardware graph is a chain/line/path. - - depth_obj_weight: - Weight of depth objective in ``'balanced'`` objective function. - - default_cx_error_rate: - Default CX error rate to be used if backend_prop is not available. - - Raises: - TranspilerError: if unknown objective type is specified or invalid options are specified. - MissingOptionalLibraryError: If docplex is not installed - """ - self.bprop = backend_prop - self.default_cx_error_rate = default_cx_error_rate - if self.bprop is None and self.default_cx_error_rate is None: - raise TranspilerError("BackendProperties or default_cx_error_rate must be specified") - from docplex.mp.model import Model - - mdl = Model() - - # *** Define main variables *** - # Add w variables - w = {} - for t in range(self.depth): - for q in range(self.num_vqubits): - for j in range(self.num_pqubits): - w[t, q, j] = mdl.binary_var(name=f"w_{t}_{q}_{j}") - # Add y variables - y = {} - for t in range(self.depth): - for ((p, q), _) in self.gates[t]: - for (i, j) in self._arcs: - y[t, p, q, i, j] = mdl.binary_var(name=f"y_{t}_{p}_{q}_{i}_{j}") - # Add x variables - x = {} - for t in range(self.depth - 1): - for q in range(self.num_vqubits): - for i in range(self.num_pqubits): - x[t, q, i, i] = mdl.binary_var(name=f"x_{t}_{q}_{i}_{i}") - for j in self._coupling.neighbors(i): - x[t, q, i, j] = mdl.binary_var(name=f"x_{t}_{q}_{i}_{j}") - - # *** Define main constraints *** - # Assignment constraints for w variables - for t in range(self.depth): - for q in range(self.num_vqubits): - mdl.add_constraint( - sum(w[t, q, j] for j in range(self.num_pqubits)) == 1, - ctname=f"assignment_vqubits_{q}_at_{t}", - ) - for t in range(self.depth): - for j in range(self.num_pqubits): - mdl.add_constraint( - sum(w[t, q, j] for q in range(self.num_vqubits)) == 1, - ctname=f"assignment_pqubits_{j}_at_{t}", - ) - # Each gate must be implemented - for t in range(self.depth): - for ((p, q), _) in self.gates[t]: - mdl.add_constraint( - sum(y[t, p, q, i, j] for (i, j) in self._arcs) == 1, - ctname=f"implement_gate_{p}_{q}_at_{t}", - ) - # Gate can be implemented iff both of its qubits are located at the associated nodes - for t in range(self.depth - 1): - for ((p, q), _) in self.gates[t]: - for (i, j) in self._arcs: - # Apply McCormick to y[t, p, q, i, j] == w[t, p, i] * w[t, q, j] - mdl.add_constraint( - y[t, p, q, i, j] >= w[t, p, i] + w[t, q, j] - 1, - ctname=f"McCormickLB_{p}_{q}_{i}_{j}_at_{t}", - ) - # Stronger version of McCormick: gate (p,q) is implemented at (i, j) - # if i moves to i or j, and j moves to i or j - mdl.add_constraint( - y[t, p, q, i, j] <= x[t, p, i, i] + x[t, p, i, j], - ctname=f"McCormickUB1_{p}_{q}_{i}_{j}_at_{t}", - ) - mdl.add_constraint( - y[t, p, q, i, j] <= x[t, q, j, i] + x[t, q, j, j], - ctname=f"McCormickUB2_{p}_{q}_{i}_{j}_at_{t}", - ) - # For last time step, use regular McCormick - for ((p, q), _) in self.gates[self.depth - 1]: - for (i, j) in self._arcs: - # Apply McCormick to y[self.depth - 1, p, q, i, j] - # == w[self.depth - 1, p, i] * w[self.depth - 1, q, j] - mdl.add_constraint( - y[self.depth - 1, p, q, i, j] - >= w[self.depth - 1, p, i] + w[self.depth - 1, q, j] - 1, - ctname=f"McCormickLB_{p}_{q}_{i}_{j}_at_last", - ) - mdl.add_constraint( - y[self.depth - 1, p, q, i, j] <= w[self.depth - 1, p, i], - ctname=f"McCormickUB1_{p}_{q}_{i}_{j}_at_last", - ) - mdl.add_constraint( - y[self.depth - 1, p, q, i, j] <= w[self.depth - 1, q, j], - ctname=f"McCormickUB2_{p}_{q}_{i}_{j}_at_last", - ) - # Logical qubit flow-out constraints - for t in range(self.depth - 1): # Flow out; skip last time step - for q in range(self.num_vqubits): - for i in range(self.num_pqubits): - mdl.add_constraint( - w[t, q, i] - == x[t, q, i, i] + sum(x[t, q, i, j] for j in self._coupling.neighbors(i)), - ctname=f"flow_out_{q}_{i}_at_{t}", - ) - # Logical qubit flow-in constraints - for t in range(1, self.depth): # Flow in; skip first time step - for q in range(self.num_vqubits): - for i in range(self.num_pqubits): - mdl.add_constraint( - w[t, q, i] - == x[t - 1, q, i, i] - + sum(x[t - 1, q, j, i] for j in self._coupling.neighbors(i)), - ctname=f"flow_in_{q}_{i}_at_{t}", - ) - # If a gate is implemented, involved qubits cannot swap with other positions - for t in range(self.depth - 1): - for ((p, q), _) in self.gates[t]: - for (i, j) in self._arcs: - mdl.add_constraint( - x[t, p, i, j] == x[t, q, j, i], ctname=f"swap_{p}_{q}_{i}_{j}_at_{t}" - ) - # Qubit not in gates can flip with their neighbors - for t in range(self.depth - 1): - q_no_gate = list(range(self.num_vqubits)) - for ((p, q), _) in self.gates[t]: - q_no_gate.remove(p) - q_no_gate.remove(q) - for (i, j) in self._arcs: - mdl.add_constraint( - sum(x[t, q, i, j] for q in q_no_gate) == sum(x[t, p, j, i] for p in q_no_gate), - ctname=f"swap_no_gate_{i}_{j}_at_{t}", - ) - - # *** Define supplemental variables *** - # Add z variables to count dummy steps (supplemental variables for symmetry breaking) - z = {} - for t in range(self.depth): - if self._is_dummy_step(t): - z[t] = mdl.binary_var(name=f"z_{t}") - - # *** Define supplemental constraints *** - # See if a dummy time step is needed - for t in range(self.depth): - if self._is_dummy_step(t): - for q in range(self.num_vqubits): - mdl.add_constraint( - sum(x[t, q, i, j] for (i, j) in self._arcs) <= z[t], - ctname=f"dummy_ts_needed_for_vqubit_{q}_at_{t}", - ) - # Symmetry breaking between dummy time steps - for t in range(self.depth - 1): - # This is a dummy time step and the next one is dummy too - if self._is_dummy_step(t) and self._is_dummy_step(t + 1): - # We cannot use the next time step unless this one is used too - mdl.add_constraint(z[t] >= z[t + 1], ctname=f"dummy_precedence_{t}") - # Symmetry breaking on the line -- only works on line topology! - if line_symm: - for h in range(1, self.num_vqubits): - mdl.add_constraint( - sum(w[0, p, 0] for p in range(h)) - + sum(w[0, q, self.num_pqubits - 1] for q in range(h, self.num_vqubits)) - >= 1, - ctname=f"sym_break_line_{h}", - ) - - # *** Define objevtive function *** - if objective == "depth": - objexr = sum(z[t] for t in range(self.depth) if self._is_dummy_step(t)) - for t in range(self.depth - 1): - for q in range(self.num_vqubits): - for (i, j) in self._arcs: - objexr += 0.01 * x[t, q, i, j] - mdl.minimize(objexr) - elif objective in ("gate_error", "balanced"): - # We add the depth objective with coefficient depth_obj_weight if balanced was selected. - objexr = 0 - for t in range(self.depth - 1): - for (p, q), node in self.gates[t]: - for (i, j) in self._arcs: - # We pay the cost for gate implementation. - pbest_fid = -np.log(self._max_expected_fidelity(node, i, j)) - objexr += y[t, p, q, i, j] * pbest_fid - # If a gate is mirrored (followed by a swap on the same qubit pair), - # its cost should be replaced with the cost of the combined (mirrored) gate. - pbest_fidm = -np.log(self._max_expected_mirrored_fidelity(node, i, j)) - objexr += x[t, q, i, j] * (pbest_fidm - pbest_fid) / 2 - # Cost of swaps on unused qubits - for q in range(self.num_vqubits): - used_qubits = {q for (pair, _) in self.gates[t] for q in pair} - if q not in used_qubits: - for i in range(self.num_pqubits): - for j in self._coupling.neighbors(i): - objexr += x[t, q, i, j] * -3 / 2 * np.log(self._cx_fidelity(i, j)) - # Cost for the last layer (x variables are not defined for depth-1) - for (p, q), node in self.gates[self.depth - 1]: - for (i, j) in self._arcs: - pbest_fid = -np.log(self._max_expected_fidelity(node, i, j)) - objexr += y[self.depth - 1, p, q, i, j] * pbest_fid - if objective == "balanced": - objexr += depth_obj_weight * sum( - z[t] for t in range(self.depth) if self._is_dummy_step(t) - ) - mdl.minimize(objexr) - else: - raise TranspilerError(f"Unknown objective type: {objective}") - - self.problem = mdl - logger.info("BIP problem stats: %s", self.problem.statistics) - - def _max_expected_fidelity(self, node, i, j): - return max( - gfid * self._cx_fidelity(i, j) ** k - for k, gfid in enumerate(self._gate_fidelities(node)) - ) - - def _max_expected_mirrored_fidelity(self, node, i, j): - return max( - gfid * self._cx_fidelity(i, j) ** k - for k, gfid in enumerate(self._mirrored_gate_fidelities(node)) - ) - - def _cx_fidelity(self, i, j) -> float: - # fidelity of cx on global physical qubits - if self.bprop is not None: - return 1.0 - self.bprop.gate_error("cx", [self.global_qubit[i], self.global_qubit[j]]) - else: - return 1.0 - self.default_cx_error_rate - - @staticmethod - @lru_cache() - def _gate_fidelities(node): - matrix = node.op.to_matrix() - target = TwoQubitWeylDecomposition(matrix) - traces = two_qubit_cnot_decompose.traces(target) - return [trace_to_fid(traces[i]) for i in range(4)] - - @staticmethod - @lru_cache() - def _mirrored_gate_fidelities(node): - matrix = node.op.to_matrix() - swap = SwapGate().to_matrix() - targetm = TwoQubitWeylDecomposition(matrix @ swap) - tracesm = two_qubit_cnot_decompose.traces(targetm) - return [trace_to_fid(tracesm[i]) for i in range(4)] - - @_optionals.HAS_CPLEX.require_in_call - def solve_cpx_problem(self, time_limit: float = 60, threads: int = None) -> str: - """Solve the BIP problem using CPLEX. - - Args: - time_limit: - Time limit (seconds) given to CPLEX. - - threads: - Number of threads to be allowed for CPLEX to use. - - Returns: - Status string that CPLEX returned after solving the BIP problem. - - Raises: - MissingOptionalLibraryError: If CPLEX is not installed - """ - self.problem.set_time_limit(time_limit) - if threads is not None: - self.problem.context.cplex_parameters.threads = threads - self.problem.context.cplex_parameters.randomseed = 777 - - self.solution = self.problem.solve() - - status = self.problem.solve_details.status - logger.info("BIP solution status: %s", status) - return status - - def get_layout(self, t: int) -> Layout: - """Get layout at time-step t. - - Args: - t: Time-step - - Returns: - Layout - """ - dic = {} - for q in range(self.num_vqubits): - for i in range(self.num_pqubits): - if self.solution.get_value(f"w_{t}_{q}_{i}") > 0.5: - dic[self._index_to_virtual[q]] = self.global_qubit[i] - layout = Layout(dic) - for reg in self._dag.qregs.values(): - layout.add_register(reg) - return layout - - def get_swaps(self, t: int) -> list: - """Get swaps (pairs of physical qubits) inserted at time-step ``t``. - - Args: - t: Time-step (<= depth - 1) - - Returns: - List of swaps (pairs of physical qubits (integers)) - """ - swaps = [] - for (i, j) in self._arcs: - if i >= j: - continue - for q in range(self.num_vqubits): - if self.solution.get_value(f"x_{t}_{q}_{i}_{j}") > 0.5: - swaps.append((self.global_qubit[i], self.global_qubit[j])) - return swaps diff --git a/qiskit/transpiler/passes/routing/bip_mapping.py b/qiskit/transpiler/passes/routing/bip_mapping.py deleted file mode 100644 index a658c1c98eaa..000000000000 --- a/qiskit/transpiler/passes/routing/bip_mapping.py +++ /dev/null @@ -1,273 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Map a DAGCircuit onto a given ``coupling_map``, allocating qubits and adding swap gates.""" -import copy -import logging -import math - -from qiskit.circuit import QuantumRegister -from qiskit.circuit.library.standard_gates import SwapGate -from qiskit.dagcircuit import DAGCircuit, DAGOpNode -from qiskit.utils import optionals as _optionals -from qiskit.transpiler import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes.routing.algorithms.bip_model import BIPMappingModel -from qiskit.transpiler.target import target_to_backend_properties, Target -from qiskit.utils.deprecation import deprecate_func -from qiskit.transpiler.passes.layout import disjoint_utils - -logger = logging.getLogger(__name__) - - -@_optionals.HAS_CPLEX.require_in_instance("BIP-based mapping pass") -@_optionals.HAS_DOCPLEX.require_in_instance("BIP-based mapping pass") -class BIPMapping(TransformationPass): - r"""Map a DAGCircuit onto a given ``coupling_map``, allocating qubits and adding swap gates. - - The BIP mapper tries to find the best layout and routing at once by - solving a BIP (binary integer programming) problem as described in [1]. - - The BIP problem represents the layer-by-layer mapping of 2-qubit gates, assuming all the gates - in a layer can be run on the ``coupling_map``. In the problem, the variables :math:`w` represent - the layout of qubits for each layer and the variables :math:`x` represent which pair of qubits - should be swapped in between layers. Based on the values in the solution of the BIP problem, - the mapped circuit will be constructed. - - The BIP mapper depends on ``docplex`` to represent the BIP problem and CPLEX (``cplex``) - to solve it. Those packages can be installed with ``pip install qiskit-terra[bip-mapper]``. - Since the free version of CPLEX can solve only small BIP problems, i.e. mapping of circuits - with less than about 5 qubits, the paid version of CPLEX may be needed to map larger circuits. - - If you want to fix physical qubits to be used in the mapping (e.g. running Quantum Volume - circuits), you need to supply ``qubit_subset``, i.e. list of physical qubits to be used - within the ``coupling_map``. - Please do not use ``initial_layout`` for that purpose because the BIP mapper gracefully - ignores ``initial_layout`` (and tries to determines its best layout). - - .. warning:: - The BIP mapper does not scale very well with respect to the number of qubits or gates. - For example, it may not work with ``qubit_subset`` beyond 10 qubits because - the BIP solver (CPLEX) may not find any solution within the default time limit. - - **References:** - - [1] G. Nannicini et al. "Optimal qubit assignment and routing via integer programming." - `arXiv:2106.06446 `_ - """ - - @deprecate_func( - since="0.24.0", - additional_msg="This has been replaced by a new transpiler plugin package: " - "qiskit-bip-mapper. More details can be found here: " - "https://github.com/qiskit-community/qiskit-bip-mapper", - ) # pylint: disable=bad-docstring-quotes - def __init__( - self, - coupling_map, - qubit_subset=None, - objective="balanced", - backend_prop=None, - time_limit=30, - threads=None, - max_swaps_inbetween_layers=None, - depth_obj_weight=0.1, - default_cx_error_rate=5e-3, - ): - """BIPMapping initializer. - - Args: - coupling_map (Union[CouplingMap, Target]): Directed graph represented a coupling map. - qubit_subset (list[int]): Sublist of physical qubits to be used in the mapping. - If None, all qubits in the coupling_map will be considered. - objective (str): Type of objective function to be minimized: - - * ``'gate_error'``: Approximate gate error of the circuit, which is given as the sum of - negative logarithm of 2q-gate fidelities in the circuit. It takes into account only - the 2q-gate (CNOT) errors reported in ``backend_prop`` and ignores the other errors - in such as 1q-gates, SPAMs and idle times. - * ``'depth'``: Depth (number of 2q-gate layers) of the circuit. - * ``'balanced'``: [Default] Weighted sum of ``'gate_error'`` and ``'depth'`` - - backend_prop (BackendProperties): Backend properties object containing 2q-gate gate errors, - which are required in computing certain types of objective function - such as ``'gate_error'`` or ``'balanced'``. If this is not available, - default_cx_error_rate is used instead. - time_limit (float): Time limit for solving BIP in seconds - threads (int): Number of threads to be allowed for CPLEX to solve BIP - max_swaps_inbetween_layers (int): - Number of swaps allowed in between layers. If None, automatically set. - Large value could decrease the probability to build infeasible BIP problem but also - could reduce the chance of finding a feasible solution within the ``time_limit``. - - depth_obj_weight (float): - Weight of depth objective in ``'balanced'`` objective. The balanced objective is the - sum of error_rate + depth_obj_weight * depth. - - default_cx_error_rate (float): - Default CX error rate to be used if backend_prop is not available. - - Raises: - MissingOptionalLibraryError: if cplex or docplex are not installed. - TranspilerError: if invalid options are specified. - """ - super().__init__() - if isinstance(coupling_map, Target): - self.target = coupling_map - self.coupling_map = self.target.build_coupling_map() - self.backend_prop = target_to_backend_properties(self.target) - else: - self.target = None - self.coupling_map = coupling_map - self.backend_prop = None - self.qubit_subset = qubit_subset - self.objective = objective - if backend_prop is not None: - self.backend_prop = backend_prop - self.time_limit = time_limit - self.threads = threads - self.max_swaps_inbetween_layers = max_swaps_inbetween_layers - self.depth_obj_weight = depth_obj_weight - self.default_cx_error_rate = default_cx_error_rate - if self.coupling_map is not None and self.qubit_subset is None: - self.qubit_subset = list(range(self.coupling_map.size())) - - def run(self, dag): - """Run the BIPMapping pass on `dag`, assuming the number of virtual qubits (defined in - `dag`) and the number of physical qubits (defined in `coupling_map`) are the same. - - Args: - dag (DAGCircuit): DAG to map. - - Returns: - DAGCircuit: A mapped DAG. If there is no 2q-gate in DAG or it fails to map, - returns the original dag. - - Raises: - TranspilerError: if the number of virtual and physical qubits are not the same. - AssertionError: if the final layout is not valid. - """ - if self.coupling_map is None: - return dag - - if len(dag.qubits) > len(self.qubit_subset): - raise TranspilerError("More virtual qubits exist than physical qubits.") - - if len(dag.qubits) != len(self.qubit_subset): - raise TranspilerError( - "BIPMapping requires the number of virtual and physical qubits to be the same. " - "Supply 'qubit_subset' to specify physical qubits to use." - ) - disjoint_utils.require_layout_isolated_to_component( - dag, self.coupling_map if self.target is None else self.target - ) - - original_dag = dag - - dummy_steps = math.ceil(math.sqrt(dag.num_qubits())) - if self.max_swaps_inbetween_layers is not None: - dummy_steps = max(0, self.max_swaps_inbetween_layers - 1) - - model = BIPMappingModel( - dag=dag, - coupling_map=self.coupling_map, - qubit_subset=self.qubit_subset, - dummy_timesteps=dummy_steps, - ) - - if len(model.su4layers) == 0: - logger.info("BIPMapping is skipped due to no 2q-gates.") - return original_dag - - model.create_cpx_problem( - objective=self.objective, - backend_prop=self.backend_prop, - depth_obj_weight=self.depth_obj_weight, - default_cx_error_rate=self.default_cx_error_rate, - ) - - status = model.solve_cpx_problem(time_limit=self.time_limit, threads=self.threads) - if model.solution is None: - logger.warning("Failed to solve a BIP problem. Status: %s", status) - return original_dag - - # Get the optimized initial layout - optimized_layout = model.get_layout(0) - - # Create a layout to track changes in layout for each layer - layout = copy.deepcopy(optimized_layout) - - # Construct the mapped circuit - canonical_qreg = QuantumRegister(self.coupling_map.size(), "q") - mapped_dag = self._create_empty_dagcircuit(dag, canonical_qreg) - interval = dummy_steps + 1 - for k, layer in enumerate(dag.layers()): - if model.is_su4layer(k): - su4dep = model.to_su4layer_depth(k) - # add swaps between (su4dep-1)-th and su4dep-th su4layer - from_steps = max(interval * (su4dep - 1), 0) - to_steps = min(interval * su4dep, model.depth - 1) - for t in range(from_steps, to_steps): # pylint: disable=invalid-name - for (i, j) in model.get_swaps(t): - mapped_dag.apply_operation_back( - op=SwapGate(), - qargs=[canonical_qreg[i], canonical_qreg[j]], - ) - # update layout, swapping physical qubits (i, j) - layout.swap(i, j) - - # map gates in k-th layer - for node in layer["graph"].nodes(): - if isinstance(node, DAGOpNode): - mapped_dag.apply_operation_back( - op=copy.deepcopy(node.op), - qargs=[canonical_qreg[layout[q]] for q in node.qargs], - cargs=node.cargs, - ) - # TODO: double check with y values? - - # Check final layout - final_layout = model.get_layout(model.depth - 1) - if layout != final_layout: - raise AssertionError( - f"Bug: final layout {final_layout} != the layout computed from swaps {layout}" - ) - - self.property_set["layout"] = self._to_full_layout(optimized_layout) - self.property_set["final_layout"] = self._to_full_layout(final_layout) - - return mapped_dag - - @staticmethod - def _create_empty_dagcircuit(source_dag: DAGCircuit, canonical_qreg: QuantumRegister): - target_dag = DAGCircuit() - target_dag.name = source_dag.name - target_dag._global_phase = source_dag._global_phase - target_dag.metadata = source_dag.metadata - - target_dag.add_qreg(canonical_qreg) - for creg in source_dag.cregs.values(): - target_dag.add_creg(creg) - - return target_dag - - def _to_full_layout(self, layout): - # fill layout with ancilla qubits (required by drawers) - idle_physical_qubits = [ - q for q in range(self.coupling_map.size()) if q not in layout.get_physical_bits() - ] - if idle_physical_qubits: - qreg = QuantumRegister(len(idle_physical_qubits), name="ancilla") - for idx, idle_q in enumerate(idle_physical_qubits): - layout[idle_q] = qreg[idx] - layout.add_register(qreg) - return layout diff --git a/qiskit/utils/optionals.py b/qiskit/utils/optionals.py index 8655f4346a0c..0321a1cbc7e5 100644 --- a/qiskit/utils/optionals.py +++ b/qiskit/utils/optionals.py @@ -58,7 +58,8 @@ * - .. py:data:: HAS_CPLEX - The `IBM CPLEX Optimizer `__ is a high-performance mathematical programming solver for linear, mixed-integer and quadratic - programming. It is required by the :class:`.BIPMapping` transpiler pass. + programming. This is no longer by Qiskit, but it weas historically and the optional + remains for backwards compatibility. * - .. py:data:: HAS_CVXPY - `CVXPY `__ is a Python package for solving convex optimization @@ -68,7 +69,8 @@ * - .. py:data:: HAS_DOCPLEX - `IBM Decision Optimization CPLEX Modelling `__ is a library for prescriptive - analysis. Like CPLEX, it is required for the :class:`.BIPMapping` transpiler pass. + analysis. Like CPLEX, this is no longer by Qiskit, but it weas historically and the + optional remains for backwards compatibility. * - .. py:data:: HAS_FIXTURES - The test suite has additional features that are available if the optional `fixtures @@ -246,13 +248,13 @@ HAS_CPLEX = _LazyImportTester( "cplex", - install="pip install 'qiskit-terra[bip-mapper]'", + install="pip install cplex", msg="This may not be possible for all Python versions and OSes", ) HAS_CVXPY = _LazyImportTester("cvxpy", install="pip install cvxpy") HAS_DOCPLEX = _LazyImportTester( {"docplex": (), "docplex.mp.model": ("Model",)}, - install="pip install 'qiskit-terra[bip-mapper]'", + install="pip install docplex", msg="This may not be possible for all Python versions and OSes", ) HAS_FIXTURES = _LazyImportTester("fixtures", install="pip install fixtures") diff --git a/releasenotes/notes/remove-deprecated-bip-mapper-e1206c8f905502dd.yaml b/releasenotes/notes/remove-deprecated-bip-mapper-e1206c8f905502dd.yaml new file mode 100644 index 000000000000..223d931c7f2d --- /dev/null +++ b/releasenotes/notes/remove-deprecated-bip-mapper-e1206c8f905502dd.yaml @@ -0,0 +1,15 @@ +--- +upgrade: + - | + The deprecated transpiler routing pass, ``BIPMapping`` has been removed. + It was marked as deprecated in the Qiskit 0.43.0 release. It has been + replaced by an external plugin package: ``qiskit-bip-mapper``. Details for + this new package can be found at the package's github repository: + + https://github.com/qiskit-community/qiskit-bip-mapper + + The pass was made into a separate plugin package for two reasons, first + the dependency on CPLEX makes it harder to use and secondly the plugin + package more cleanly integrates with :func:`~.transpile`. The optional + extra ``bip-mapper`` to install the ``cplex`` and ``docplex`` to support + this pass has been removed as nothing in Qiskit optionally requires it anymore. diff --git a/test/python/transpiler/test_bip_mapping.py b/test/python/transpiler/test_bip_mapping.py deleted file mode 100644 index a95ca727f79a..000000000000 --- a/test/python/transpiler/test_bip_mapping.py +++ /dev/null @@ -1,379 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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 -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test the BIPMapping pass""" - -import unittest - -from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister -from qiskit.circuit import Barrier -from qiskit.circuit.library.standard_gates import SwapGate, CXGate -from qiskit.converters import circuit_to_dag -from qiskit.test import QiskitTestCase -from qiskit.providers.fake_provider import FakeLima -from qiskit.transpiler import CouplingMap, Layout, PassManager, Target -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes import BIPMapping -from qiskit.transpiler.passes import CheckMap, Collect2qBlocks, ConsolidateBlocks, UnitarySynthesis -from qiskit.utils import optionals - - -@unittest.skipUnless(optionals.HAS_CPLEX, "cplex is required to run the BIPMapping tests") -@unittest.skipUnless(optionals.HAS_DOCPLEX, "docplex is required to run the BIPMapping tests") -class TestBIPMapping(QiskitTestCase): - """Tests the BIPMapping pass.""" - - def test_empty(self): - """Returns the original circuit if the circuit is empty.""" - coupling = CouplingMap([[0, 1]]) - circuit = QuantumCircuit(2) - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling)(circuit) - self.assertEqual(circuit, actual) - - def test_no_two_qubit_gates(self): - """Returns the original circuit if the circuit has no 2q-gates - q0:--[H]-- - q1:------- - CouplingMap map: [0]--[1] - """ - coupling = CouplingMap([[0, 1]]) - - circuit = QuantumCircuit(2) - circuit.h(0) - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling)(circuit) - - self.assertEqual(circuit, actual) - - def test_trivial_case(self): - """No need to have any swap, the CX are distance 1 to each other - q0:--(+)-[H]-(+)- - | | - q1:---.-------|-- - | - q2:-----------.-- - CouplingMap map: [1]--[0]--[2] - """ - coupling = CouplingMap([[0, 1], [0, 2]]) - - circuit = QuantumCircuit(3) - circuit.cx(1, 0) - circuit.h(0) - circuit.cx(2, 0) - - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling)(circuit) - self.assertEqual(3, len(actual)) - for inst, _, _ in actual.data: # there are no swaps - self.assertFalse(isinstance(inst, SwapGate)) - - def test_no_swap(self): - """Adding no swap if not giving initial layout""" - coupling = CouplingMap([[0, 1], [0, 2]]) - - circuit = QuantumCircuit(3) - circuit.cx(1, 2) - - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling)(circuit) - - q = QuantumRegister(3, name="q") - expected = QuantumCircuit(q) - expected.cx(q[0], q[1]) - - self.assertEqual(expected, actual) - - def test_ignore_initial_layout(self): - """Ignoring initial layout even when it is supplied""" - coupling = CouplingMap([[0, 1], [0, 2]]) - - circuit = QuantumCircuit(3) - circuit.cx(1, 2) - - property_set = {"layout": Layout.generate_trivial_layout(*circuit.qubits)} - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling)(circuit, property_set) - - q = QuantumRegister(3, name="q") - expected = QuantumCircuit(q) - expected.cx(q[0], q[1]) - - self.assertEqual(expected, actual) - - def test_can_map_measurements_correctly(self): - """Verify measurement nodes are updated to map correct cregs to re-mapped qregs.""" - coupling = CouplingMap([[0, 1], [0, 2]]) - - qr = QuantumRegister(3, "qr") - cr = ClassicalRegister(2) - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[1], qr[2]) - circuit.measure(qr[1], cr[0]) - circuit.measure(qr[2], cr[1]) - - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling)(circuit) - - q = QuantumRegister(3, "q") - expected = QuantumCircuit(q, cr) - expected.cx(q[0], q[1]) - expected.measure(q[0], cr[0]) # <- changed due to initial layout change - expected.measure(q[1], cr[1]) # <- changed due to initial layout change - - self.assertEqual(expected, actual) - - def test_can_map_measurements_correctly_with_target(self): - """Verify measurement nodes are updated to map correct cregs to re-mapped qregs.""" - target = Target() - target.add_instruction(CXGate(), {(0, 1): None, (0, 2): None}) - - qr = QuantumRegister(3, "qr") - cr = ClassicalRegister(2) - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[1], qr[2]) - circuit.measure(qr[1], cr[0]) - circuit.measure(qr[2], cr[1]) - - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(target)(circuit) - - q = QuantumRegister(3, "q") - expected = QuantumCircuit(q, cr) - expected.cx(q[0], q[1]) - expected.measure(q[0], cr[0]) # <- changed due to initial layout change - expected.measure(q[1], cr[1]) # <- changed due to initial layout change - - self.assertEqual(expected, actual) - - def test_never_modify_mapped_circuit(self): - """Test that the mapping is idempotent. - It should not modify a circuit which is already compatible with the - coupling map, and can be applied repeatedly without modifying the circuit. - """ - coupling = CouplingMap([[0, 1], [0, 2]]) - - circuit = QuantumCircuit(3, 2) - circuit.cx(1, 2) - circuit.measure(1, 0) - circuit.measure(2, 1) - dag = circuit_to_dag(circuit) - - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - mapped_dag = BIPMapping(coupling).run(dag) - remapped_dag = BIPMapping(coupling).run(mapped_dag) - - self.assertEqual(mapped_dag, remapped_dag) - - def test_no_swap_multi_layer(self): - """Can find the best layout for a circuit with multiple layers.""" - coupling = CouplingMap([[0, 1], [1, 2], [2, 3]]) - - qr = QuantumRegister(4, name="qr") - circuit = QuantumCircuit(qr) - circuit.cx(qr[1], qr[0]) - circuit.cx(qr[0], qr[3]) - - property_set = {} - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling, objective="depth")(circuit, property_set) - self.assertEqual(2, actual.depth()) - - CheckMap(coupling)(actual, property_set) - self.assertTrue(property_set["is_swap_mapped"]) - - def test_unmappable_cnots_in_a_layer(self): - """Test mapping of a circuit with 2 cnots in a layer into T-shape coupling, - which BIPMapping cannot map.""" - qr = QuantumRegister(4, "q") - cr = ClassicalRegister(4, "c") - circuit = QuantumCircuit(qr, cr) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.measure(qr, cr) - - coupling = CouplingMap([[0, 1], [1, 2], [1, 3]]) # {0: [1], 1: [2, 3]} - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling)(circuit) - - # Fails to map and returns the original circuit - self.assertEqual(circuit, actual) - - def test_multi_cregs(self): - """Test for multiple ClassicalRegisters.""" - - # ┌───┐ ░ ┌─┐ - # qr_0: ──■────────────┤ X ├─░─┤M├───────── - # ┌─┴─┐ ┌───┐└─┬─┘ ░ └╥┘┌─┐ - # qr_1: ┤ X ├──■──┤ H ├──■───░──╫─┤M├────── - # └───┘┌─┴─┐└───┘ ░ ║ └╥┘┌─┐ - # qr_2: ──■──┤ X ├───────────░──╫──╫─┤M├─── - # ┌─┴─┐└───┘ ░ ║ ║ └╥┘┌─┐ - # qr_3: ┤ X ├────────────────░──╫──╫──╫─┤M├ - # └───┘ ░ ║ ║ ║ └╥┘ - # c: 2/════════════════════════╩══╬══╩══╬═ - # 0 ║ 1 ║ - # ║ ║ - # d: 2/═══════════════════════════╩═════╩═ - # 0 1 - qr = QuantumRegister(4, "qr") - cr1 = ClassicalRegister(2, "c") - cr2 = ClassicalRegister(2, "d") - circuit = QuantumCircuit(qr, cr1, cr2) - circuit.cx(qr[0], qr[1]) - circuit.cx(qr[2], qr[3]) - circuit.cx(qr[1], qr[2]) - circuit.h(qr[1]) - circuit.cx(qr[1], qr[0]) - circuit.barrier(qr) - circuit.measure(qr[0], cr1[0]) - circuit.measure(qr[1], cr2[0]) - circuit.measure(qr[2], cr1[1]) - circuit.measure(qr[3], cr2[1]) - - coupling = CouplingMap([[0, 1], [0, 2], [2, 3]]) # linear [1, 0, 2, 3] - property_set = {} - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling, objective="depth")(circuit, property_set) - self.assertEqual(5, actual.depth()) - - CheckMap(coupling)(actual, property_set) - self.assertTrue(property_set["is_swap_mapped"]) - - def test_swaps_in_dummy_steps(self): - """Test the case when swaps are inserted in dummy steps.""" - - # ┌───┐ ░ ░ - # q_0: ──■──┤ H ├─░───■────────░───■─────── - # ┌─┴─┐├───┤ ░ │ ░ │ - # q_1: ┤ X ├┤ H ├─░───┼────■───░───┼────■── - # └───┘├───┤ ░ │ ┌─┴─┐ ░ ┌─┴─┐ │ - # q_2: ──■──┤ H ├─░───┼──┤ X ├─░─┤ X ├──┼── - # ┌─┴─┐├───┤ ░ ┌─┴─┐└───┘ ░ └───┘┌─┴─┐ - # q_3: ┤ X ├┤ H ├─░─┤ X ├──────░──────┤ X ├ - # └───┘└───┘ ░ └───┘ ░ └───┘ - circuit = QuantumCircuit(4) - circuit.cx(0, 1) - circuit.cx(2, 3) - circuit.h([0, 1, 2, 3]) - circuit.barrier() - circuit.cx(0, 3) - circuit.cx(1, 2) - circuit.barrier() - circuit.cx(0, 2) - circuit.cx(1, 3) - - coupling = CouplingMap.from_line(4) - property_set = {} - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling, objective="depth")(circuit, property_set) - self.assertEqual(7, actual.depth()) - - CheckMap(coupling)(actual, property_set) - self.assertTrue(property_set["is_swap_mapped"]) - - # no swaps before the first barrier - for inst, _, _ in actual.data: - if isinstance(inst, Barrier): - break - self.assertFalse(isinstance(inst, SwapGate)) - - def test_different_number_of_virtual_and_physical_qubits(self): - """Test the case when number of virtual and physical qubits are different.""" - - # q_0: ──■────■─────── - # ┌─┴─┐ │ - # q_1: ┤ X ├──┼────■── - # └───┘ │ ┌─┴─┐ - # q_2: ──■────┼──┤ X ├ - # ┌─┴─┐┌─┴─┐└───┘ - # q_3: ┤ X ├┤ X ├───── - # └───┘└───┘ - circuit = QuantumCircuit(4) - circuit.cx(0, 1) - circuit.cx(2, 3) - circuit.cx(0, 3) - circuit.cx(1, 2) - - coupling = CouplingMap.from_line(5) - with self.assertRaises(TranspilerError): - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - BIPMapping(coupling)(circuit) - - def test_qubit_subset(self): - """Test if `qubit_subset` option works as expected.""" - circuit = QuantumCircuit(3) - circuit.cx(0, 1) - circuit.cx(1, 2) - circuit.cx(0, 2) - - coupling = CouplingMap([(0, 1), (1, 3), (3, 2)]) - qubit_subset = [0, 1, 3] - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - actual = BIPMapping(coupling, qubit_subset=qubit_subset)(circuit) - # all used qubits are in qubit_subset - bit_indices = {bit: index for index, bit in enumerate(actual.qubits)} - for _, qargs, _ in actual.data: - for q in qargs: - self.assertTrue(bit_indices[q] in qubit_subset) - # ancilla qubits are set in the resulting qubit - idle = QuantumRegister(1, name="ancilla") - self.assertEqual(idle[0], actual._layout.initial_layout[2]) - - def test_unconnected_qubit_subset(self): - """Fails if qubits in `qubit_subset` are not connected.""" - circuit = QuantumCircuit(3) - circuit.cx(0, 1) - - coupling = CouplingMap([(0, 1), (1, 3), (3, 2)]) - with self.assertRaises(TranspilerError): - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - BIPMapping(coupling, qubit_subset=[0, 1, 2])(circuit) - - def test_objective_function(self): - """Test if ``objective`` functions prioritize metrics correctly.""" - - # ┌──────┐┌──────┐ ┌──────┐ - # q_0: ┤0 ├┤0 ├─────┤0 ├ - # │ Dcx ││ │ │ Dcx │ - # q_1: ┤1 ├┤ Dcx ├──■──┤1 ├ - # └──────┘│ │ │ └──────┘ - # q_2: ───■────┤1 ├──┼─────■──── - # ┌─┴─┐ └──────┘┌─┴─┐ ┌─┴─┐ - # q_3: ─┤ X ├──────────┤ X ├─┤ X ├── - # └───┘ └───┘ └───┘ - qc = QuantumCircuit(4) - qc.dcx(0, 1) - qc.cx(2, 3) - qc.dcx(0, 2) - qc.cx(1, 3) - qc.dcx(0, 1) - qc.cx(2, 3) - coupling = CouplingMap(FakeLima().configuration().coupling_map) - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - dep_opt = BIPMapping(coupling, objective="depth", qubit_subset=[0, 1, 3, 4])(qc) - with self.assertWarnsRegex(DeprecationWarning, r"^The class.*is deprecated"): - err_opt = BIPMapping( - coupling, - objective="gate_error", - qubit_subset=[0, 1, 3, 4], - backend_prop=FakeLima().properties(), - )(qc) - # depth = number of su4 layers (mirrored gates have to be consolidated as single su4 gates) - pm_ = PassManager([Collect2qBlocks(), ConsolidateBlocks(basis_gates=["cx", "u"])]) - dep_opt = pm_.run(dep_opt) - err_opt = pm_.run(err_opt) - self.assertLessEqual(dep_opt.depth(), err_opt.depth()) - # count CNOTs after synthesized - dep_opt = UnitarySynthesis(basis_gates=["cx", "u"])(dep_opt) - err_opt = UnitarySynthesis(basis_gates=["cx", "u"])(err_opt) - self.assertGreater(dep_opt.count_ops()["cx"], err_opt.count_ops()["cx"])