diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index c40ae57bb148..2e2d6e5cb14d 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -122,7 +122,6 @@ def __eq__(self, other): or self.definition != other.definition ): return False - for self_param, other_param in zip_longest(self.params, other.params): try: if self_param == other_param: diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index f52d0c28b470..721497775aa4 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -376,6 +376,63 @@ def remove_cregs(self, *cregs): for creg in cregs: del self.cregs[creg.name] + def remove_qubits(self, *qubits): + """ + Remove quantum bits from the circuit. All bits MUST be idle. + Any registers with references to at least one of the specified bits will + also be removed. + + Args: + qubits (List[Qubit]): The bits to remove. + + Raises: + DAGCircuitError: a qubit is not a :obj:`.Qubit`, is not in the circuit, + or is not idle. + """ + if any(not isinstance(qubit, Qubit) for qubit in qubits): + raise DAGCircuitError( + "qubits not of type Qubit: %s" % [b for b in qubits if not isinstance(b, Qubit)] + ) + + qubits = set(qubits) + unknown_qubits = qubits.difference(self.qubits) + if unknown_qubits: + raise DAGCircuitError("qubits not in circuit: %s" % unknown_qubits) + + busy_qubits = {bit for bit in qubits if not self._is_wire_idle(bit)} + if busy_qubits: + raise DAGCircuitError("qubits not idle: %s" % busy_qubits) + + # remove any references to bits + qregs_to_remove = {qreg for qreg in self.qregs.values() if not qubits.isdisjoint(qreg)} + self.remove_qregs(*qregs_to_remove) + + for qubit in qubits: + self._remove_idle_wire(qubit) + self.qubits.remove(qubit) + + def remove_qregs(self, *qregs): + """ + Remove classical registers from the circuit, leaving underlying bits + in place. + + Raises: + DAGCircuitError: a qreg is not a QuantumRegister, or is not in + the circuit. + """ + if any(not isinstance(qreg, QuantumRegister) for qreg in qregs): + raise DAGCircuitError( + "qregs not of type QuantumRegister: %s" + % [r for r in qregs if not isinstance(r, QuantumRegister)] + ) + + unknown_qregs = set(qregs).difference(self.qregs.values()) + if unknown_qregs: + raise DAGCircuitError("qregs not in circuit: %s" % unknown_qregs) + + for qreg in qregs: + del self.qregs[qreg.name] + def _is_wire_idle(self, wire): """Check if a wire is idle. diff --git a/qiskit/dagcircuit/dagnode.py b/qiskit/dagcircuit/dagnode.py index ed6cfe15da24..5952aa8764c8 100644 --- a/qiskit/dagcircuit/dagnode.py +++ b/qiskit/dagcircuit/dagnode.py @@ -74,7 +74,7 @@ def semantic_eq(node1, node2, bit_indices1=None, bit_indices2=None): node2_cargs = [bit_indices2[carg] for carg in node2.cargs] # For barriers, qarg order is not significant so compare as sets - if "barrier" == node1.op.name == node2.op.name: + if node1.op.name == node2.op.name and node1.name in {"barrier", "swap"}: return set(node1_qargs) == set(node2_qargs) if node1_qargs == node2_qargs: @@ -87,7 +87,6 @@ def semantic_eq(node1, node2, bit_indices1=None, bit_indices2=None): ): if bit_indices1.get(node1.wire, None) == bit_indices2.get(node2.wire, None): return True - return False diff --git a/qiskit/transpiler/passes/routing/layout_transformation.py b/qiskit/transpiler/passes/routing/layout_transformation.py index 3898d2f8256c..536b282d9135 100644 --- a/qiskit/transpiler/passes/routing/layout_transformation.py +++ b/qiskit/transpiler/passes/routing/layout_transformation.py @@ -35,6 +35,7 @@ def __init__( to_layout: Union[Layout, str], seed: Union[int, np.random.default_rng] = None, trials=4, + inplace=True, ): """LayoutTransformation initializer. @@ -55,6 +56,10 @@ def __init__( trials (int): How many randomized trials to perform, taking the best circuit as output. + + inplace (bool): Whether to apply transform to dag in-place. If False, the + orginal dag is returned. The swap network on the physical qubits, however, + can be retrieved from the ``perm_circ`` attribute. """ super().__init__() self.from_layout = from_layout @@ -67,6 +72,9 @@ def __init__( graph = self.coupling_map.graph.to_undirected() self.token_swapper = ApproximateTokenSwapper(graph, seed) self.trials = trials + self.perm_circ = None + self.perm_qubits = None + self.inplace = inplace def run(self, dag): """Apply the specified partial permutation to the circuit. @@ -106,9 +114,11 @@ def run(self, dag): pqubit: to_layout.get_virtual_bits()[vqubit] for vqubit, pqubit in from_layout.get_virtual_bits().items() } - perm_circ = self.token_swapper.permutation_circuit(permutation, self.trials) qubits = [dag.qubits[i[0]] for i in sorted(perm_circ.inputmap.items(), key=lambda x: x[0])] - dag.compose(perm_circ.circuit, qubits=qubits) + self.property_set["perm_circ"] = perm_circ + self.property_set["perm_qubits"] = qubits + + dag.compose(perm_circ.circuit, qubits=qubits, inplace=self.inplace) return dag diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index bb379b056444..beab881d8619 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -22,6 +22,12 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.circuit.library.standard_gates import SwapGate from qiskit.transpiler.layout import Layout +from qiskit.transpiler.passes.routing.utils import ( + route_cf_multiblock, + route_cf_looping, + combine_permutations, +) +from qiskit.circuit import IfElseOp, WhileLoopOp, ForLoopOp, ControlFlowOp from qiskit._accelerate import stochastic_swap as stochastic_swap_rs @@ -43,7 +49,9 @@ class StochasticSwap(TransformationPass): the circuit. """ - def __init__(self, coupling_map, trials=20, seed=None, fake_run=False): + _instance_num = 0 # track number of instances of this class + + def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_layout=None): """StochasticSwap initializer. The coupling map is a connected graph @@ -57,6 +65,7 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False): seed (int): seed for random number generator fake_run (bool): if true, it only pretend to do routing, i.e., no swap is effectively added. + initial_layout (Layout): starting layout at beginning of pass. """ super().__init__() self.coupling_map = coupling_map @@ -64,9 +73,10 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False): self.seed = seed self.fake_run = fake_run self.qregs = None - self.rng = None self.trivial_layout = None + self.initial_layout = initial_layout self._qubit_indices = None + self._instance_num += 1 def run(self, dag): """Run the StochasticSwap pass on `dag`. @@ -89,7 +99,8 @@ def run(self, dag): raise TranspilerError("The layout does not match the amount of qubits in the DAG") canonical_register = dag.qregs["q"] - self.trivial_layout = Layout.generate_trivial_layout(canonical_register) + if self.initial_layout is None: + self.initial_layout = Layout.generate_trivial_layout(canonical_register) self._qubit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} self.qregs = dag.qregs @@ -205,8 +216,8 @@ def _layer_permutation(self, layer_partition, layout, qubit_subset, coupling, tr edges = best_edges.edges() for idx in range(len(edges) // 2): - swap_src = self.trivial_layout._p2v[edges[2 * idx]] - swap_tgt = self.trivial_layout._p2v[edges[2 * idx + 1]] + swap_src = self.initial_layout._p2v[edges[2 * idx]] + swap_tgt = self.initial_layout._p2v[edges[2 * idx + 1]] trial_circuit.apply_operation_back(SwapGate(), [swap_src, swap_tgt], []) best_circuit = trial_circuit @@ -229,7 +240,7 @@ def _layer_update(self, dag, layer, best_layout, best_depth, best_circuit): """ layout = best_layout logger.debug("layer_update: layout = %s", layout) - logger.debug("layer_update: self.trivial_layout = %s", self.trivial_layout) + logger.debug("layer_update: self.initial_layout = %s", self.initial_layout) # Output any swaps if best_depth > 0: @@ -239,14 +250,16 @@ def _layer_update(self, dag, layer, best_layout, best_depth, best_circuit): logger.debug("layer_update: there are no swaps in this layer") # Output this layer layer_circuit = layer["graph"] - order = layout.reorder_bits(dag.qubits) + initial_v2p = self.initial_layout.get_virtual_bits() + new_v2p = layout.get_virtual_bits() + initial_order = [initial_v2p[qubit] for qubit in dag.qubits] + new_order = [new_v2p[qubit] for qubit in dag.qubits] + order = combine_permutations(initial_order, new_order) dag.compose(layer_circuit, qubits=order) def _mapper(self, circuit_graph, coupling_graph, trials=20): """Map a DAGCircuit onto a CouplingMap using swap gates. - Use self.trivial_layout for the initial layout. - Args: circuit_graph (DAGCircuit): input DAG circuit coupling_graph (CouplingMap): coupling graph to map onto @@ -266,10 +279,10 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): for i, v in enumerate(layerlist): logger.debug(" %d: %s", i, v["partition"]) - qubit_subset = self.trivial_layout.get_virtual_bits().keys() + qubit_subset = self.initial_layout.get_virtual_bits().keys() # Find swap circuit to precede each layer of input circuit - layout = self.trivial_layout.copy() + layout = self.initial_layout.copy() # Construct an empty DAGCircuit with the same set of # qregs and cregs as the input circuit @@ -277,17 +290,23 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): if not self.fake_run: dagcircuit_output = circuit_graph.copy_empty_like() - logger.debug("trivial_layout = %s", layout) + logger.debug("layout = %s", layout) # Iterate over layers for i, layer in enumerate(layerlist): + layer_dag = layer["graph"] + cf_nodes = layer_dag.op_nodes(op=ControlFlowOp) + if cf_nodes: + # handle layers with control flow serially + success_flag = False + else: + # Attempt to find a permutation for this layer + success_flag, best_circuit, best_depth, best_layout = self._layer_permutation( + layer["partition"], layout, qubit_subset, coupling_graph, trials + ) - # Attempt to find a permutation for this layer - success_flag, best_circuit, best_depth, best_layout = self._layer_permutation( - layer["partition"], layout, qubit_subset, coupling_graph, trials - ) - logger.debug("mapper: layer %d", i) - logger.debug("mapper: success_flag=%s,best_depth=%s", success_flag, str(best_depth)) + logger.debug("mapper: layer %d", i) + logger.debug("mapper: success_flag=%s,best_depth=%s", success_flag, str(best_depth)) # If this fails, try one gate at a time in this layer if not success_flag: @@ -296,34 +315,45 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): # Go through each gate in the layer for j, serial_layer in enumerate(serial_layerlist): - - success_flag, best_circuit, best_depth, best_layout = self._layer_permutation( - serial_layer["partition"], layout, qubit_subset, coupling_graph, trials - ) - logger.debug("mapper: layer %d, sublayer %d", i, j) - logger.debug( - "mapper: success_flag=%s,best_depth=%s,", success_flag, str(best_depth) - ) - - # Give up if we fail again - if not success_flag: - raise TranspilerError( - "swap mapper failed: " + "layer %d, sublayer %d" % (i, j) - ) - - # Update the record of qubit positions - # for each inner iteration - layout = best_layout - # Update the DAG - if not self.fake_run: - self._layer_update( - dagcircuit_output, - serial_layerlist[j], - best_layout, - best_depth, + layer_dag = serial_layer["graph"] + # layer_dag has only one operation + op_node = layer_dag.op_nodes()[0] + if not isinstance(op_node.op, ControlFlowOp): + ( + success_flag, best_circuit, + best_depth, + best_layout, + ) = self._layer_permutation( + serial_layer["partition"], layout, qubit_subset, coupling_graph, trials + ) + logger.debug("mapper: layer %d, sublayer %d", i, j) + logger.debug( + "mapper: success_flag=%s,best_depth=%s,", success_flag, str(best_depth) ) + # Give up if we fail again + if not success_flag: + raise TranspilerError( + "swap mapper failed: " + "layer %d, sublayer %d" % (i, j) + ) + + # Update the record of qubit positions + # for each inner iteration + layout = best_layout + # Update the DAG + if not self.fake_run: + self._layer_update( + dagcircuit_output, + serial_layerlist[j], + best_layout, + best_depth, + best_circuit, + ) + else: + layout = self._controlflow_layer_update( + dagcircuit_output, layer_dag, layout, circuit_graph, _seed=self.seed + ) else: # Update the record of qubit positions for each iteration layout = best_layout @@ -339,7 +369,56 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): logger.debug("mapper: self.trivial_layout = %s", self.trivial_layout) logger.debug("mapper: layout = %s", layout) + self.property_set["final_layout"] = layout if self.fake_run: - self.property_set["final_layout"] = layout return circuit_graph return dagcircuit_output + + def _controlflow_layer_update( + self, dagcircuit_output, layer_dag, current_layout, root_dag, _seed=None + ): + """ + Updates the new dagcircuit with a routed control flow operation. + + Args: + dagcircuit_output (DAGCircuit): dagcircuit that is being built with routed operations. + layer_dag (DAGCircuit): layer to route containing a single controlflow operation. + current_layout (Layout): current layout coming into this layer. + root_dag (DAGCircuit): root dag of pass + _seed (int or None): seed used to derive seeds for child instances of this pass where + it is used by stochastic_swap_rs.swap_trials as well as LayoutTransformation. If + the seed is not None the instance_num class variable gets added to this seed to + seed other instances. + + Returns: + Layout: updated layout after this layer has been routed. + + Raises: + TranspilerError: if layer_dag does not contain a recognized ControlFlowOp. + + """ + cf_opnode = layer_dag.op_nodes()[0] + seed = _seed if _seed is None else _seed + self._instance_num + _pass = self.__class__(self.coupling_map, initial_layout=current_layout, seed=seed) + if isinstance(cf_opnode.op, IfElseOp): + updated_ctrl_op, cf_layout, idle_qubits = route_cf_multiblock( + _pass, cf_opnode, current_layout, self.qregs, root_dag, seed=self.seed + ) + elif isinstance(cf_opnode.op, (ForLoopOp, WhileLoopOp)): + updated_ctrl_op, cf_layout, idle_qubits = route_cf_looping( + _pass, cf_opnode, current_layout, root_dag, seed=self.seed + ) + else: + raise TranspilerError(f"unsupported control flow operation: {cf_opnode}") + + cf_layer_dag = DAGCircuit() + cf_qubits = [qubit for qubit in root_dag.qubits if qubit not in idle_qubits] + qreg = QuantumRegister(len(cf_qubits), "q") + cf_layer_dag.add_qreg(qreg) + for creg in layer_dag.cregs.values(): + cf_layer_dag.add_creg(creg) + cf_layer_dag.apply_operation_back(updated_ctrl_op, cf_layer_dag.qubits, cf_opnode.cargs) + target_qubits = [qubit for qubit in dagcircuit_output.qubits if qubit not in idle_qubits] + order = current_layout.reorder_bits(target_qubits) + dagcircuit_output.compose(cf_layer_dag, qubits=order) + return cf_layout diff --git a/qiskit/transpiler/passes/routing/utils.py b/qiskit/transpiler/passes/routing/utils.py new file mode 100644 index 000000000000..1f5ab7788d2f --- /dev/null +++ b/qiskit/transpiler/passes/routing/utils.py @@ -0,0 +1,179 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# 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. + +"""Utility functions for routing""" + +import numpy as np +from qiskit.transpiler.layout import Layout +from qiskit.dagcircuit import DAGCircuit +from qiskit.circuit import QuantumRegister + + +def route_cf_multiblock(tpass, cf_opnode, current_layout, qregs, root_dag, seed=None): + """Transpile control flow instructions which may contain multiple + blocks (e.g. IfElseOp). Since each control flow block may yield a + different layout, this function applies swaps to the shorter depth + blocks to make all final layouts match. + + Args: + tpass (BasePass): Transpiler pass object to use recursively. + cf_opnode (DAGOpNode): multiblock instruction node e.g. IfElseOp. + current_layout (Layout): The current layout at the start of the instruction. + qregs (list(QuantumRegister)): quantum registers for circuit + root_dag (DAGCircuit): root dag of compilation + seed (int): seed for RNG of internal layout transformation. + Returns: + IfElseOp: transpiled control flow operation + final_layout (Layout): layout after instruction + + """ + # pylint: disable=cyclic-import + from qiskit.transpiler.passes.routing.layout_transformation import LayoutTransformation + from qiskit.converters import dag_to_circuit, circuit_to_dag + + if current_layout is None: + canonical_register = qregs["q"] + current_layout = Layout.generate_trivial_layout(canonical_register) + coupling = tpass.coupling_map + block_dags = [] # control flow dag blocks + block_layouts = [] # control flow layouts + # expand to full width for routing + cf_op = cf_opnode.op + indices = {bit: i for i, bit in enumerate(root_dag.qubits)} + order = [indices[bit] for bit in cf_opnode.qargs] + for block in cf_op.blocks: + full_dag_block = root_dag.copy_empty_like() + dag_block = circuit_to_dag(block) + full_dag_block.compose(dag_block, qubits=order) + tpass.initial_layout = current_layout + updated_dag_block = tpass.run(full_dag_block) + block_dags.append(updated_dag_block) + block_layouts.append(tpass.property_set["final_layout"].copy()) + deepest_index = np.argmax([block.depth(recurse=True) for block in block_dags]) + block_circuits = [None] * len(block_layouts) + p2v = current_layout.get_physical_bits() + idle_qubits = set(root_dag.qubits) + for i, updated_dag_block in enumerate(block_dags): + if i == deepest_index: + block_circuits[i] = dag_to_circuit(updated_dag_block) + else: + layout_xform = LayoutTransformation( + coupling, block_layouts[i], block_layouts[deepest_index], seed=seed, inplace=False + ) + layout_xform.run(updated_dag_block) + physical_swap_dag = layout_xform.property_set["perm_circ"].circuit + if physical_swap_dag.depth(): + virtual_swap_dag = updated_dag_block.copy_empty_like() + order = [ + p2v[virtual_swap_dag.qubits.index(qubit)] + for qubit in layout_xform.property_set["perm_qubits"] + ] + virtual_swap_dag.compose(physical_swap_dag, qubits=order) + updated_dag_block.compose(virtual_swap_dag) + idle_qubits &= set(updated_dag_block.idle_wires()) + # contract idle bits from full width post routing + for i, updated_dag_block in enumerate(block_dags): + updated_dag_block.remove_qubits(*idle_qubits) + new_dag_block = DAGCircuit() + new_num_qubits = updated_dag_block.num_qubits() + qreg = QuantumRegister(new_num_qubits, "q") + new_dag_block.add_qreg(qreg) + for creg in updated_dag_block.cregs.values(): + new_dag_block.add_creg(creg) + for node in updated_dag_block.op_nodes(): + new_qargs = [qreg[updated_dag_block.qubits.index(bit)] for bit in node.qargs] + new_dag_block.apply_operation_back(node.op, new_qargs, node.cargs) + block_circuits[i] = dag_to_circuit(new_dag_block) + + final_layout = block_layouts[deepest_index] + return cf_op.replace_blocks(block_circuits), final_layout, idle_qubits + + +def route_cf_looping(tpass, cf_opnode, current_layout, root_dag, seed=None): + """For looping this pass adds a swap layer using LayoutTransformation + to the end of the loop body to bring the layout back to the + starting layout. This prevents reapplying layout changing + swaps for every iteration of the loop. + + Args: + tpass (BasePass): pass object to run + cf_opnode (DAGOpNode): looping instruction e.g. ForLoopOp, WhileLoopOp + current_layout (Layout): The current layout at the start and by the + end of the instruction. + root_dag (DAGCircuit): root dagcircuit + seed (int): seed for RNG of internal layout transformation. + + Returns: + tuple(ControlFlowOp, Layout): Transpiled control flow + operation and layout after instruction + + """ + # pylint: disable=cyclic-import + from qiskit.transpiler.passes.routing.layout_transformation import LayoutTransformation + from qiskit.converters import dag_to_circuit, circuit_to_dag + + cf_op = cf_opnode.op # control flow operation + coupling = tpass.coupling_map + dag_block = circuit_to_dag(cf_op.blocks[0]) + # expand to full width for routing + full_dag_block = root_dag.copy_empty_like() + start_layout = current_layout + indices = {bit: i for i, bit in enumerate(root_dag.qubits)} + order = [indices[bit] for bit in cf_opnode.qargs] + full_dag_block.compose(dag_block, qubits=order) + updated_dag_block = tpass.run(full_dag_block) + updated_layout = tpass.property_set["final_layout"].copy() + layout_xform = LayoutTransformation( + coupling, updated_layout, start_layout, seed=seed, inplace=False + ) + layout_xform.run(updated_dag_block) + physical_swap_dag = layout_xform.property_set["perm_circ"].circuit + if physical_swap_dag.depth(): + p2v = current_layout.get_physical_bits() + virtual_swap_dag = updated_dag_block.copy_empty_like() + order = [ + p2v[virtual_swap_dag.qubits.index(qubit)] + for qubit in layout_xform.property_set["perm_qubits"] + ] + virtual_swap_dag.compose(physical_swap_dag, qubits=order) + updated_dag_block.compose(virtual_swap_dag) + # contract from full width post routing + idle_qubits = set(root_dag.qubits) & set(updated_dag_block.idle_wires()) + updated_dag_block.remove_qubits(*idle_qubits) + new_dag_block = DAGCircuit() + new_num_qubits = updated_dag_block.num_qubits() + qreg = QuantumRegister(new_num_qubits, "q") + new_dag_block.add_qreg(qreg) + for creg in updated_dag_block.cregs.values(): + new_dag_block.add_creg(creg) + for node in updated_dag_block.op_nodes(): + new_qargs = [qreg[updated_dag_block.qubits.index(bit)] for bit in node.qargs] + new_dag_block.apply_operation_back(node.op, new_qargs, node.cargs) + updated_circ_block = dag_to_circuit(new_dag_block) + cf_op.num_qubits = updated_circ_block.num_qubits + return cf_op.replace_blocks([updated_circ_block]), current_layout, idle_qubits + + +def combine_permutations(*permutations): + """ + Chain a series of permutations. + + Args: + *permutations (list(int)): permutations to combine + + Returns: + list: combined permutation + """ + order = permutations[0] + for this_order in permutations[1:]: + order = [order[i] for i in this_order] + return order diff --git a/qiskit/transpiler/passes/utils/check_map.py b/qiskit/transpiler/passes/utils/check_map.py index 3e764db42961..bd4bcb3500b3 100644 --- a/qiskit/transpiler/passes/utils/check_map.py +++ b/qiskit/transpiler/passes/utils/check_map.py @@ -52,9 +52,9 @@ def run(self, dag): 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 - + cf_ops = dag.op_nodes(op=ControlFlowOp) for gate in dag.two_qubit_ops(): - if dag.has_calibration_for(gate): + if dag.has_calibration_for(gate) or gate in cf_ops: continue physical_q0 = qubit_indices[gate.qargs[0]] physical_q1 = qubit_indices[gate.qargs[1]] @@ -67,7 +67,7 @@ def run(self, dag): ) self.property_set["is_swap_mapped"] = False return - for cf_instr in dag.op_nodes(op=ControlFlowOp): + for cf_instr in cf_ops: order = [qubit_indices[bit] for bit in cf_instr.qargs] for block in cf_instr.op.blocks: dag_block = circuit_to_dag(block) diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py index 8aed99218c6f..1e3417f44586 100644 --- a/test/python/transpiler/test_stochastic_swap.py +++ b/test/python/transpiler/test_stochastic_swap.py @@ -13,14 +13,17 @@ """Test the Stochastic Swap pass""" import unittest +from ddt import ddt, data from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler import CouplingMap, PassManager from qiskit.transpiler.exceptions import TranspilerError from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.test import QiskitTestCase +from qiskit.transpiler.passes.utils import CheckMap +@ddt class TestStochasticSwap(QiskitTestCase): """ Tests the StochasticSwap pass. @@ -566,6 +569,647 @@ def test_single_gates_omitted(self): after = circuit_to_dag(after) self.assertEqual(expected_dag, after) + def test_controlflow_pre_if_else_route(self): + """test swap with if else controlflow construct""" + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.cx(0, 2) + qc.measure(2, 2) + true_body = QuantumCircuit(qreg, creg) + true_body.x(3) + false_body = QuantumCircuit(qreg, creg) + false_body.x(4) + qc.if_else((creg[2], 0), true_body, false_body, qreg, creg) + qc.barrier(qreg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=82).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.swap(0, 1) + expected.cx(1, 2) + expected.measure(2, 2) + eqreg = QuantumRegister(2, name="q") + etrue_body = QuantumCircuit(eqreg, creg) + etrue_body.x(0) + efalse_body = QuantumCircuit(eqreg, creg) + efalse_body.x(1) + new_order = [1, 0, 2, 3, 4] + expected.if_else((creg[2], 0), etrue_body, efalse_body, qreg[[3, 4]], creg) + expected.barrier(qreg) + expected.measure(qreg, creg[new_order]) + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_pre_if_else_route_post_x(self): + """test swap with if else controlflow construct; pre-cx and post x""" + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.cx(0, 2) + qc.measure(2, 2) + true_body = QuantumCircuit(qreg, creg) + true_body.x(3) + false_body = QuantumCircuit(qreg, creg) + false_body.x(4) + qc.if_else((creg[2], 0), true_body, false_body, qreg, creg) + qc.x(1) + qc.barrier(qreg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=431).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.swap(1, 2) + expected.cx(0, 1) + expected.measure(1, 2) + new_order = [0, 2, 1, 3, 4] + eqreg = QuantumRegister(2, "q") + etrue_body = QuantumCircuit(eqreg, creg) + etrue_body.x(0) + efalse_body = QuantumCircuit(eqreg, creg) + efalse_body.x(1) + expected.if_else((creg[2], 0), etrue_body, efalse_body, qreg[[3, 4]], creg) + expected.x(2) + expected.barrier(qreg) + expected.measure(qreg, creg[new_order]) + + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_post_if_else_route(self): + """test swap with if else controlflow construct; post cx""" + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.measure(0, 0) + true_body = QuantumCircuit(qreg, creg) + true_body.x(3) + false_body = QuantumCircuit(qreg, creg) + false_body.x(4) + qc.barrier(qreg) + qc.if_else((creg[0], 0), true_body, false_body, qreg, creg) + qc.barrier(qreg) + qc.cx(0, 2) + qc.barrier(qreg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=6508).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.measure(0, 0) + eqreg = QuantumRegister(2, "q") + etrue_body = QuantumCircuit(eqreg, creg) + etrue_body.x(0) + efalse_body = QuantumCircuit(eqreg, creg) + efalse_body.x(1) + expected.barrier(qreg) + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[3, 4]], creg) + expected.barrier(qreg) + expected.swap(0, 1) + expected.cx(1, 2) + expected.barrier(qreg) + expected.measure(qreg, creg[[1, 0, 2, 3, 4]]) + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_pre_if_else2(self): + """test swap with if else controlflow construct; cx in if statement""" + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.cx(0, 2) + qc.x(1) + qc.measure(0, 0) + true_body = QuantumCircuit(qreg, creg) + true_body.x(0) + false_body = QuantumCircuit(qreg, creg) + qc.if_else((creg[0], 0), true_body, false_body, qreg, creg) + qc.barrier(qreg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=38).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.x(1) + expected.swap(0, 1) + expected.cx(1, 2) + expected.measure(1, 0) + eqreg = QuantumRegister(1, "q") + etrue_body = QuantumCircuit(eqreg, creg) + etrue_body.x(0) + efalse_body = QuantumCircuit(eqreg, creg) + new_order = [1, 0, 2, 3, 4] + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[1]], creg) + expected.barrier(qreg) + expected.measure(qreg, creg[new_order]) + + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_intra_if_else_route(self): + """test swap with if else controlflow construct""" + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.x(1) + qc.measure(0, 0) + true_body = QuantumCircuit(qreg, creg) + true_body.cx(0, 2) + false_body = QuantumCircuit(qreg, creg) + false_body.cx(0, 4) + qc.if_else((creg[0], 0), true_body, false_body, qreg, creg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=21).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.x(1) + expected.measure(0, 0) + etrue_body = QuantumCircuit(qreg, creg) + etrue_body.swap(0, 1) + etrue_body.cx(1, 2) + etrue_body.swap(1, 2) + etrue_body.swap(3, 4) + efalse_body = QuantumCircuit(qreg, creg) + efalse_body.swap(0, 1) + efalse_body.swap(1, 2) + efalse_body.swap(3, 4) + efalse_body.cx(2, 3) + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg) + new_order = [1, 2, 0, 4, 3] + expected.measure(qreg, creg[new_order]) + + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + @data(1, 2) + def test_controlflow_pre_intra_if_else(self, seed): + """test swap with if else controlflow construct; cx in if statement""" + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.cx(0, 2) + qc.x(1) + qc.measure(0, 0) + true_body = QuantumCircuit(qreg, creg) + true_body.cx(0, 2) + false_body = QuantumCircuit(qreg, creg) + false_body.cx(0, 4) + qc.if_else((creg[0], 0), true_body, false_body, qreg, creg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=seed, trials=20).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + if seed == 1: + eqreg = QuantumRegister(5, "q") + etrue_body = QuantumCircuit(eqreg, creg) + efalse_body = QuantumCircuit(eqreg, creg) + expected.h(0) + expected.x(1) + expected.swap(2, 1) + expected.cx(0, 1) + expected.measure(0, 0) + + etrue_body.cx(0, 2) + etrue_body.swap(0, 2) + etrue_body.swap(2, 1) + etrue_body.swap(4, 3) + + efalse_body.swap(0, 2) + efalse_body.swap(2, 1) + efalse_body.swap(3, 4) + efalse_body.cx(1, 3) + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[0, 2, 1, 3, 4]], creg) + expected.measure(qreg, creg[[2, 1, 0, 4, 3]]) + elif seed == 2: + eqreg = QuantumRegister(4, "q") + etrue_body = QuantumCircuit(eqreg, creg) + efalse_body = QuantumCircuit(eqreg, creg) + expected.h(0) + expected.x(1) + expected.swap(0, 1) + expected.cx(1, 2) + expected.measure(1, 0) + + etrue_body.cx(0, 1) + etrue_body.swap(2, 3) + etrue_body.swap(0, 1) + + efalse_body.swap(0, 1) + efalse_body.swap(2, 3) + efalse_body.cx(1, 2) + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[1, 2, 3, 4]], creg) + expected.measure(qreg, creg[[1, 2, 0, 4, 3]]) + else: + raise ValueError(f"unsupported test seed={seed}") + + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_pre_intra_post_if_else(self): + """test swap with if else controlflow construct; cx before, in, and after if + statement""" + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap([(i, i + 1) for i in range(num_qubits - 1)]) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.cx(0, 2) + qc.x(1) + qc.measure(0, 0) + true_body = QuantumCircuit(qreg, creg) + true_body.cx(0, 2) + false_body = QuantumCircuit(qreg, creg) + false_body.cx(0, 4) + qc.if_else((creg[0], 0), true_body, false_body, qreg, creg) + qc.h(3) + qc.cx(3, 0) + qc.barrier() + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=58).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.x(1) + expected.swap(1, 2) + expected.cx(0, 1) + expected.measure(0, 0) + etrue_body = QuantumCircuit(qreg, creg) + etrue_body.cx(0, 2) + etrue_body.swap(0, 2) + etrue_body.swap(4, 3) + etrue_body.swap(1, 3) + efalse_body = QuantumCircuit(qreg, creg) + efalse_body.swap(0, 2) + efalse_body.swap(3, 4) + efalse_body.swap(1, 3) + efalse_body.cx(2, 1) + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[0, 2, 1, 3, 4]], creg) + expected.swap(1, 2) + expected.h(4) + expected.swap(3, 4) + expected.cx(3, 2) + expected.barrier() + expected.measure(qreg, creg[[2, 4, 0, 3, 1]]) + + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_no_layout_change(self): + """test controlflow with no layout change needed""" + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap.from_line(num_qubits) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.cx(0, 2) + qc.x(1) + qc.measure(0, 0) + true_body = QuantumCircuit(qreg, creg) + true_body.x(2) + false_body = QuantumCircuit(qreg, creg) + false_body.x(4) + qc.if_else((creg[0], 0), true_body, false_body, qreg, creg) + qc.barrier(qreg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=23).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.x(1) + expected.swap(1, 2) + expected.cx(0, 1) + expected.measure(0, 0) + eqreg = QuantumRegister(2, "q") + etrue_body = QuantumCircuit(eqreg, creg) + etrue_body.x(0) + efalse_body = QuantumCircuit(eqreg, creg) + efalse_body.x(1) + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg[[1, 4]], creg) + expected.barrier(qreg) + expected.measure(qreg, creg[[0, 2, 1, 3, 4]]) + + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + @data(1, 2, 3) + def test_controlflow_for_loop(self, nloops): + """test stochastic swap with for_loop""" + # if the loop has only one iteration it isn't necessary for the pass + # to swap back to the starting layout. This test would check that + # optimization. + num_qubits = 3 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap.from_line(num_qubits) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.x(1) + for_body = QuantumCircuit(qreg, creg) + for_body.cx(0, 2) + loop_parameter = None + qc.for_loop(range(nloops), loop_parameter, for_body, qreg, creg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=687).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.x(1) + efor_body = QuantumCircuit(qreg, creg) + efor_body.swap(0, 1) + efor_body.cx(1, 2) + efor_body.swap(0, 1) + loop_parameter = None + expected.for_loop(range(nloops), loop_parameter, efor_body, qreg, creg) + expected.measure(qreg, creg) + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_while_loop(self): + """test while loop""" + num_qubits = 4 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(len(qreg)) + coupling = CouplingMap.from_line(num_qubits) + check_map_pass = CheckMap(coupling) + qc = QuantumCircuit(qreg, creg) + while_body = QuantumCircuit(qreg, creg) + while_body.reset(qreg[2:]) + while_body.h(qreg[2:]) + while_body.cx(0, 3) + while_body.measure(qreg[3], creg[3]) + qc.while_loop((creg, 0), while_body, qc.qubits, qc.clbits) + qc.barrier() + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=58).run(dag) + cqc = dag_to_circuit(cdag) + expected = QuantumCircuit(qreg, creg) + ewhile_body = QuantumCircuit(qreg, creg) + ewhile_body.reset(qreg[2:]) + ewhile_body.h(qreg[2:]) + ewhile_body.swap(0, 1) + ewhile_body.swap(2, 3) + ewhile_body.cx(1, 2) + ewhile_body.measure(qreg[2], creg[3]) + ewhile_body.swap(1, 0) + ewhile_body.swap(3, 2) + expected.while_loop((creg, 0), ewhile_body, expected.qubits, expected.clbits) + expected.barrier() + expected.measure(qreg, creg) + check_map_pass.run(circuit_to_dag(expected)) + self.assertEqual(cqc, expected) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_nested_inner_cnot(self): + """test swap in nested if else controlflow construct; swap in inner""" + seed = 392 + num_qubits = 3 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap.from_line(num_qubits) + check_map_pass = CheckMap(coupling) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.x(1) + qc.measure(0, 0) + true_body = QuantumCircuit(qreg, creg) + true_body.x(0) + + for_body = QuantumCircuit(qreg, creg) + for_body.delay(10, 0) + for_body.barrier(qreg) + for_body.cx(0, 2) + loop_parameter = None + true_body.for_loop(range(3), loop_parameter, for_body, qreg, creg) + + false_body = QuantumCircuit(qreg, creg) + false_body.y(0) + qc.if_else((creg[0], 0), true_body, false_body, qreg, creg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=seed).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.x(1) + expected.measure(0, 0) + etrue_body = QuantumCircuit(qreg, creg) + etrue_body.x(0) + + efor_body = QuantumCircuit(qreg, creg) + efor_body.delay(10, 0) + efor_body.barrier(qreg) + efor_body.swap(1, 2) + efor_body.cx(0, 1) + efor_body.swap(1, 2) + etrue_body.for_loop(range(3), loop_parameter, efor_body, qreg, creg) + + efalse_body = QuantumCircuit(qreg, creg) + efalse_body.y(0) + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg) + expected.measure(qreg, creg) + + self.assertEqual(cqc, expected) + check_map_pass = CheckMap(coupling) + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + + def test_controlflow_nested_outer_cnot(self): + """test swap with nested if else controlflow construct; swap in outer""" + seed = 200 + num_qubits = 5 + qreg = QuantumRegister(num_qubits, "q") + creg = ClassicalRegister(num_qubits) + coupling = CouplingMap.from_line(num_qubits) + check_map_pass = CheckMap(coupling) + qc = QuantumCircuit(qreg, creg) + qc.h(0) + qc.x(1) + qc.measure(0, 0) + true_body = QuantumCircuit(qreg, creg) + true_body.cx(0, 2) + true_body.x(0) + + for_body = QuantumCircuit(qreg, creg) + for_body.delay(10, 0) + for_body.barrier(qreg) + for_body.cx(1, 3) + loop_parameter = None + true_body.for_loop(range(3), loop_parameter, for_body, qreg, creg) + + false_body = QuantumCircuit(qreg, creg) + false_body.y(0) + qc.if_else((creg[0], 0), true_body, false_body, qreg, creg) + qc.measure(qreg, creg) + + dag = circuit_to_dag(qc) + cdag = StochasticSwap(coupling, seed=seed).run(dag) + cqc = dag_to_circuit(cdag) + + expected = QuantumCircuit(qreg, creg) + expected.h(0) + expected.x(1) + expected.measure(0, 0) + etrue_body = QuantumCircuit(qreg, creg) + etrue_body.swap(1, 2) + etrue_body.cx(0, 1) + etrue_body.x(0) + + efor_body = QuantumCircuit(qreg, creg) + efor_body.delay(10, 0) + efor_body.barrier(qreg) + efor_body.cx(1, 3) + etrue_body.for_loop(range(3), loop_parameter, efor_body, qreg[[0, 2, 1, 3, 4]], creg) + + efalse_body = QuantumCircuit(qreg, creg) + efalse_body.y(0) + efalse_body.swap(1, 2) + expected.if_else((creg[0], 0), etrue_body, efalse_body, qreg, creg) + expected.measure(qreg, creg[[0, 2, 1, 3, 4]]) + + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + self.assertEqual(cqc, expected) + + def test_controlflow_disjoint_looping(self): + """Test looping controlflow on different qubit register""" + num_qubits = 4 + cm = CouplingMap.from_line(num_qubits) + qr = QuantumRegister(num_qubits, "q") + qc = QuantumCircuit(qr) + loop_body = QuantumCircuit(2) + loop_body.cx(0, 1) + qc.for_loop((0,), None, loop_body, [0, 2], []) + cqc = StochasticSwap(cm, seed=653)(qc) + + expected = QuantumCircuit(qr) + efor_body = QuantumCircuit(3) + efor_body.swap(1, 2) + efor_body.cx(0, 1) + efor_body.swap(1, 2) + expected.for_loop((0,), None, efor_body, [0, 1, 2], []) + self.assertEqual(cqc, expected) + + def test_controlflow_disjoint_multiblock(self): + """Test looping controlflow on different qubit register""" + num_qubits = 4 + cm = CouplingMap.from_line(num_qubits) + qr = QuantumRegister(num_qubits, "q") + cr = ClassicalRegister(1) + qc = QuantumCircuit(qr, cr) + true_body = QuantumCircuit(3, 1) + true_body.cx(0, 1) + false_body = QuantumCircuit(3, 1) + false_body.cx(0, 2) + qc.if_else((cr[0], 1), true_body, false_body, [0, 1, 2], []) + cqc = StochasticSwap(cm, seed=353)(qc) + + expected = QuantumCircuit(qr, cr) + etrue_body = QuantumCircuit(true_body.qregs[0], cr) + etrue_body.cx(0, 1) + etrue_body.swap(0, 1) + efalse_body = QuantumCircuit(false_body.qregs[0], cr) + efalse_body.swap(0, 1) + efalse_body.cx(1, 2) + expected.if_else((cr[0], 1), etrue_body, efalse_body, [0, 1, 2], []) + self.assertEqual(cqc, expected) + + def test_controlflow_multiple_ops_per_layer(self): + """Test circuits with multiple operations per layer""" + num_qubits = 5 + coupling = CouplingMap.from_line(num_qubits) + check_map_pass = CheckMap(coupling) + qr = QuantumRegister(num_qubits, "q") + qc = QuantumCircuit(qr) + qc.cx(0, 2) + with qc.for_loop((0,)): + qc.cx(2, 4) + cqc = StochasticSwap(coupling, seed=68745)(qc) + + expected = QuantumCircuit(qr) + expected.swap(0, 1) + expected.cx(1, 2) + efor_body = QuantumCircuit(3) + efor_body.swap(1, 2) + efor_body.cx(0, 1) + efor_body.swap(2, 1) + expected.for_loop((0,), None, efor_body, [2, 3, 4], []) + + check_map_pass.run(circuit_to_dag(expected)) + self.assertTrue(check_map_pass.property_set["is_swap_mapped"]) + self.assertEqual(cqc, expected) + if __name__ == "__main__": unittest.main()