From dc280c881991cc19b92d4da379b12b8d27e1abeb Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 17 Apr 2023 06:58:27 -0400 Subject: [PATCH] Add full path transpile() support for disjoint backends (#9840) * Add full path transpile() support for disjoint backends This commit finalizes the last piece of the full path transpile() support at all optimization levels. The primary piece to accomplish this was adding DenseLayout support for targeting a disjoint CouplingMap. With this piece added then all the default transpiler paths in the preset pass managers are able to support running with a disjoint CouplingMap. The biggest exception is the TrivialLayout pass which can result in an invalid layout being selected (where 2q connectivity crosses connected components). To handle this edge case a check is added to each routing stage to ensure the selected layout is valid for the coupling map and it is routable. This enables all the default paths at all 4 optimization levels for transpile() to be usable with disjoint connectivity. For optimization_level=0/TrivialLayout if the trivial layout is invalid the routing pass will raise an error saying as much. It's worth pointing out that NoiseAdaptiveLayout still doesn't support disjoint connectivity. However, since it's performance is poor and it's not used by default in any preset passmanager we can add this at a later date, but all other combinations of layout and routing methods should work (given the caveats with TrivialLayout) above. * Fix test determinism * Add future facing test for shared classical bits between components * Enable shared control flow test Now that #9802 supports shared classical bits correctly this commit re-enables the tests disabled in the previous commit. * Remove unused condition for faulty qubits backnedv1 path * Remove opt level 1 variant of test_chained_data_dependency * Use enumerate() in check_layout_isolated_to_component() Co-authored-by: Kevin Hartman * Expand level 0 test coverage * Update test/python/compiler/test_transpiler.py Co-authored-by: Kevin Hartman * s/check_layout_isolated_to_component/require_layout_isolated_to_component/g --------- Co-authored-by: Kevin Hartman --- .../transpiler/passes/layout/dense_layout.py | 85 ++--- .../passes/layout/disjoint_utils.py | 15 + .../passes/layout/trivial_layout.py | 5 - .../transpiler/passes/routing/basic_swap.py | 2 + .../transpiler/passes/routing/bip_mapping.py | 2 + .../passes/routing/lookahead_swap.py | 2 + .../transpiler/passes/routing/sabre_swap.py | 2 + .../passes/routing/stochastic_swap.py | 3 + test/python/compiler/test_transpiler.py | 291 +++++++++++++++++- test/python/transpiler/test_dense_layout.py | 14 +- 10 files changed, 363 insertions(+), 58 deletions(-) diff --git a/qiskit/transpiler/passes/layout/dense_layout.py b/qiskit/transpiler/passes/layout/dense_layout.py index cd87fad134ca..be29c28e519d 100644 --- a/qiskit/transpiler/passes/layout/dense_layout.py +++ b/qiskit/transpiler/passes/layout/dense_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.passes.layout import disjoint_utils from qiskit._accelerate.dense_layout import best_subset @@ -47,21 +48,9 @@ def __init__(self, coupling_map=None, backend_prop=None, target=None): self.coupling_map = coupling_map self.backend_prop = backend_prop self.target = target - num_qubits = 0 self.adjacency_matrix = None if target is not None: - num_qubits = target.num_qubits self.coupling_map = target.build_coupling_map() - if self.coupling_map is not None: - self.adjacency_matrix = rustworkx.adjacency_matrix(self.coupling_map.graph) - self.error_mat, self._use_error = _build_error_matrix(num_qubits, target=target) - else: - if self.coupling_map: - num_qubits = self.coupling_map.size() - self.adjacency_matrix = rustworkx.adjacency_matrix(self.coupling_map.graph) - self.error_mat, self._use_error = _build_error_matrix( - num_qubits, backend_prop=self.backend_prop, coupling_map=self.coupling_map - ) def run(self, dag): """Run the DenseLayout pass on `dag`. @@ -79,13 +68,20 @@ def run(self, dag): raise TranspilerError( "A coupling_map or target with constrained qargs is necessary to run the pass." ) - if dag.num_qubits() > len(self.coupling_map.largest_connected_component()): - raise TranspilerError( - "Coupling Map is disjoint, this pass can't be used with a disconnected coupling " - "map for a circuit this wide." - ) + layout_components = disjoint_utils.run_pass_over_connected_components( + dag, self.coupling_map, self._inner_run + ) + layout_mapping = {} + for component in layout_components: + layout_mapping.update(component) + layout = Layout(layout_mapping) + for qreg in dag.qregs.values(): + layout.add_register(qreg) + self.property_set["layout"] = layout + + def _inner_run(self, dag, coupling_map): num_dag_qubits = len(dag.qubits) - if num_dag_qubits > self.coupling_map.size(): + if num_dag_qubits > coupling_map.size(): raise TranspilerError("Number of qubits greater than device.") num_cx = 0 num_meas = 0 @@ -101,15 +97,13 @@ def run(self, dag): if "measure" in ops.keys(): num_meas = ops["measure"] - best_sub = self._best_subset(num_dag_qubits, num_meas, num_cx) - layout = Layout() - for i, qubit in enumerate(dag.qubits): - layout.add(qubit, int(best_sub[i])) - for qreg in dag.qregs.values(): - layout.add_register(qreg) - self.property_set["layout"] = layout + best_sub = self._best_subset(num_dag_qubits, num_meas, num_cx, coupling_map) + layout_mapping = { + qubit: coupling_map.graph[int(best_sub[i])] for i, qubit in enumerate(dag.qubits) + } + return layout_mapping - def _best_subset(self, num_qubits, num_meas, num_cx): + def _best_subset(self, num_qubits, num_meas, num_cx, coupling_map): """Computes the qubit mapping with the best connectivity. Args: @@ -125,14 +119,25 @@ def _best_subset(self, num_qubits, num_meas, num_cx): if num_qubits == 0: return [] + adjacency_matrix = rustworkx.adjacency_matrix(coupling_map.graph) + reverse_index_map = {v: k for k, v in enumerate(coupling_map.graph.nodes())} + + error_mat, use_error = _build_error_matrix( + coupling_map.size(), + reverse_index_map, + backend_prop=self.backend_prop, + coupling_map=self.coupling_map, + target=self.target, + ) + rows, cols, best_map = best_subset( num_qubits, - self.adjacency_matrix, + adjacency_matrix, num_meas, num_cx, - self._use_error, - self.coupling_map.is_symmetric, - self.error_mat, + use_error, + coupling_map.is_symmetric, + error_mat, ) data = [1] * len(rows) sp_sub_graph = coo_matrix((data, (rows, cols)), shape=(num_qubits, num_qubits)).tocsr() @@ -141,7 +146,7 @@ def _best_subset(self, num_qubits, num_meas, num_cx): return best_map -def _build_error_matrix(num_qubits, target=None, coupling_map=None, backend_prop=None): +def _build_error_matrix(num_qubits, qubit_map, target=None, coupling_map=None, backend_prop=None): error_mat = np.zeros((num_qubits, num_qubits)) use_error = False if target is not None and target.qargs is not None: @@ -161,13 +166,15 @@ def _build_error_matrix(num_qubits, target=None, coupling_map=None, backend_prop # the possible worst case. error = max(error, props.error) max_error = error + if any(qubit not in qubit_map for qubit in qargs): + continue # TODO: Factor in T1 and T2 to error matrix after #7736 if len(qargs) == 1: - qubit = qargs[0] + qubit = qubit_map[qargs[0]] error_mat[qubit][qubit] = max_error use_error = True elif len(qargs) == 2: - error_mat[qargs[0]][qargs[1]] = max_error + error_mat[qubit_map[qargs[0]]][qubit_map[qargs[1]]] = max_error use_error = True elif backend_prop and coupling_map: error_dict = { @@ -178,14 +185,16 @@ def _build_error_matrix(num_qubits, target=None, coupling_map=None, backend_prop for edge in coupling_map.get_edges(): gate_error = error_dict.get(edge) if gate_error is not None: - error_mat[edge[0]][edge[1]] = gate_error + if edge[0] not in qubit_map or edge[1] not in qubit_map: + continue + error_mat[qubit_map[edge[0]]][qubit_map[edge[1]]] = gate_error use_error = True for index, qubit_data in enumerate(backend_prop.qubits): - # Handle faulty qubits edge case - if index >= num_qubits: - break + if index not in qubit_map: + continue for item in qubit_data: if item.name == "readout_error": - error_mat[index][index] = item.value + mapped_index = qubit_map[index] + error_mat[mapped_index][mapped_index] = item.value use_error = True return error_mat, use_error diff --git a/qiskit/transpiler/passes/layout/disjoint_utils.py b/qiskit/transpiler/passes/layout/disjoint_utils.py index f4f55a352e1e..e7d7c9756193 100644 --- a/qiskit/transpiler/passes/layout/disjoint_utils.py +++ b/qiskit/transpiler/passes/layout/disjoint_utils.py @@ -127,6 +127,21 @@ def combine_barriers(dag: DAGCircuit, retain_uuid: bool = True): node.op.label = None +def require_layout_isolated_to_component(dag: DAGCircuit, coupling_map: CouplingMap) -> bool: + """Check that the layout of the dag does not require connectivity across connected components + in the CouplingMap""" + qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} + component_sets = [set(x.graph.nodes()) for x in coupling_map.connected_components()] + for inst in dag.two_qubit_ops(): + component_index = None + for i, component_set in enumerate(component_sets): + if qubit_indices[inst.qargs[0]] in component_set: + component_index = i + break + if qubit_indices[inst.qargs[1]] not in component_sets[component_index]: + raise TranspilerError("Chosen layout is not valid for the target disjoint connectivity") + + def separate_dag(dag: DAGCircuit) -> List[DAGCircuit]: """Separate a dag circuit into it's connected components.""" # Split barriers into single qubit barriers before splitting connected components diff --git a/qiskit/transpiler/passes/layout/trivial_layout.py b/qiskit/transpiler/passes/layout/trivial_layout.py index 5f1457d094bc..74fdc5b4ec30 100644 --- a/qiskit/transpiler/passes/layout/trivial_layout.py +++ b/qiskit/transpiler/passes/layout/trivial_layout.py @@ -61,11 +61,6 @@ def run(self, dag): 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." - ) 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 91a4d4ded611..f286015b7b3d 100644 --- a/qiskit/transpiler/passes/routing/basic_swap.py +++ b/qiskit/transpiler/passes/routing/basic_swap.py @@ -18,6 +18,7 @@ from qiskit.transpiler.layout import Layout from qiskit.circuit.library.standard_gates import SwapGate from qiskit.transpiler.target import Target +from qiskit.transpiler.passes.layout import disjoint_utils class BasicSwap(TransformationPass): @@ -71,6 +72,7 @@ def run(self, dag): if len(dag.qubits) > len(self.coupling_map.physical_qubits): raise TranspilerError("The layout does not match the amount of qubits in the DAG") + disjoint_utils.require_layout_isolated_to_component(dag, self.coupling_map) canonical_register = dag.qregs["q"] trivial_layout = Layout.generate_trivial_layout(canonical_register) diff --git a/qiskit/transpiler/passes/routing/bip_mapping.py b/qiskit/transpiler/passes/routing/bip_mapping.py index 58b040a9821c..e2e87f1761ca 100644 --- a/qiskit/transpiler/passes/routing/bip_mapping.py +++ b/qiskit/transpiler/passes/routing/bip_mapping.py @@ -24,6 +24,7 @@ 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__) @@ -166,6 +167,7 @@ def run(self, dag): "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) original_dag = dag diff --git a/qiskit/transpiler/passes/routing/lookahead_swap.py b/qiskit/transpiler/passes/routing/lookahead_swap.py index bb0476ee4f41..92c0fc5adf35 100644 --- a/qiskit/transpiler/passes/routing/lookahead_swap.py +++ b/qiskit/transpiler/passes/routing/lookahead_swap.py @@ -23,6 +23,7 @@ from qiskit.transpiler.layout import Layout from qiskit.dagcircuit import DAGOpNode from qiskit.transpiler.target import Target +from qiskit.transpiler.passes.layout import disjoint_utils logger = logging.getLogger(__name__) @@ -129,6 +130,7 @@ def run(self, dag): f"The number of DAG qubits ({len(dag.qubits)}) is greater than the number of " f"available device qubits ({number_of_available_qubits})." ) + disjoint_utils.require_layout_isolated_to_component(dag, self.coupling_map) register = dag.qregs["q"] current_state = _SystemState( diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index 3fef57f16f66..54693b68984d 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -22,6 +22,7 @@ from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.layout import Layout from qiskit.transpiler.target import Target +from qiskit.transpiler.passes.layout import disjoint_utils from qiskit.dagcircuit import DAGOpNode from qiskit.tools.parallel import CPU_COUNT @@ -212,6 +213,7 @@ def run(self, dag): heuristic = Heuristic.Decay else: raise TranspilerError("Heuristic %s not recognized." % self.heuristic) + disjoint_utils.require_layout_isolated_to_component(dag, self.coupling_map) self.dist_matrix = self.coupling_map.distance_matrix diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index df549ef848bc..2e65c0e90168 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -28,6 +28,7 @@ from qiskit.circuit import IfElseOp, WhileLoopOp, ForLoopOp, ControlFlowOp, Instruction from qiskit._accelerate import stochastic_swap as stochastic_swap_rs from qiskit._accelerate import nlayout +from qiskit.transpiler.passes.layout import disjoint_utils from .utils import get_swap_map_dag @@ -104,6 +105,8 @@ def run(self, dag): if len(dag.qubits) > len(self.coupling_map.physical_qubits): raise TranspilerError("The layout does not match the amount of qubits in the DAG") + disjoint_utils.require_layout_isolated_to_component(dag, self.coupling_map) + self.rng = np.random.default_rng(self.seed) canonical_register = dag.qregs["q"] diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 0dcc5ba9ba81..b3b9f08411aa 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -29,7 +29,7 @@ from qiskit.exceptions import QiskitError from qiskit import BasicAer from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, pulse, qpy, qasm3 -from qiskit.circuit import Parameter, Gate, Qubit, Clbit +from qiskit.circuit import Parameter, Gate, Qubit, Clbit, Reset from qiskit.compiler import transpile from qiskit.dagcircuit import DAGOutNode, DAGOpNode from qiskit.converters import circuit_to_dag @@ -2101,8 +2101,7 @@ def run(self, circuit, **kwargs): self.backend = FakeMultiChip() - # Add level 0 and 1 when TrivialLayout supports disjoint coupling maps - @data(2, 3) + @data(0, 1, 2, 3) def test_basic_connected_circuit(self, opt_level): """Test basic connected circuit on disjoint backend""" qc = QuantumCircuit(5) @@ -2120,8 +2119,7 @@ def test_basic_connected_circuit(self, opt_level): continue self.assertIn(qubits, self.backend.target[op_name]) - # Add level 0 and 1 when TrivialLayout supports disjoint coupling maps - @data(2, 3) + @data(0, 1, 2, 3) def test_triple_circuit(self, opt_level): """Test a split circuit with one circuit component per chip.""" qc = QuantumCircuit(30) @@ -2156,6 +2154,12 @@ def test_triple_circuit(self, opt_level): qc.cy(20, 28) qc.cy(20, 29) qc.measure_all() + + if opt_level == 0: + with self.assertRaises(TranspilerError): + tqc = transpile(qc, self.backend, optimization_level=opt_level, seed_transpiler=42) + return + tqc = transpile(qc, self.backend, optimization_level=opt_level, seed_transpiler=42) for inst in tqc.data: qubits = tuple(tqc.find_bit(x).index for x in inst.qubits) @@ -2164,10 +2168,89 @@ def test_triple_circuit(self, opt_level): continue self.assertIn(qubits, self.backend.target[op_name]) - # Add level 0 and 1 when TrivialLayout supports disjoint coupling maps - # Tagged as slow until #9834 is fixed + def test_disjoint_control_flow(self): + """Test control flow circuit on disjoint coupling map.""" + qc = QuantumCircuit(6, 1) + qc.h(0) + qc.ecr(0, 1) + qc.cx(0, 2) + qc.measure(0, 0) + with qc.if_test((qc.clbits[0], True)): + qc.reset(0) + qc.cz(1, 0) + qc.h(3) + qc.cz(3, 4) + qc.cz(3, 5) + target = self.backend.target + target.add_instruction(Reset(), {(i,): None for i in range(target.num_qubits)}) + target.add_instruction(IfElseOp, name="if_else") + tqc = transpile(qc, target=target) + edges = set(target.build_coupling_map().graph.edge_list()) + + def _visit_block(circuit, qubit_mapping=None): + for instruction in circuit: + if instruction.operation.name == "barrier": + continue + qargs = tuple(qubit_mapping[x] for x in instruction.qubits) + self.assertTrue(target.instruction_supported(instruction.operation.name, qargs)) + if isinstance(instruction.operation, ControlFlowOp): + for block in instruction.operation.blocks: + new_mapping = { + inner: qubit_mapping[outer] + for outer, inner in zip(instruction.qubits, block.qubits) + } + _visit_block(block, new_mapping) + elif len(qargs) == 2: + self.assertIn(qargs, edges) + self.assertIn(instruction.operation.name, target) + + _visit_block( + tqc, + qubit_mapping={qubit: index for index, qubit in enumerate(tqc.qubits)}, + ) + + def test_disjoint_control_flow_shared_classical(self): + """Test circuit with classical data dependency between connected components.""" + creg = ClassicalRegister(19) + qc = QuantumCircuit(25) + qc.add_register(creg) + qc.h(0) + for i in range(18): + qc.cx(0, i + 1) + for i in range(18): + qc.measure(i, creg[i]) + with qc.if_test((creg, 0)): + qc.h(20) + qc.ecr(20, 21) + qc.ecr(20, 22) + qc.ecr(20, 23) + qc.ecr(20, 24) + target = self.backend.target + target.add_instruction(Reset(), {(i,): None for i in range(target.num_qubits)}) + target.add_instruction(IfElseOp, name="if_else") + tqc = transpile(qc, target=target) + + def _visit_block(circuit, qubit_mapping=None): + for instruction in circuit: + if instruction.operation.name == "barrier": + continue + qargs = tuple(qubit_mapping[x] for x in instruction.qubits) + self.assertTrue(target.instruction_supported(instruction.operation.name, qargs)) + if isinstance(instruction.operation, ControlFlowOp): + for block in instruction.operation.blocks: + new_mapping = { + inner: qubit_mapping[outer] + for outer, inner in zip(instruction.qubits, block.qubits) + } + _visit_block(block, new_mapping) + + _visit_block( + tqc, + qubit_mapping={qubit: index for index, qubit in enumerate(tqc.qubits)}, + ) + @slow_test - @data(2, 3) + @data(1, 2, 3) def test_six_component_circuit(self, opt_level): """Test input circuit with more than 1 component per backend component.""" qc = QuantumCircuit(42) @@ -2222,7 +2305,7 @@ def test_six_component_circuit(self, opt_level): continue self.assertIn(qubits, self.backend.target[op_name]) - @data(2, 3) + @data(0, 1, 2, 3) def test_shared_classical_between_components_condition(self, opt_level): """Test a condition sharing classical bits between components.""" creg = ClassicalRegister(19) @@ -2258,7 +2341,7 @@ def _visit_block(circuit, qubit_mapping=None): qubit_mapping={qubit: index for index, qubit in enumerate(tqc.qubits)}, ) - @data(2, 3) + @data(0, 1, 2, 3) def test_shared_classical_between_components_condition_large_to_small(self, opt_level): """Test a condition sharing classical bits between components.""" creg = ClassicalRegister(2) @@ -2271,7 +2354,7 @@ def test_shared_classical_between_components_condition_large_to_small(self, opt_ qc.h(0).c_if(creg, 0) for i in range(18): qc.ecr(0, i + 1).c_if(creg, 0) - tqc = transpile(qc, self.backend, optimization_level=opt_level) + tqc = transpile(qc, self.backend, optimization_level=opt_level, seed_transpiler=123456789) def _visit_block(circuit, qubit_mapping=None): for instruction in circuit: @@ -2320,7 +2403,7 @@ def _visit_block(circuit, qubit_mapping=None): op_node._node_id, lambda edge_data: isinstance(edge_data, Clbit) )[0] - @data(2, 3) + @data(1, 2, 3) def test_shared_classical_between_components_condition_large_to_small_reverse_index( self, opt_level ): @@ -2384,6 +2467,10 @@ def _visit_block(circuit, qubit_mapping=None): op_node._node_id, lambda edge_data: isinstance(edge_data, Clbit) )[0] + # Level 1 skipped in this test for now because routing inserts more swaps + # and tricking the intermediate layout permutation to validate ordering + # will be different compared to higher optimization levels. We have similar + # coverage provided by above tests for level 1. @data(2, 3) def test_chained_data_dependency(self, opt_level): """Test 3 component circuit with shared clbits between each component.""" @@ -2466,3 +2553,183 @@ def _visit_block(circuit, qubit_mapping=None): op_node._node_id, lambda edge_data: isinstance(edge_data, Clbit) )[0] self.assertIn(initial_layout._p2v[qubit_map[op_node.qargs[0]]], third_component) + + @data("sabre", "stochastic", "basic", "lookahead") + def test_basic_connected_circuit_dense_layout(self, routing_method): + """Test basic connected circuit on disjoint backend""" + qc = QuantumCircuit(5) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(0, 3) + qc.cx(0, 4) + qc.measure_all() + tqc = transpile( + qc, + self.backend, + layout_method="dense", + routing_method=routing_method, + seed_transpiler=42, + ) + for inst in tqc.data: + qubits = tuple(tqc.find_bit(x).index for x in inst.qubits) + op_name = inst.operation.name + if op_name == "barrier": + continue + self.assertIn(qubits, self.backend.target[op_name]) + + # Lookahead swap skipped for performance + @data("sabre", "stochastic", "basic") + def test_triple_circuit_dense_layout(self, routing_method): + """Test a split circuit with one circuit component per chip.""" + qc = QuantumCircuit(30) + qc.h(0) + qc.h(10) + qc.h(20) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(0, 3) + qc.cx(0, 4) + qc.cx(0, 5) + qc.cx(0, 6) + qc.cx(0, 7) + qc.cx(0, 8) + qc.cx(0, 9) + qc.ecr(10, 11) + qc.ecr(10, 12) + qc.ecr(10, 13) + qc.ecr(10, 14) + qc.ecr(10, 15) + qc.ecr(10, 16) + qc.ecr(10, 17) + qc.ecr(10, 18) + qc.ecr(10, 19) + qc.cy(20, 21) + qc.cy(20, 22) + qc.cy(20, 23) + qc.cy(20, 24) + qc.cy(20, 25) + qc.cy(20, 26) + qc.cy(20, 27) + qc.cy(20, 28) + qc.cy(20, 29) + qc.measure_all() + tqc = transpile( + qc, + self.backend, + layout_method="dense", + routing_method=routing_method, + seed_transpiler=42, + ) + for inst in tqc.data: + qubits = tuple(tqc.find_bit(x).index for x in inst.qubits) + op_name = inst.operation.name + if op_name == "barrier": + continue + self.assertIn(qubits, self.backend.target[op_name]) + + @data("sabre", "stochastic", "basic", "lookahead") + def test_triple_circuit_invalid_layout(self, routing_method): + """Test a split circuit with one circuit component per chip.""" + qc = QuantumCircuit(30) + qc.h(0) + qc.h(10) + qc.h(20) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(0, 3) + qc.cx(0, 4) + qc.cx(0, 5) + qc.cx(0, 6) + qc.cx(0, 7) + qc.cx(0, 8) + qc.cx(0, 9) + qc.ecr(10, 11) + qc.ecr(10, 12) + qc.ecr(10, 13) + qc.ecr(10, 14) + qc.ecr(10, 15) + qc.ecr(10, 16) + qc.ecr(10, 17) + qc.ecr(10, 18) + qc.ecr(10, 19) + qc.cy(20, 21) + qc.cy(20, 22) + qc.cy(20, 23) + qc.cy(20, 24) + qc.cy(20, 25) + qc.cy(20, 26) + qc.cy(20, 27) + qc.cy(20, 28) + qc.cy(20, 29) + qc.measure_all() + with self.assertRaises(TranspilerError): + transpile( + qc, + self.backend, + layout_method="trivial", + routing_method=routing_method, + seed_transpiler=42, + ) + + # Lookahead swap skipped for performance reasons + @data("sabre", "stochastic", "basic") + def test_six_component_circuit_dense_layout(self, routing_method): + """Test input circuit with more than 1 component per backend component.""" + qc = QuantumCircuit(42) + qc.h(0) + qc.h(10) + qc.h(20) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(0, 3) + qc.cx(0, 4) + qc.cx(0, 5) + qc.cx(0, 6) + qc.cx(0, 7) + qc.cx(0, 8) + qc.cx(0, 9) + qc.ecr(10, 11) + qc.ecr(10, 12) + qc.ecr(10, 13) + qc.ecr(10, 14) + qc.ecr(10, 15) + qc.ecr(10, 16) + qc.ecr(10, 17) + qc.ecr(10, 18) + qc.ecr(10, 19) + qc.cy(20, 21) + qc.cy(20, 22) + qc.cy(20, 23) + qc.cy(20, 24) + qc.cy(20, 25) + qc.cy(20, 26) + qc.cy(20, 27) + qc.cy(20, 28) + qc.cy(20, 29) + qc.h(30) + qc.cx(30, 31) + qc.cx(30, 32) + qc.cx(30, 33) + qc.h(34) + qc.cx(34, 35) + qc.cx(34, 36) + qc.cx(34, 37) + qc.h(38) + qc.cx(38, 39) + qc.cx(39, 40) + qc.cx(39, 41) + qc.measure_all() + tqc = transpile( + qc, + self.backend, + layout_method="dense", + routing_method=routing_method, + seed_transpiler=42, + ) + for inst in tqc.data: + qubits = tuple(tqc.find_bit(x).index for x in inst.qubits) + op_name = inst.operation.name + if op_name == "barrier": + continue + self.assertIn(qubits, self.backend.target[op_name]) diff --git a/test/python/transpiler/test_dense_layout.py b/test/python/transpiler/test_dense_layout.py index 10e5bbed9252..01642b4036d1 100644 --- a/test/python/transpiler/test_dense_layout.py +++ b/test/python/transpiler/test_dense_layout.py @@ -24,6 +24,7 @@ from qiskit.converters import circuit_to_dag from qiskit.test import QiskitTestCase from qiskit.providers.fake_provider import FakeTokyo +from qiskit.transpiler.passes.layout.dense_layout import _build_error_matrix class TestDenseLayout(QiskitTestCase): @@ -150,12 +151,16 @@ def test_target_too_small_for_circuit(self): def test_19q_target_with_noise_error_matrix(self): """Test the error matrix construction works for a just cx target.""" - pass_ = DenseLayout(target=self.target_19) expected_error_mat = np.zeros((19, 19)) for qargs, props in self.target_19["cx"].items(): error = props.error expected_error_mat[qargs[0]][qargs[1]] = error - np.testing.assert_array_equal(expected_error_mat, pass_.error_mat) + error_mat = _build_error_matrix( + self.target_19.num_qubits, + {i: i for i in range(self.target_19.num_qubits)}, + target=self.target_19, + )[0] + np.testing.assert_array_equal(expected_error_mat, error_mat) def test_multiple_gate_error_matrix(self): """Test error matrix ona small target with multiple gets on each qubit generates""" @@ -192,7 +197,10 @@ def test_multiple_gate_error_matrix(self): [2e-2, 1e-3, 1e-2], ] ) - np.testing.assert_array_equal(expected_error_matrix, DenseLayout(target=target).error_mat) + error_mat = _build_error_matrix( + target.num_qubits, {i: i for i in range(target.num_qubits)}, target=target + )[0] + np.testing.assert_array_equal(expected_error_matrix, error_mat) def test_5q_circuit_20q_with_if_else(self): """Test layout works finds a dense 5q subgraph in a 19q heavy hex target."""