diff --git a/qiskit/transpiler/passes/layout/csp_layout.py b/qiskit/transpiler/passes/layout/csp_layout.py index 97f1d34b01df..06445f8a996f 100644 --- a/qiskit/transpiler/passes/layout/csp_layout.py +++ b/qiskit/transpiler/passes/layout/csp_layout.py @@ -21,6 +21,7 @@ from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.exceptions import TranspilerError from qiskit.utils import optionals as _optionals +from qiskit.transpiler.target import Target @_optionals.HAS_CONSTRAINT.require_in_instance @@ -28,7 +29,12 @@ class CSPLayout(AnalysisPass): """If possible, chooses a Layout as a CSP, using backtracking.""" def __init__( - self, coupling_map, strict_direction=False, seed=None, call_limit=1000, time_limit=10 + self, + coupling_map, + strict_direction=False, + seed=None, + call_limit=1000, + time_limit=10, ): """If possible, chooses a Layout as a CSP, using backtracking. @@ -42,7 +48,7 @@ def __init__( * time limit reached: If no perfect layout was found and the time limit was reached. Args: - coupling_map (Coupling): Directed graph representing a coupling map. + coupling_map (Union[CouplingMap, Target]): Directed graph representing a coupling map. strict_direction (bool): If True, considers the direction of the coupling map. Default is False. seed (int): Sets the seed of the PRNG. @@ -53,7 +59,13 @@ def __init__( None means no time limit. Default: 10 seconds. """ super().__init__() - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map + self.strict_direction = strict_direction self.call_limit = call_limit self.time_limit = time_limit diff --git a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py index 4c678af5ea78..e3259b4d0ad9 100644 --- a/qiskit/transpiler/passes/layout/full_ancilla_allocation.py +++ b/qiskit/transpiler/passes/layout/full_ancilla_allocation.py @@ -15,6 +15,7 @@ from qiskit.circuit import QuantumRegister from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.target import Target class FullAncillaAllocation(AnalysisPass): @@ -35,10 +36,15 @@ def __init__(self, coupling_map): """FullAncillaAllocation initializer. Args: - coupling_map (Coupling): directed graph representing a coupling map. + coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map. """ super().__init__() - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map self.ancilla_name = "ancilla" def run(self, dag): diff --git a/qiskit/transpiler/passes/layout/layout_2q_distance.py b/qiskit/transpiler/passes/layout/layout_2q_distance.py index f31b44591f0c..6af709851702 100644 --- a/qiskit/transpiler/passes/layout/layout_2q_distance.py +++ b/qiskit/transpiler/passes/layout/layout_2q_distance.py @@ -19,6 +19,7 @@ """ from qiskit.transpiler.basepasses import AnalysisPass +from qiskit.transpiler.target import Target class Layout2qDistance(AnalysisPass): @@ -34,11 +35,16 @@ def __init__(self, coupling_map, property_name="layout_score"): """Layout2qDistance initializer. Args: - coupling_map (CouplingMap): Directed graph represented a coupling map. + coupling_map (Union[CouplingMap, Target]): Directed graph represented a coupling map. property_name (str): The property name to save the score. Default: layout_score """ super().__init__() - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map self.property_name = property_name def run(self, dag): diff --git a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py index f8dcc566d1be..8bee5a7b2913 100644 --- a/qiskit/transpiler/passes/layout/noise_adaptive_layout.py +++ b/qiskit/transpiler/passes/layout/noise_adaptive_layout.py @@ -19,6 +19,7 @@ from qiskit.transpiler.layout import Layout from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.target import target_to_backend_properties, Target class NoiseAdaptiveLayout(AnalysisPass): @@ -58,13 +59,18 @@ def __init__(self, backend_prop): """NoiseAdaptiveLayout initializer. Args: - backend_prop (BackendProperties): backend properties object + backend_prop (Union[BackendProperties, Target]): backend properties object Raises: TranspilerError: if invalid options """ super().__init__() - self.backend_prop = backend_prop + if isinstance(backend_prop, Target): + self.target = backend_prop + self.backend_prop = target_to_backend_properties(self.target) + else: + self.target = None + self.backend_prop = backend_prop self.swap_graph = rx.PyDiGraph() self.cx_reliability = {} self.readout_reliability = {} diff --git a/qiskit/transpiler/passes/layout/sabre_layout.py b/qiskit/transpiler/passes/layout/sabre_layout.py index fbe3abb8cdef..26db9ddecb43 100644 --- a/qiskit/transpiler/passes/layout/sabre_layout.py +++ b/qiskit/transpiler/passes/layout/sabre_layout.py @@ -34,6 +34,7 @@ NeighborTable, ) from qiskit.transpiler.passes.routing.sabre_swap import process_swaps, apply_gate +from qiskit.transpiler.target import Target from qiskit.tools.parallel import CPU_COUNT logger = logging.getLogger(__name__) @@ -86,7 +87,7 @@ def __init__( """SabreLayout initializer. Args: - coupling_map (Coupling): directed graph representing a coupling map. + coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map. routing_pass (BasePass): the routing pass to use while iterating. If specified this pass operates as an :class:`~.AnalysisPass` and will only populate the ``layout`` field in the property set and @@ -124,17 +125,13 @@ def __init__( both ``routing_pass`` and ``layout_trials`` are specified """ super().__init__() - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map self._neighbor_table = None - if self.coupling_map is not None: - if not self.coupling_map.is_symmetric: - # deepcopy is needed here to avoid modifications updating - # shared references in passes which require directional - # constraints - self.coupling_map = copy.deepcopy(self.coupling_map) - self.coupling_map.make_symmetric() - self._neighbor_table = NeighborTable(rx.adjacency_matrix(self.coupling_map.graph)) - if routing_pass is not None and (swap_trials is not None or layout_trials is not None): raise TranspilerError("Both routing_pass and swap_trials can't be set at the same time") self.routing_pass = routing_pass @@ -150,6 +147,14 @@ def __init__( else: self.layout_trials = layout_trials self.skip_routing = skip_routing + if self.coupling_map is not None: + if not self.coupling_map.is_symmetric: + # deepcopy is needed here to avoid modifications updating + # shared references in passes which require directional + # constraints + self.coupling_map = copy.deepcopy(self.coupling_map) + self.coupling_map.make_symmetric() + self._neighbor_table = NeighborTable(rx.adjacency_matrix(self.coupling_map.graph)) def run(self, dag): """Run the SabreLayout pass on `dag`. diff --git a/qiskit/transpiler/passes/layout/trivial_layout.py b/qiskit/transpiler/passes/layout/trivial_layout.py index b9469878add6..5f1457d094bc 100644 --- a/qiskit/transpiler/passes/layout/trivial_layout.py +++ b/qiskit/transpiler/passes/layout/trivial_layout.py @@ -15,6 +15,7 @@ from qiskit.transpiler.layout import Layout from qiskit.transpiler.basepasses import AnalysisPass from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.target import Target class TrivialLayout(AnalysisPass): @@ -33,13 +34,18 @@ def __init__(self, coupling_map): """TrivialLayout initializer. Args: - coupling_map (Coupling): directed graph representing a coupling map. + coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map. Raises: TranspilerError: if invalid options """ super().__init__() - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map def run(self, dag): """Run the TrivialLayout pass on `dag`. @@ -48,15 +54,18 @@ def run(self, dag): dag (DAGCircuit): DAG to find layout for. Raises: - TranspilerError: if dag wider than self.coupling_map + TranspilerError: if dag wider than the target backend """ + if self.target is not None: + if dag.num_qubits() > self.target.num_qubits: + raise TranspilerError("Number of qubits greater than device.") + elif dag.num_qubits() > self.coupling_map.size(): + raise TranspilerError("Number of qubits greater than device.") if not self.coupling_map.is_connected(): raise TranspilerError( "Coupling Map is disjoint, this pass can't be used with a disconnected coupling " "map." ) - if dag.num_qubits() > self.coupling_map.size(): - raise TranspilerError("Number of qubits greater than device.") self.property_set["layout"] = Layout.generate_trivial_layout( *(dag.qubits + list(dag.qregs.values())) ) diff --git a/qiskit/transpiler/passes/routing/basic_swap.py b/qiskit/transpiler/passes/routing/basic_swap.py index a493aa3d1e93..91a4d4ded611 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -17,6 +17,7 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.layout import Layout from qiskit.circuit.library.standard_gates import SwapGate +from qiskit.transpiler.target import Target class BasicSwap(TransformationPass): @@ -31,12 +32,17 @@ def __init__(self, coupling_map, fake_run=False): """BasicSwap initializer. Args: - coupling_map (CouplingMap): Directed graph represented a coupling map. + coupling_map (Union[CouplingMap, Target]): Directed graph represented a coupling map. fake_run (bool): if true, it only pretend to do routing, i.e., no swap is effectively added. """ super().__init__() - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map self.fake_run = fake_run def run(self, dag): diff --git a/qiskit/transpiler/passes/routing/bip_mapping.py b/qiskit/transpiler/passes/routing/bip_mapping.py index 7a87b8272e40..9f6fc177eece 100644 --- a/qiskit/transpiler/passes/routing/bip_mapping.py +++ b/qiskit/transpiler/passes/routing/bip_mapping.py @@ -22,6 +22,7 @@ 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 logger = logging.getLogger(__name__) @@ -77,7 +78,7 @@ def __init__( """BIPMapping initializer. Args: - coupling_map (CouplingMap): Directed graph represented a coupling map. + 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: @@ -112,17 +113,25 @@ def __init__( TranspilerError: if invalid options are specified. """ super().__init__() - self.coupling_map = coupling_map + 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 - if self.coupling_map is not None and self.qubit_subset is None: - self.qubit_subset = list(range(self.coupling_map.size())) self.objective = objective - self.backend_prop = backend_prop + 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 diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 3898d2f8256c..3d67e0ffb572 100644 --- a/qiskit/transpiler/passes/routing/layout_transformation.py +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -19,6 +19,7 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper +from qiskit.transpiler.target import Target class LayoutTransformation(TransformationPass): @@ -30,7 +31,7 @@ class LayoutTransformation(TransformationPass): def __init__( self, - coupling_map: CouplingMap, + coupling_map: Union[CouplingMap, Target, None], from_layout: Union[Layout, str], to_layout: Union[Layout, str], seed: Union[int, np.random.default_rng] = None, @@ -39,7 +40,7 @@ def __init__( """LayoutTransformation initializer. Args: - coupling_map (CouplingMap): + coupling_map: Directed graph representing a coupling map. from_layout (Union[Layout, str]): @@ -59,12 +60,15 @@ def __init__( super().__init__() self.from_layout = from_layout self.to_layout = to_layout - if coupling_map: - self.coupling_map = coupling_map - graph = coupling_map.graph.to_undirected() + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() else: + self.target = None + self.coupling_map = coupling_map + if self.coupling_map is None: self.coupling_map = CouplingMap.from_full(len(to_layout)) - graph = self.coupling_map.graph.to_undirected() + graph = self.coupling_map.graph.to_undirected() self.token_swapper = ApproximateTokenSwapper(graph, seed) self.trials = trials diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index 26da165b96c0..bb0476ee4f41 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -22,6 +22,7 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout from qiskit.dagcircuit import DAGOpNode +from qiskit.transpiler.target import Target logger = logging.getLogger(__name__) @@ -85,7 +86,7 @@ def __init__(self, coupling_map, search_depth=4, search_width=4, fake_run=False) """LookaheadSwap initializer. Args: - coupling_map (CouplingMap): CouplingMap of the target backend. + coupling_map (Union[CouplingMap, Target]): CouplingMap of the target backend. search_depth (int): lookahead tree depth when ranking best SWAP options. search_width (int): lookahead tree width when ranking best SWAP options. fake_run (bool): if true, it only pretend to do routing, i.e., no @@ -93,7 +94,12 @@ def __init__(self, coupling_map, search_depth=4, search_width=4, fake_run=False) """ super().__init__() - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map self.search_depth = search_depth self.search_width = search_width self.fake_run = fake_run diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 9caa53bd07e5..04e15cbce94b 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -21,6 +21,7 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout +from qiskit.transpiler.target import Target from qiskit.dagcircuit import DAGOpNode from qiskit.tools.parallel import CPU_COUNT @@ -76,7 +77,7 @@ def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, t r"""SabreSwap initializer. Args: - coupling_map (CouplingMap): CouplingMap of the target backend. + coupling_map (Union[CouplingMap, Target]): CouplingMap of the target backend. heuristic (str): The type of heuristic to use when deciding best swap strategy ('basic' or 'lookahead' or 'decay'). seed (int): random seed used to tie-break among candidate swaps. @@ -139,16 +140,20 @@ def __init__(self, coupling_map, heuristic="basic", seed=None, fake_run=False, t super().__init__() # Assume bidirectional couplings, fixing gate direction is easy later. - if coupling_map is None or coupling_map.is_symmetric: - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() else: + self.coupling_map = coupling_map + self.target = None + if self.coupling_map is not None and not self.coupling_map.is_symmetric: # A deepcopy is needed here to avoid modifications updating # shared references in passes which require directional # constraints - self.coupling_map = deepcopy(coupling_map) + self.coupling_map = deepcopy(self.coupling_map) self.coupling_map.make_symmetric() self._neighbor_table = None - if coupling_map is not None: + if self.coupling_map is not None: self._neighbor_table = NeighborTable( rustworkx.adjacency_matrix(self.coupling_map.graph) ) diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index c7009d911cbe..df549ef848bc 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -24,6 +24,7 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.circuit.library.standard_gates import SwapGate from qiskit.transpiler.layout import Layout +from qiskit.transpiler.target import Target from qiskit.circuit import IfElseOp, WhileLoopOp, ForLoopOp, ControlFlowOp, Instruction from qiskit._accelerate import stochastic_swap as stochastic_swap_rs from qiskit._accelerate import nlayout @@ -56,7 +57,7 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_l If these are not satisfied, the behavior is undefined. Args: - coupling_map (CouplingMap): Directed graph representing a coupling + coupling_map (Union[CouplingMap, Target]): Directed graph representing a coupling map. trials (int): maximum number of iterations to attempt seed (int): seed for random number generator @@ -65,7 +66,12 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_l initial_layout (Layout): starting layout at beginning of pass. """ super().__init__() - self.coupling_map = coupling_map + if isinstance(coupling_map, Target): + self.target = coupling_map + self.coupling_map = self.target.build_coupling_map() + else: + self.target = None + self.coupling_map = coupling_map self.trials = trials self.seed = seed self.rng = None diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index 0fc635b32094..f014bdb78203 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -20,18 +20,37 @@ class CheckMap(AnalysisPass): """Check if a DAG circuit is already mapped to a coupling map. Check if a DAGCircuit is mapped to `coupling_map` by checking that all - 2-qubit interactions are laid out to be physically close, setting the - property ``is_swap_mapped`` to ``True`` or ``False`` accordingly. + 2-qubit interactions are laid out to be on adjacent qubits in the global coupling + map of the device, setting the property ``is_swap_mapped`` to ``True`` or ``False`` + accordingly. Note this does not validate directionality of the connectivity between + qubits. If you need to check gates are implemented in a native direction + for a target use the :class:`~.CheckGateDirection` pass instead. """ - def __init__(self, coupling_map): + def __init__(self, coupling_map=None, target=None): """CheckMap initializer. Args: coupling_map (CouplingMap): Directed graph representing a coupling map. + target (Target): A target representing the target backend, if both + ``coupling_map`` and this are specified then this argument will take + precedence and ``coupling_map`` will be ignored. """ super().__init__() - self.coupling_map = coupling_map + if coupling_map is None and target is None: + self.qargs = None + else: + self.qargs = set() + if target is not None: + if target.qargs is not None: + for edge in target.qargs: + if len(edge) == 2: + self.qargs.add(edge) + self.qargs.add((edge[1], edge[0])) + else: + for edge in coupling_map.get_edges(): + self.qargs.add(edge) + self.qargs.add((edge[1], edge[0])) def run(self, dag): """Run the CheckMap pass on `dag`. @@ -46,12 +65,10 @@ def run(self, dag): self.property_set["is_swap_mapped"] = True - if self.coupling_map is None or len(self.coupling_map.graph) == 0: + if not self.qargs: return qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} - # Use dist matrix directly to avoid validation overhead - dist_matrix = self.coupling_map.distance_matrix for node in dag.op_nodes(include_directives=False): is_controlflow_op = isinstance(node.op, ControlFlowOp) if len(node.qargs) == 2 and not is_controlflow_op: @@ -59,7 +76,7 @@ def run(self, dag): continue physical_q0 = qubit_indices[node.qargs[0]] physical_q1 = qubit_indices[node.qargs[1]] - if dist_matrix[physical_q0, physical_q1] != 1: + if (physical_q0, physical_q1) not in self.qargs: self.property_set["check_map_msg"] = "{}({}, {}) failed".format( node.name, physical_q0, diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index ba4c1266ce68..ca4c54494d34 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -32,7 +32,11 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map backend_properties = pass_manager_config.backend_properties - routing_pass = BasicSwap(coupling_map) + if target is None: + routing_pass = BasicSwap(coupling_map) + else: + routing_pass = BasicSwap(target) + vf2_call_limit = common.get_vf2_call_limit( optimization_level, pass_manager_config.layout_method, @@ -88,6 +92,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana seed_transpiler = pass_manager_config.seed_transpiler target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map + coupling_map_routing = target + if coupling_map_routing is None: + coupling_map_routing = coupling_map backend_properties = pass_manager_config.backend_properties vf2_call_limit = common.get_vf2_call_limit( optimization_level, @@ -95,9 +102,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana pass_manager_config.initial_layout, ) if optimization_level == 3: - routing_pass = StochasticSwap(coupling_map, trials=200, seed=seed_transpiler) + routing_pass = StochasticSwap(coupling_map_routing, trials=200, seed=seed_transpiler) else: - routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) + routing_pass = StochasticSwap(coupling_map_routing, trials=20, seed=seed_transpiler) if optimization_level == 0: return common.generate_routing_passmanager( @@ -139,6 +146,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana seed_transpiler = pass_manager_config.seed_transpiler target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map + coupling_map_routing = target + if coupling_map_routing is None: + coupling_map_routing = coupling_map backend_properties = pass_manager_config.backend_properties vf2_call_limit = common.get_vf2_call_limit( optimization_level, @@ -146,7 +156,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana pass_manager_config.initial_layout, ) if optimization_level == 0: - routing_pass = LookaheadSwap(coupling_map, search_depth=2, search_width=2) + routing_pass = LookaheadSwap(coupling_map_routing, search_depth=2, search_width=2) return common.generate_routing_passmanager( routing_pass, target, @@ -155,7 +165,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 1: - routing_pass = LookaheadSwap(coupling_map, search_depth=4, search_width=4) + routing_pass = LookaheadSwap(coupling_map_routing, search_depth=4, search_width=4) return common.generate_routing_passmanager( routing_pass, target, @@ -167,7 +177,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 2: - routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) + routing_pass = LookaheadSwap(coupling_map_routing, search_depth=5, search_width=6) return common.generate_routing_passmanager( routing_pass, target, @@ -178,7 +188,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana use_barrier_before_measurement=True, ) if optimization_level == 3: - routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) + routing_pass = LookaheadSwap(coupling_map_routing, search_depth=5, search_width=6) return common.generate_routing_passmanager( routing_pass, target, @@ -199,6 +209,9 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana seed_transpiler = pass_manager_config.seed_transpiler target = pass_manager_config.target coupling_map = pass_manager_config.coupling_map + coupling_map_routing = target + if coupling_map_routing is None: + coupling_map_routing = coupling_map backend_properties = pass_manager_config.backend_properties vf2_call_limit = common.get_vf2_call_limit( optimization_level, @@ -207,7 +220,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 0: routing_pass = SabreSwap( - coupling_map, heuristic="basic", seed=seed_transpiler, trials=5 + coupling_map_routing, + heuristic="basic", + seed=seed_transpiler, + trials=5, ) return common.generate_routing_passmanager( routing_pass, @@ -218,7 +234,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 1: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=5 + coupling_map_routing, + heuristic="decay", + seed=seed_transpiler, + trials=5, ) return common.generate_routing_passmanager( routing_pass, @@ -232,7 +251,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 2: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=10 + coupling_map_routing, + heuristic="decay", + seed=seed_transpiler, + trials=10, ) return common.generate_routing_passmanager( routing_pass, @@ -245,7 +267,10 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) if optimization_level == 3: routing_pass = SabreSwap( - coupling_map, heuristic="decay", seed=seed_transpiler, trials=20 + coupling_map_routing, + heuristic="decay", + seed=seed_transpiler, + trials=20, ) return common.generate_routing_passmanager( routing_pass, diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 4d15fca99f24..04a1347a761a 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -202,7 +202,7 @@ def generate_embed_passmanager(coupling_map): that can be used to expand and apply an initial layout to a circuit Args: - coupling_map (CouplingMap): The coupling map for the backend to embed + coupling_map (Union[CouplingMap, Target): The coupling map for the backend to embed the circuit to. Returns: PassManager: The embedding passmanager that assumes the layout property @@ -273,7 +273,7 @@ def _run_post_layout_condition(property_set): return False routing = PassManager() - routing.append(CheckMap(coupling_map)) + routing.append(CheckMap(coupling_map, target=target)) def _swap_condition(property_set): return not property_set["is_swap_mapped"] diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index d137e16bbe88..ddb9560b0369 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -77,16 +77,24 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa def _choose_layout_condition(property_set): return not property_set["layout"] + if target is None: + coupling_map_layout = coupling_map + else: + coupling_map_layout = target + if layout_method == "trivial": - _choose_layout = TrivialLayout(coupling_map) + _choose_layout = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout = NoiseAdaptiveLayout(backend_properties) + if target is None: + _choose_layout = NoiseAdaptiveLayout(backend_properties) + else: + _choose_layout = NoiseAdaptiveLayout(target) elif layout_method == "sabre": skip_routing = pass_manager_config.routing_method is not None and routing_method != "sabre" _choose_layout = SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=1, seed=seed_transpiler, swap_trials=5, @@ -122,7 +130,7 @@ def _swap_mapped(property_set): layout = PassManager() layout.append(_given_layout) layout.append(_choose_layout, condition=_choose_layout_condition) - embed = common.generate_embed_passmanager(coupling_map) + embed = common.generate_embed_passmanager(coupling_map_layout) layout.append( [pass_ for x in embed.passes() for pass_ in x["passes"]], condition=_swap_mapped ) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 1ad77652005e..b5d435c9b6e3 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -113,10 +113,15 @@ def _vf2_match_not_found(property_set): return True return False + if target is None: + coupling_map_layout = coupling_map + else: + coupling_map_layout = target + _choose_layout_0 = ( [] if pass_manager_config.layout_method - else [TrivialLayout(coupling_map), CheckMap(coupling_map)] + else [TrivialLayout(coupling_map_layout), CheckMap(coupling_map, target=target)] ) _choose_layout_1 = ( @@ -132,14 +137,17 @@ def _vf2_match_not_found(property_set): ) if layout_method == "trivial": - _improve_layout = TrivialLayout(coupling_map) + _improve_layout = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _improve_layout = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _improve_layout = NoiseAdaptiveLayout(backend_properties) + if target is None: + _improve_layout = NoiseAdaptiveLayout(backend_properties) + else: + _improve_layout = NoiseAdaptiveLayout(target) elif layout_method == "sabre": _improve_layout = SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=2, seed=seed_transpiler, swap_trials=5, @@ -151,7 +159,7 @@ def _vf2_match_not_found(property_set): _improve_layout = common.if_has_control_flow_else( DenseLayout(coupling_map, backend_properties, target=target), SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=2, seed=seed_transpiler, swap_trials=5, @@ -222,7 +230,7 @@ def _swap_mapped(property_set): layout.append( [BarrierBeforeFinalMeasurements(), _improve_layout], condition=_vf2_match_not_found ) - embed = common.generate_embed_passmanager(coupling_map) + embed = common.generate_embed_passmanager(coupling_map_layout) layout.append( [pass_ for x in embed.passes() for pass_ in x["passes"]], condition=_swap_mapped ) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 61699ee9c2ab..c7313cf4475d 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -121,15 +121,23 @@ def _vf2_match_not_found(property_set): ) ) + if target is None: + coupling_map_layout = coupling_map + else: + coupling_map_layout = target + if layout_method == "trivial": - _choose_layout_1 = TrivialLayout(coupling_map) + _choose_layout_1 = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout_1 = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) + if target is None: + _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) + else: + _choose_layout_1 = NoiseAdaptiveLayout(target) elif layout_method == "sabre": _choose_layout_1 = SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=2, seed=seed_transpiler, swap_trials=10, @@ -182,7 +190,7 @@ def _swap_mapped(property_set): layout.append( [BarrierBeforeFinalMeasurements(), _choose_layout_1], condition=_vf2_match_not_found ) - embed = common.generate_embed_passmanager(coupling_map) + embed = common.generate_embed_passmanager(coupling_map_layout) layout.append( [pass_ for x in embed.passes() for pass_ in x["passes"]], condition=_swap_mapped ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 34dc592afc89..cc20771d9e54 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -126,16 +126,25 @@ def _vf2_match_not_found(property_set): target=target, ) ) + + if target is None: + coupling_map_layout = coupling_map + else: + coupling_map_layout = target + # 2b. if VF2 didn't converge on a solution use layout_method (dense). if layout_method == "trivial": - _choose_layout_1 = TrivialLayout(coupling_map) + _choose_layout_1 = TrivialLayout(coupling_map_layout) elif layout_method == "dense": _choose_layout_1 = DenseLayout(coupling_map, backend_properties, target=target) elif layout_method == "noise_adaptive": - _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) + if target is None: + _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) + else: + _choose_layout_1 = NoiseAdaptiveLayout(target) elif layout_method == "sabre": _choose_layout_1 = SabreLayout( - coupling_map, + coupling_map_layout, max_iterations=4, seed=seed_transpiler, swap_trials=20, @@ -214,7 +223,7 @@ def _swap_mapped(property_set): layout.append( [BarrierBeforeFinalMeasurements(), _choose_layout_1], condition=_vf2_match_not_found ) - embed = common.generate_embed_passmanager(coupling_map) + embed = common.generate_embed_passmanager(coupling_map_layout) layout.append( [pass_ for x in embed.passes() for pass_ in x["passes"]], condition=_swap_mapped ) diff --git a/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml b/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml new file mode 100644 index 000000000000..5cdf3739ac22 --- /dev/null +++ b/releasenotes/notes/target-aware-layout-routing-2b39bd87a9f928e7.yaml @@ -0,0 +1,30 @@ +--- +features: + - | + The following layout and routing transpiler passes from the + :mod:`.qiskit.transpiler.passes` modules now will support accepting a + :class:`~.Target` object which is used to model the constraints of a target + backend via the first positional argument (currently named either + ``coupling_map`` or ``backend_properties``). + + The list of these passes with the support for new ``target`` argument are: + + * :class:`~.CSPLayout` + * :class:`~.FullAncillaAllocation` + * :class:`~.Layout2qDistance` + * :class:`~.NoiseAdaptiveLayout` + * :class:`~.SabreLayout` + * :class:`~.TrivialLayout` + * :class:`~.BasicSwap` + * :class:`~.BIPMapping` + * :class:`~.LayoutTransformation` + * :class:`~.LookaheadSwap` + * :class:`~.SabreSwap` + * :class:`~.StochasticSwap` + * :class:`~.CheckMap` + - | + The pass manager construction helper function :func:`~.generate_embed_passmanager` + will now also accept a :class:`~.Target` for it's sole positional argument + (currently named ``coupling_map``). This can be used to construct a layout + embedding :class:`~.PassManager` from a :class:`~.Target` object instead of + from a :class:`~.CouplingMap`. diff --git a/test/python/transpiler/test_basic_swap.py b/test/python/transpiler/test_basic_swap.py index 90f4e76aa452..f9b9f7824eed 100644 --- a/test/python/transpiler/test_basic_swap.py +++ b/test/python/transpiler/test_basic_swap.py @@ -14,7 +14,8 @@ import unittest from qiskit.transpiler.passes import BasicSwap -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, Target +from qiskit.circuit.library import CXGate from qiskit.converters import circuit_to_dag from qiskit import QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase @@ -105,6 +106,40 @@ def test_a_single_swap(self): self.assertEqual(circuit_to_dag(expected), after) + def test_a_single_swap_with_target(self): + """Adding a swap + q0:------- + + q1:--(+)-- + | + q2:---.--- + + CouplingMap map: [1]--[0]--[2] + + q0:--X---.--- + | | + q1:--X---|--- + | + q2:-----(+)-- + + """ + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (0, 2): None}) + + qr = QuantumRegister(3, "q") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[2]) + dag = circuit_to_dag(circuit) + + expected = QuantumCircuit(qr) + expected.swap(qr[1], qr[0]) + expected.cx(qr[0], qr[2]) + + pass_ = BasicSwap(target) + after = pass_.run(dag) + + self.assertEqual(circuit_to_dag(expected), after) + def test_a_single_swap_bigger_cm(self): """Swapper in a bigger coupling map q0:------- diff --git a/test/python/transpiler/test_bip_mapping.py b/test/python/transpiler/test_bip_mapping.py index 742d4c920288..60283abec750 100644 --- a/test/python/transpiler/test_bip_mapping.py +++ b/test/python/transpiler/test_bip_mapping.py @@ -16,11 +16,11 @@ from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister from qiskit.circuit import Barrier -from qiskit.circuit.library.standard_gates import SwapGate +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 +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 @@ -127,6 +127,28 @@ def test_can_map_measurements_correctly(self): 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]) + + 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 diff --git a/test/python/transpiler/test_check_map.py b/test/python/transpiler/test_check_map.py index e5d508da52ed..329eb7edc7cc 100644 --- a/test/python/transpiler/test_check_map.py +++ b/test/python/transpiler/test_check_map.py @@ -15,8 +15,9 @@ import unittest from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister +from qiskit.circuit.library import CXGate from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, Target from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase @@ -43,6 +44,25 @@ def test_trivial_nop_map(self): pass_.run(dag) self.assertTrue(pass_.property_set["is_swap_mapped"]) + def test_trivial_nop_map_target(self): + """Trivial map in a circuit without entanglement + qr0:---[H]--- + + qr1:---[H]--- + + qr2:---[H]--- + + CouplingMap map: None + """ + qr = QuantumRegister(3, "qr") + circuit = QuantumCircuit(qr) + circuit.h(qr) + target = Target() + dag = circuit_to_dag(circuit) + pass_ = CheckMap(target=target) + pass_.run(dag) + self.assertTrue(pass_.property_set["is_swap_mapped"]) + def test_swap_mapped_true(self): """Mapped is easy to check qr0:--(+)-[H]-(+)- @@ -85,6 +105,26 @@ def test_swap_mapped_false(self): self.assertFalse(pass_.property_set["is_swap_mapped"]) + def test_swap_mapped_false_target(self): + """Needs [0]-[1] in a [0]--[2]--[1] + qr0:--(+)-- + | + qr1:---.--- + + CouplingMap map: [0]--[2]--[1] + """ + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + target = Target(num_qubits=2) + target.add_instruction(CXGate(), {(0, 2): None, (2, 1): None}) + dag = circuit_to_dag(circuit) + + pass_ = CheckMap(target=target) + pass_.run(dag) + + self.assertFalse(pass_.property_set["is_swap_mapped"]) + def test_swap_mapped_cf_true(self): """Check control flow blocks are mapped.""" num_qubits = 3 diff --git a/test/python/transpiler/test_csp_layout.py b/test/python/transpiler/test_csp_layout.py index 86564c553a0d..85163b120a8b 100644 --- a/test/python/transpiler/test_csp_layout.py +++ b/test/python/transpiler/test_csp_layout.py @@ -20,7 +20,7 @@ from qiskit.transpiler.passes import CSPLayout from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase -from qiskit.providers.fake_provider import FakeTenerife, FakeRueschlikon, FakeTokyo +from qiskit.providers.fake_provider import FakeTenerife, FakeRueschlikon, FakeTokyo, FakeYorktownV2 class TestCSPLayout(QiskitTestCase): @@ -72,6 +72,32 @@ def test_3q_circuit_5q_coupling(self): self.assertEqual(layout[qr[2]], 4) self.assertEqual(pass_.property_set["CSPLayout_stop_reason"], "solution found") + def test_3q_circuit_5q_coupling_with_target(self): + """3 qubits in Yorktown, without considering the direction + qr1 + / | + qr0 - qr2 - 3 + | / + 4 + """ + target = FakeYorktownV2().target + + qr = QuantumRegister(3, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) # qr1 -> qr0 + circuit.cx(qr[0], qr[2]) # qr0 -> qr2 + circuit.cx(qr[1], qr[2]) # qr1 -> qr2 + + dag = circuit_to_dag(circuit) + pass_ = CSPLayout(target, strict_direction=False, seed=self.seed) + pass_.run(dag) + layout = pass_.property_set["layout"] + + self.assertEqual(layout[qr[0]], 3) + self.assertEqual(layout[qr[1]], 2) + self.assertEqual(layout[qr[2]], 4) + self.assertEqual(pass_.property_set["CSPLayout_stop_reason"], "solution found") + def test_9q_circuit_16q_coupling(self): """9 qubits in Rueschlikon, without considering the direction q0[1] - q0[0] - q1[3] - q0[3] - q1[0] - q1[1] - q1[2] - 8 diff --git a/test/python/transpiler/test_full_ancilla_allocation.py b/test/python/transpiler/test_full_ancilla_allocation.py index ccf3e321f335..741b3b98a2b4 100644 --- a/test/python/transpiler/test_full_ancilla_allocation.py +++ b/test/python/transpiler/test_full_ancilla_allocation.py @@ -16,7 +16,8 @@ from qiskit.circuit import QuantumRegister, QuantumCircuit from qiskit.converters import circuit_to_dag -from qiskit.transpiler import CouplingMap, Layout +from qiskit.transpiler import CouplingMap, Layout, Target +from qiskit.circuit.library import CXGate from qiskit.transpiler.passes import FullAncillaAllocation from qiskit.test import QiskitTestCase from qiskit.transpiler.exceptions import TranspilerError @@ -61,6 +62,41 @@ def test_3q_circuit_5q_coupling(self): self.assertEqual(after_layout[3], ancilla[0]) self.assertEqual(after_layout[4], ancilla[1]) + def test_3q_circuit_5q_target(self): + """Allocates 2 ancillas for a 3q circuit in a 5q coupling map + + 0 -> q0 + q0 -> 0 1 -> q1 + q1 -> 1 => 2 -> q2 + q2 -> 2 3 -> ancilla0 + 4 -> ancilla1 + """ + target = Target(num_qubits=5) + target.add_instruction(CXGate(), {edge: None for edge in self.cmap5.get_edges()}) + + qr = QuantumRegister(3, "q") + circ = QuantumCircuit(qr) + dag = circuit_to_dag(circ) + + initial_layout = Layout() + initial_layout[0] = qr[0] + initial_layout[1] = qr[1] + initial_layout[2] = qr[2] + + pass_ = FullAncillaAllocation(target) + pass_.property_set["layout"] = initial_layout + + pass_.run(dag) + after_layout = pass_.property_set["layout"] + + ancilla = QuantumRegister(2, "ancilla") + + self.assertEqual(after_layout[0], qr[0]) + self.assertEqual(after_layout[1], qr[1]) + self.assertEqual(after_layout[2], qr[2]) + self.assertEqual(after_layout[3], ancilla[0]) + self.assertEqual(after_layout[4], ancilla[1]) + def test_3q_with_holes_5q_coupling(self): """Allocates 3 ancillas for a 2q circuit on a 5q coupling, with holes diff --git a/test/python/transpiler/test_layout_score.py b/test/python/transpiler/test_layout_score.py index b1d3db9c386d..7d60b24d0062 100644 --- a/test/python/transpiler/test_layout_score.py +++ b/test/python/transpiler/test_layout_score.py @@ -15,9 +15,11 @@ import unittest from qiskit import QuantumRegister, QuantumCircuit +from qiskit.circuit.library import CXGate from qiskit.transpiler.passes import Layout2qDistance from qiskit.transpiler import CouplingMap, Layout from qiskit.converters import circuit_to_dag +from qiskit.transpiler.target import Target from qiskit.test import QiskitTestCase @@ -101,6 +103,54 @@ def test_swap_mapped_false(self): self.assertEqual(pass_.property_set["layout_score"], 1) + def test_swap_mapped_true_target(self): + """Mapped circuit. Good Layout + qr0 (0):--(+)---(+)- + | | + qr1 (1):---.-----|-- + | + qr2 (2):---------.-- + + CouplingMap map: [1]--[0]--[2] + """ + qr = QuantumRegister(3, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + circuit.cx(qr[0], qr[2]) + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (0, 2): None}) + layout = Layout().generate_trivial_layout(qr) + + dag = circuit_to_dag(circuit) + pass_ = Layout2qDistance(target) + pass_.property_set["layout"] = layout + pass_.run(dag) + + self.assertEqual(pass_.property_set["layout_score"], 0) + + def test_swap_mapped_false_target(self): + """Needs [0]-[1] in a [0]--[2]--[1] Result:1 + qr0:--(+)-- + | + qr1:---.--- + + CouplingMap map: [0]--[2]--[1] + """ + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + target = Target() + target.add_instruction(CXGate(), {(0, 2): None, (2, 1): None}) + + layout = Layout().generate_trivial_layout(qr) + + dag = circuit_to_dag(circuit) + pass_ = Layout2qDistance(target) + pass_.property_set["layout"] = layout + pass_.run(dag) + + self.assertEqual(pass_.property_set["layout_score"], 1) + if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_layout_transformation.py b/test/python/transpiler/test_layout_transformation.py index 5fe2fe315bc0..6d38f744ced1 100644 --- a/test/python/transpiler/test_layout_transformation.py +++ b/test/python/transpiler/test_layout_transformation.py @@ -17,7 +17,8 @@ from qiskit import QuantumRegister, QuantumCircuit from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase -from qiskit.transpiler import CouplingMap, Layout +from qiskit.transpiler import CouplingMap, Layout, Target +from qiskit.circuit.library import CXGate from qiskit.transpiler.passes import LayoutTransformation @@ -65,6 +66,25 @@ def test_four_qubit(self): self.assertEqual(circuit_to_dag(expected), output_dag) + def test_four_qubit_with_target(self): + """Test if the permutation {0->3,1->0,2->1,3->2} is implemented correctly.""" + v = QuantumRegister(4, "v") # virtual qubits + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (1, 2): None, (2, 3): None}) + from_layout = Layout({v[0]: 0, v[1]: 1, v[2]: 2, v[3]: 3}) + to_layout = Layout({v[0]: 3, v[1]: 0, v[2]: 1, v[3]: 2}) + ltpass = LayoutTransformation(target, from_layout=from_layout, to_layout=to_layout, seed=42) + qc = QuantumCircuit(4) # input (empty) physical circuit + dag = circuit_to_dag(qc) + output_dag = ltpass.run(dag) + + expected = QuantumCircuit(4) + expected.swap(1, 0) + expected.swap(1, 2) + expected.swap(2, 3) + + self.assertEqual(circuit_to_dag(expected), output_dag) + def test_full_connected_coupling_map(self): """Test if the permutation {0->3,1->0,2->1,3->2} in a fully connected map.""" v = QuantumRegister(4, "v") # virtual qubits diff --git a/test/python/transpiler/test_lookahead_swap.py b/test/python/transpiler/test_lookahead_swap.py index c28a6f62d681..70e8ae111daa 100644 --- a/test/python/transpiler/test_lookahead_swap.py +++ b/test/python/transpiler/test_lookahead_swap.py @@ -16,8 +16,9 @@ from numpy import pi from qiskit.dagcircuit import DAGCircuit from qiskit.transpiler.passes import LookaheadSwap -from qiskit.transpiler import CouplingMap +from qiskit.transpiler import CouplingMap, Target from qiskit.converters import circuit_to_dag +from qiskit.circuit.library import CXGate from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase from qiskit.providers.fake_provider import FakeMelbourne @@ -125,6 +126,35 @@ def test_lookahead_swap_maps_measurements(self): self.assertIn(mapped_measure_qargs, [{qr[0], qr[1]}, {qr[1], qr[2]}]) + def test_lookahead_swap_maps_measurements_with_target(self): + """Verify measurement nodes are updated to map correct cregs to re-mapped qregs. + + Create a circuit with measures on q0 and q2, following a swap between q0 and q2. + Since that swap is not in the coupling, one of the two will be required to move. + Verify that the mapped measure corresponds to one of the two possible layouts following + the swap. + + """ + + qr = QuantumRegister(3, "q") + cr = ClassicalRegister(2) + circuit = QuantumCircuit(qr, cr) + + circuit.cx(qr[0], qr[2]) + circuit.measure(qr[0], cr[0]) + circuit.measure(qr[2], cr[1]) + + dag_circuit = circuit_to_dag(circuit) + + target = Target() + target.add_instruction(CXGate(), {(0, 1): None, (1, 2): None}) + + mapped_dag = LookaheadSwap(target).run(dag_circuit) + + mapped_measure_qargs = {op.qargs[0] for op in mapped_dag.named_nodes("measure")} + + self.assertIn(mapped_measure_qargs, [{qr[0], qr[1]}, {qr[1], qr[2]}]) + def test_lookahead_swap_maps_barriers(self): """Verify barrier nodes are updated to re-mapped qregs. diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 7ed0f69e768f..7a0ebbfba692 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -20,7 +20,7 @@ from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase from qiskit.compiler.transpiler import transpile -from qiskit.providers.fake_provider import FakeAlmaden +from qiskit.providers.fake_provider import FakeAlmaden, FakeAlmadenV2 from qiskit.providers.fake_provider import FakeKolkata from qiskit.providers.fake_provider import FakeMontreal @@ -93,6 +93,39 @@ def test_6q_circuit_20q_coupling(self): layout = pass_.property_set["layout"] self.assertEqual([layout[q] for q in circuit.qubits], [7, 8, 12, 6, 11, 13]) + def test_6q_circuit_20q_coupling_with_target(self): + """Test finds layout for 6q circuit on 20q device.""" + # ┌───┐┌───┐┌───┐┌───┐┌───┐ + # q0_0: ┤ X ├┤ X ├┤ X ├┤ X ├┤ X ├ + # └─┬─┘└─┬─┘└─┬─┘└─┬─┘└─┬─┘ + # q0_1: ──┼────■────┼────┼────┼── + # │ ┌───┐ │ │ │ + # q0_2: ──┼──┤ X ├──┼────■────┼── + # │ └───┘ │ │ + # q1_0: ──■─────────┼─────────┼── + # ┌───┐ │ │ + # q1_1: ─────┤ X ├──┼─────────■── + # └───┘ │ + # q1_2: ────────────■──────────── + qr0 = QuantumRegister(3, "q0") + qr1 = QuantumRegister(3, "q1") + circuit = QuantumCircuit(qr0, qr1) + circuit.cx(qr1[0], qr0[0]) + circuit.cx(qr0[1], qr0[0]) + circuit.cx(qr1[2], qr0[0]) + circuit.x(qr0[2]) + circuit.cx(qr0[2], qr0[0]) + circuit.x(qr1[1]) + circuit.cx(qr1[1], qr0[0]) + + dag = circuit_to_dag(circuit) + target = FakeAlmadenV2().target + pass_ = SabreLayout(target, seed=0, swap_trials=32, layout_trials=32) + pass_.run(dag) + + layout = pass_.property_set["layout"] + self.assertEqual([layout[q] for q in circuit.qubits], [7, 8, 12, 6, 11, 13]) + def test_layout_with_classical_bits(self): """Test sabre layout with classical bits recreate from issue #8635.""" qc = QuantumCircuit.from_qasm_str( diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index e1fc30bfa33b..1b6ee4641799 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -19,7 +19,7 @@ from qiskit.circuit.library import CCXGate, HGate, Measure, SwapGate from qiskit.converters import circuit_to_dag from qiskit.transpiler.passes import SabreSwap, TrivialLayout -from qiskit.transpiler import CouplingMap, PassManager, TranspilerError +from qiskit.transpiler import CouplingMap, PassManager, Target, TranspilerError from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.test import QiskitTestCase from qiskit.utils import optionals @@ -121,6 +121,27 @@ def test_trivial_case(self): self.assertEqual(new_qc, qc) + def test_trivial_with_target(self): + """Test that an already mapped circuit is unchanged with target.""" + coupling = CouplingMap.from_ring(5) + target = Target(num_qubits=5) + target.add_instruction(SwapGate(), {edge: None for edge in coupling.get_edges()}) + + qr = QuantumRegister(5, "q") + qc = QuantumCircuit(qr) + qc.cx(0, 1) # free + qc.cx(2, 3) # free + qc.h(0) # free + qc.cx(1, 2) # F + qc.cx(1, 0) + qc.cx(4, 3) # F + qc.cx(0, 4) + + passmanager = PassManager(SabreSwap(target, "basic")) + new_qc = passmanager.run(qc) + + self.assertEqual(new_qc, qc) + def test_lookahead_mode(self): """Test lookahead mode's lookahead finds single SWAP gate. ┌───┐ diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py index 1065044715ac..6816bec649af 100644 --- a/test/python/transpiler/test_stochastic_swap.py +++ b/test/python/transpiler/test_stochastic_swap.py @@ -25,7 +25,7 @@ from qiskit.test import QiskitTestCase from qiskit.transpiler.passes.utils import CheckMap from qiskit.circuit.random import random_circuit -from qiskit.providers.fake_provider import FakeMumbai +from qiskit.providers.fake_provider import FakeMumbai, FakeMumbaiV2 from qiskit.compiler.transpiler import transpile from qiskit.circuit import ControlFlowOp, Clbit @@ -1268,6 +1268,19 @@ def test_random_circuit_no_control_flow(self, size): ) self.assert_valid_circuit(tqc) + @data(*range(1, 27)) + def test_random_circuit_no_control_flow_target(self, size): + """Test that transpiled random circuits without control flow are physical circuits.""" + circuit = random_circuit(size, 3, measure=True, seed=12342) + tqc = transpile( + circuit, + routing_method="stochastic", + layout_method="dense", + seed_transpiler=12342, + target=FakeMumbaiV2().target, + ) + self.assert_valid_circuit(tqc) + @data(*range(4, 27)) def test_random_circuit_for_loop(self, size): """Test that transpiled random circuits with nested for loops are physical circuits.""" diff --git a/test/python/transpiler/test_trivial_layout.py b/test/python/transpiler/test_trivial_layout.py index f75478676b34..89c3acf1db48 100644 --- a/test/python/transpiler/test_trivial_layout.py +++ b/test/python/transpiler/test_trivial_layout.py @@ -17,6 +17,8 @@ from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.transpiler import CouplingMap from qiskit.transpiler.passes import TrivialLayout +from qiskit.transpiler.target import Target +from qiskit.circuit.library import CXGate from qiskit.transpiler import TranspilerError from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase @@ -47,6 +49,24 @@ def test_3q_circuit_5q_coupling(self): for i in range(3): self.assertEqual(layout[qr[i]], i) + def test_3q_circuit_5q_coupling_with_target(self): + """Test finds trivial layout for 3q circuit on 5q device.""" + qr = QuantumRegister(3, "q") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) + circuit.cx(qr[0], qr[2]) + circuit.cx(qr[1], qr[2]) + + dag = circuit_to_dag(circuit) + target = Target() + target.add_instruction(CXGate(), {tuple(edge): None for edge in self.cmap5}) + pass_ = TrivialLayout(target) + pass_.run(dag) + layout = pass_.property_set["layout"] + + for i in range(3): + self.assertEqual(layout[qr[i]], i) + def test_9q_circuit_16q_coupling(self): """Test finds trivial layout for 9q circuit with 2 registers on 16q device.""" qr0 = QuantumRegister(4, "q0") diff --git a/test/python/visualization/references/pass_manager_standard.dot b/test/python/visualization/references/pass_manager_standard.dot index d4b1d30422d5..c3af9aaa37af 100644 --- a/test/python/visualization/references/pass_manager_standard.dot +++ b/test/python/visualization/references/pass_manager_standard.dot @@ -53,37 +53,39 @@ fontname=helvetica; label="[5] "; labeljust=l; 16 [color=red, fontname=helvetica, label=CheckMap, shape=rectangle]; -17 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +17 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; 17 -> 16; +18 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +18 -> 16; 12 -> 16; } -subgraph cluster_18 { +subgraph cluster_19 { fontname=helvetica; label="[6] do_while"; labeljust=l; -19 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; -16 -> 19; +20 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +16 -> 20; } -subgraph cluster_20 { +subgraph cluster_21 { fontname=helvetica; label="[7] "; labeljust=l; -21 [color=blue, fontname=helvetica, label=GateDirection, shape=rectangle]; -22 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -22 -> 21; -23 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -23 -> 21; -19 -> 21; +22 [color=blue, fontname=helvetica, label=GateDirection, shape=rectangle]; +23 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +23 -> 22; +24 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +24 -> 22; +20 -> 22; } -subgraph cluster_24 { +subgraph cluster_25 { fontname=helvetica; label="[8] "; labeljust=l; -25 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -21 -> 25; +26 [color=blue, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +22 -> 26; } } diff --git a/test/python/visualization/references/pass_manager_style.dot b/test/python/visualization/references/pass_manager_style.dot index 5cfd2a95ea56..54815259b050 100644 --- a/test/python/visualization/references/pass_manager_style.dot +++ b/test/python/visualization/references/pass_manager_style.dot @@ -53,37 +53,39 @@ fontname=helvetica; label="[5] "; labeljust=l; 16 [color=green, fontname=helvetica, label=CheckMap, shape=rectangle]; -17 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +17 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=dashed]; 17 -> 16; +18 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +18 -> 16; 12 -> 16; } -subgraph cluster_18 { +subgraph cluster_19 { fontname=helvetica; label="[6] do_while"; labeljust=l; -19 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; -16 -> 19; +20 [color=blue, fontname=helvetica, label=BarrierBeforeFinalMeasurements, shape=rectangle]; +16 -> 20; } -subgraph cluster_20 { +subgraph cluster_21 { fontname=helvetica; label="[7] "; labeljust=l; -21 [color=blue, fontname=helvetica, label=GateDirection, shape=rectangle]; -22 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; -22 -> 21; -23 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; -23 -> 21; -19 -> 21; +22 [color=blue, fontname=helvetica, label=GateDirection, shape=rectangle]; +23 [color=black, fontname=helvetica, fontsize=10, label=coupling_map, shape=ellipse, style=solid]; +23 -> 22; +24 [color=black, fontname=helvetica, fontsize=10, label=target, shape=ellipse, style=dashed]; +24 -> 22; +20 -> 22; } -subgraph cluster_24 { +subgraph cluster_25 { fontname=helvetica; label="[8] "; labeljust=l; -25 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; -21 -> 25; +26 [color=grey, fontname=helvetica, label=RemoveResetInZeroState, shape=rectangle]; +22 -> 26; } }