diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py index 3778075e27a2..69176b7b141a 100644 --- a/qiskit/dagcircuit/dagcircuit.py +++ b/qiskit/dagcircuit/dagcircuit.py @@ -20,11 +20,11 @@ composed, and modified. Some natural properties like depth can be computed directly from the graph. """ -from collections import OrderedDict, defaultdict, deque +from collections import OrderedDict, defaultdict, deque, namedtuple import copy import itertools import math -from typing import Generator, Any, List +from typing import Dict, Generator, Any, List import numpy as np import rustworkx as rx @@ -45,9 +45,13 @@ from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.dagcircuit.exceptions import DAGCircuitError from qiskit.dagcircuit.dagnode import DAGNode, DAGOpNode, DAGInNode, DAGOutNode +from qiskit.circuit.bit import Bit from qiskit.utils.deprecation import deprecate_func +BitLocations = namedtuple("BitLocations", ("index", "registers")) + + class DAGCircuit: """ Quantum circuit as a directed acyclic graph. @@ -95,6 +99,13 @@ def __init__(self): self.qubits: List[Qubit] = [] self.clbits: List[Clbit] = [] + # Dictionary mapping of Qubit and Clbit instances to a tuple comprised of + # 0) corresponding index in dag.{qubits,clbits} and + # 1) a list of Register-int pairs for each Register containing the Bit and + # its index within that register. + self._qubit_indices: Dict[Qubit, BitLocations] = {} + self._clbit_indices: Dict[Clbit, BitLocations] = {} + self._global_phase = 0 self._calibrations = defaultdict(dict) @@ -226,8 +237,9 @@ def add_qubits(self, qubits): if duplicate_qubits: raise DAGCircuitError("duplicate qubits %s" % duplicate_qubits) - self.qubits.extend(qubits) for qubit in qubits: + self.qubits.append(qubit) + self._qubit_indices[qubit] = BitLocations(len(self.qubits) - 1, []) self._add_wire(qubit) def add_clbits(self, clbits): @@ -239,8 +251,9 @@ def add_clbits(self, clbits): if duplicate_clbits: raise DAGCircuitError("duplicate clbits %s" % duplicate_clbits) - self.clbits.extend(clbits) for clbit in clbits: + self.clbits.append(clbit) + self._clbit_indices[clbit] = BitLocations(len(self.clbits) - 1, []) self._add_wire(clbit) def add_qreg(self, qreg): @@ -252,8 +265,13 @@ def add_qreg(self, qreg): self.qregs[qreg.name] = qreg existing_qubits = set(self.qubits) for j in range(qreg.size): + if qreg[j] in self._qubit_indices: + self._qubit_indices[qreg[j]].registers.append((qreg, j)) if qreg[j] not in existing_qubits: self.qubits.append(qreg[j]) + self._qubit_indices[qreg[j]] = BitLocations( + len(self.qubits) - 1, registers=[(qreg, j)] + ) self._add_wire(qreg[j]) def add_creg(self, creg): @@ -265,8 +283,13 @@ def add_creg(self, creg): self.cregs[creg.name] = creg existing_clbits = set(self.clbits) for j in range(creg.size): + if creg[j] in self._clbit_indices: + self._clbit_indices[creg[j]].registers.append((creg, j)) if creg[j] not in existing_clbits: self.clbits.append(creg[j]) + self._clbit_indices[creg[j]] = BitLocations( + len(self.clbits) - 1, registers=[(creg, j)] + ) self._add_wire(creg[j]) def _add_wire(self, wire): @@ -294,6 +317,38 @@ def _add_wire(self, wire): else: raise DAGCircuitError(f"duplicate wire {wire}") + def find_bit(self, bit: Bit) -> BitLocations: + """ + Finds locations in the circuit, by mapping the Qubit and Clbit to positional index + BitLocations is defined as: BitLocations = namedtuple("BitLocations", ("index", "registers")) + + Args: + bit (Bit): The bit to locate. + + Returns: + namedtuple(int, List[Tuple(Register, int)]): A 2-tuple. The first element (``index``) + contains the index at which the ``Bit`` can be found (in either + :obj:`~DAGCircuit.qubits`, :obj:`~DAGCircuit.clbits`, depending on its + type). The second element (``registers``) is a list of ``(register, index)`` + pairs with an entry for each :obj:`~Register` in the circuit which contains the + :obj:`~Bit` (and the index in the :obj:`~Register` at which it can be found). + + Raises: + DAGCircuitError: If the supplied :obj:`~Bit` was of an unknown type. + DAGCircuitError: If the supplied :obj:`~Bit` could not be found on the circuit. + """ + try: + if isinstance(bit, Qubit): + return self._qubit_indices[bit] + elif isinstance(bit, Clbit): + return self._clbit_indices[bit] + else: + raise DAGCircuitError(f"Could not locate bit of unknown type: {type(bit)}") + except KeyError as err: + raise DAGCircuitError( + f"Could not locate provided bit: {bit}. Has it been added to the DAGCircuit?" + ) from err + def remove_clbits(self, *clbits): """ Remove classical bits from the circuit. All bits MUST be idle. @@ -328,6 +383,11 @@ def remove_clbits(self, *clbits): for clbit in clbits: self._remove_idle_wire(clbit) self.clbits.remove(clbit) + del self._clbit_indices[clbit] + + # Update the indices of remaining clbits + for i, clbit in enumerate(self.clbits): + self._clbit_indices[clbit] = self._clbit_indices[clbit]._replace(index=i) def remove_cregs(self, *cregs): """ @@ -350,6 +410,10 @@ def remove_cregs(self, *cregs): for creg in cregs: del self.cregs[creg.name] + for j in range(creg.size): + bit = creg[j] + bit_position = self._clbit_indices[bit] + bit_position.registers.remove((creg, j)) def remove_qubits(self, *qubits): """ @@ -385,6 +449,11 @@ def remove_qubits(self, *qubits): for qubit in qubits: self._remove_idle_wire(qubit) self.qubits.remove(qubit) + del self._qubit_indices[qubit] + + # Update the indices of remaining qubits + for i, qubit in enumerate(self.qubits): + self._qubit_indices[qubit] = self._qubit_indices[qubit]._replace(index=i) def remove_qregs(self, *qregs): """ @@ -407,6 +476,10 @@ def remove_qregs(self, *qregs): for qreg in qregs: del self.qregs[qreg.name] + for j in range(qreg.size): + bit = qreg[j] + bit_position = self._qubit_indices[bit] + bit_position.registers.remove((qreg, j)) def _is_wire_idle(self, wire): """Check if a wire is idle. diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 924b18e755f6..5383f940c255 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -135,6 +135,7 @@ def run(self, dag): return dag qarg_indices = {qubit: index for index, qubit in enumerate(dag.qubits)} + # Names of instructions assumed to supported by any backend. if self._target is None: basic_instrs = ["measure", "reset", "barrier", "snapshot", "delay"] diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py index ed732bfd6dc0..a075aa9ebc23 100644 --- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py +++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py @@ -60,7 +60,6 @@ def run(self, dag): if self._target is None: basic_insts = {"measure", "reset", "barrier", "snapshot", "delay"} device_insts = basic_insts | set(self._basis_gates) - qubit_mapping = {bit: idx for idx, bit in enumerate(dag.qubits)} for node in dag.op_nodes(): if isinstance(node.op, ControlFlowOp): @@ -78,7 +77,7 @@ def run(self, dag): inst_supported = ( self._target.instruction_supported( operation_name=node.op.name, - qargs=tuple(qubit_mapping[x] for x in node.qargs), + qargs=tuple(dag.find_bit(x).index for x in node.qargs), ) if self._target is not None else node.name in device_insts diff --git a/qiskit/transpiler/passes/basis/unroller.py b/qiskit/transpiler/passes/basis/unroller.py index 806b61d752c6..aa5c389eee16 100644 --- a/qiskit/transpiler/passes/basis/unroller.py +++ b/qiskit/transpiler/passes/basis/unroller.py @@ -55,9 +55,6 @@ def run(self, dag): """ if self.basis is None and self.target is None: return dag - qubit_mapping = {} - if self.target is not None: - qubit_mapping = {bit: index for index, bit in enumerate(dag.qubits)} # Walk through the DAG and expand each non-basis node basic_insts = ["measure", "reset", "barrier", "snapshot", "delay"] for node in dag.op_nodes(): @@ -66,7 +63,7 @@ def run(self, dag): run_qubits = None if self.target is not None: - run_qubits = tuple(qubit_mapping[x] for x in node.qargs) + run_qubits = tuple(dag.find_bit(x).index for x in node.qargs) if ( self.target.instruction_supported(node.op.name, qargs=run_qubits) or node.op.name == "barrier" diff --git a/qiskit/transpiler/passes/calibration/base_builder.py b/qiskit/transpiler/passes/calibration/base_builder.py index 6d49c23abb31..d69dcb171871 100644 --- a/qiskit/transpiler/passes/calibration/base_builder.py +++ b/qiskit/transpiler/passes/calibration/base_builder.py @@ -60,9 +60,8 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Returns: A DAG with calibrations added to it. """ - qubit_map = {qubit: i for i, qubit in enumerate(dag.qubits)} for node in dag.gate_nodes(): - qubits = [qubit_map[q] for q in node.qargs] + qubits = [dag.find_bit(q).index for q in node.qargs] if self.supported(node.op, qubits) and not dag.has_calibration_for(node): # calibration can be provided and no user-defined calibration is already provided diff --git a/qiskit/transpiler/passes/layout/apply_layout.py b/qiskit/transpiler/passes/layout/apply_layout.py index cd26a1ec4e91..2f9305b23244 100644 --- a/qiskit/transpiler/passes/layout/apply_layout.py +++ b/qiskit/transpiler/passes/layout/apply_layout.py @@ -83,9 +83,8 @@ def run(self, dag): full_layout = Layout() old_phys_to_virtual = layout.get_physical_bits() new_virtual_to_physical = post_layout.get_virtual_bits() - qubit_index_map = {bit: index for index, bit in enumerate(dag.qubits)} for new_virt, new_phys in new_virtual_to_physical.items(): - old_phys = qubit_index_map[new_virt] + old_phys = dag.find_bit(new_virt).index old_virt = old_phys_to_virtual[old_phys] full_layout.add(old_virt, new_phys) for reg in layout.get_registers(): diff --git a/qiskit/transpiler/passes/layout/disjoint_utils.py b/qiskit/transpiler/passes/layout/disjoint_utils.py index 4b7ca06a094c..b714a3b22ba2 100644 --- a/qiskit/transpiler/passes/layout/disjoint_utils.py +++ b/qiskit/transpiler/passes/layout/disjoint_utils.py @@ -156,15 +156,14 @@ def require_layout_isolated_to_component( coupling_map = components_source.build_coupling_map(filter_idle_qubits=True) else: coupling_map = components_source - 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: + if dag.find_bit(inst.qargs[0]).index in component_set: component_index = i break - if qubit_indices[inst.qargs[1]] not in component_sets[component_index]: + if dag.find_bit(inst.qargs[1]).index not in component_sets[component_index]: raise TranspilerError("Chosen layout is not valid for the target disjoint connectivity") diff --git a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py index e7afc7788510..e9dcd0903d09 100644 --- a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py +++ b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py @@ -126,7 +126,6 @@ def collect_key(x): return "d" op_nodes = dag.topological_op_nodes(key=collect_key) - qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for nd in op_nodes: can_process = True @@ -140,7 +139,7 @@ def collect_key(x): ): can_process = False - cur_qubits = {qubit_indices[bit] for bit in nd.qargs} + cur_qubits = {dag.find_bit(bit).index for bit in nd.qargs} if can_process: # if the gate is valid, check if grouping up the bits diff --git a/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py b/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py index 20ae08586858..730b418fb2cf 100644 --- a/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py +++ b/qiskit/transpiler/passes/optimization/crosstalk_adaptive_schedule.py @@ -719,8 +719,8 @@ def run(self, dag): """ self.dag = dag - # process input program self.qubit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} + # process input program self.assign_gate_id(self.dag) self.extract_dag_overlap_sets(self.dag) self.extract_crosstalk_relevant_sets() diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py b/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py index 98f000df5d34..bf130693acad 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py @@ -194,7 +194,6 @@ def _step(self, dag): runs = dag.collect_1q_runs() did_work = False - qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for run in runs: # identify the preceding blocking gates run_clone = copy(run) @@ -218,7 +217,7 @@ def _step(self, dag): ) # re-synthesize - qubit = qubit_indices[run[0].qargs[0]] + qubit = dag.find_bit(run[0].qargs[0]).index new_preceding_run = self._resynthesize(preceding_run + commuted_preceding, qubit) new_succeeding_run = self._resynthesize(commuted_succeeding + succeeding_run, qubit) new_run = self._resynthesize(run_clone, qubit) @@ -229,7 +228,7 @@ def _step(self, dag): (preceding_run or []) + run + (succeeding_run or []), new_preceding_run.op_nodes() + new_run.op_nodes() + new_succeeding_run.op_nodes(), self._optimize1q._basis_gates, - qubit_indices[run[0].qargs[0]], + dag.find_bit(run[0].qargs[0]).index, ): if preceding_run and new_preceding_run is not None: self._replace_subdag(dag, preceding_run, new_preceding_run) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index e400dc3321e2..6e740511a38a 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -190,9 +190,8 @@ def run(self, dag): DAGCircuit: the optimized DAG. """ runs = dag.collect_1q_runs() - qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for run in runs: - qubit = qubit_indices[run[0].qargs[0]] + qubit = dag.find_bit(run[0].qargs[0]).index operator = run[0].op.to_matrix() for node in run[1:]: operator = node.op.to_matrix().dot(operator) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py index 97a7fbf188e8..1dd03a738e32 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_gates.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_gates.py @@ -66,14 +66,11 @@ def run(self, dag): use_u = "u" in self.basis use_p = "p" in self.basis runs = dag.collect_runs(["u1", "u2", "u3", "u", "p"]) - qubit_mapping = {} - if self.target is not None: - qubit_mapping = {bit: index for index, bit in enumerate(dag.qubits)} runs = _split_runs_on_parameters(runs) for run in runs: run_qubits = None if self.target is not None: - run_qubits = tuple(qubit_mapping[x] for x in run[0].qargs) + run_qubits = tuple(dag.find_bit(x).index for x in run[0].qargs) if self.target.instruction_supported("p", run_qubits): right_name = "p" diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py index 1b0c734bd0b8..60b5cf9e3ad3 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py @@ -167,13 +167,11 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: # Used to keep track of nodes that do not decompose using swap strategies. accumulator = new_dag.copy_empty_like() - self._bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} - for node in dag.topological_op_nodes(): if isinstance(node.op, Commuting2qBlock): # Check that the swap strategy creates enough connectivity for the node. - self._check_edges(node, swap_strategy) + self._check_edges(dag, node, swap_strategy) # Compose any accumulated non-swap strategy gates to the dag accumulator = self._compose_non_swap_nodes(accumulator, current_layout, new_dag) @@ -214,7 +212,7 @@ def _compose_non_swap_nodes( # Re-initialize the node accumulator return new_dag.copy_empty_like() - def _position_in_cmap(self, j: int, k: int, layout: Layout) -> tuple[int, ...]: + def _position_in_cmap(self, dag: DAGCircuit, j: int, k: int, layout: Layout) -> tuple[int, ...]: """A helper function to track the movement of virtual qubits through the swaps. Args: @@ -225,8 +223,8 @@ def _position_in_cmap(self, j: int, k: int, layout: Layout) -> tuple[int, ...]: Returns: The position in the coupling map of the virtual qubits j and k as a tuple. """ - bit0 = self._bit_indices[layout.get_physical_bits()[j]] - bit1 = self._bit_indices[layout.get_physical_bits()[k]] + bit0 = dag.find_bit(layout.get_physical_bits()[j]).index + bit1 = dag.find_bit(layout.get_physical_bits()[k]).index return bit0, bit1 @@ -325,7 +323,7 @@ def swap_decompose( # to all the gates that can be applied at the ith swap layer. current_layer = {} for (j, k), local_gate in gate_layers.get(i, {}).items(): - current_layer[self._position_in_cmap(j, k, current_layout)] = local_gate + current_layer[self._position_in_cmap(dag, j, k, current_layout)] = local_gate # Not all gates that are applied at the ith swap layer can be applied at the same # time. We therefore greedily build sub-layers. @@ -354,7 +352,7 @@ def _make_op_layers( gate_layers: dict[int, dict[tuple, Gate]] = defaultdict(dict) for node in op.node_block: - edge = (self._bit_indices[node.qargs[0]], self._bit_indices[node.qargs[1]]) + edge = (dag.find_bit(node.qargs[0]).index, dag.find_bit(node.qargs[1]).index) bit0 = layout.get_virtual_bits()[dag.qubits[edge[0]]] bit1 = layout.get_virtual_bits()[dag.qubits[edge[1]]] @@ -365,7 +363,7 @@ def _make_op_layers( return gate_layers - def _check_edges(self, node: DAGOpNode, swap_strategy: SwapStrategy): + def _check_edges(self, dag: DAGCircuit, node: DAGOpNode, swap_strategy: SwapStrategy): """Check if the swap strategy can create the required connectivity. Args: @@ -379,7 +377,7 @@ def _check_edges(self, node: DAGOpNode, swap_strategy: SwapStrategy): required_edges = set() for sub_node in node.op: - edge = (self._bit_indices[sub_node.qargs[0]], self._bit_indices[sub_node.qargs[1]]) + edge = (dag.find_bit(sub_node.qargs[0]).index, dag.find_bit(sub_node.qargs[1]).index) required_edges.add(edge) # Check that the swap strategy supports all required edges diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py index f24e0668c2b1..da804d97b2f0 100644 --- a/qiskit/transpiler/passes/routing/sabre_swap.py +++ b/qiskit/transpiler/passes/routing/sabre_swap.py @@ -226,7 +226,6 @@ def run(self, dag): canonical_register = dag.qregs["q"] current_layout = Layout.generate_trivial_layout(canonical_register) self._qubit_indices = {bit: idx for idx, bit in enumerate(canonical_register)} - self._clbit_indices = {bit: idx for idx, bit in enumerate(dag.clbits)} layout_mapping = { self._qubit_indices[k]: v for k, v in current_layout.get_virtual_bits().items() } diff --git a/qiskit/transpiler/passes/routing/stochastic_swap.py b/qiskit/transpiler/passes/routing/stochastic_swap.py index f2fdbfec5f76..067d955f91b7 100644 --- a/qiskit/transpiler/passes/routing/stochastic_swap.py +++ b/qiskit/transpiler/passes/routing/stochastic_swap.py @@ -88,7 +88,6 @@ def __init__(self, coupling_map, trials=20, seed=None, fake_run=False, initial_l self.fake_run = fake_run self.qregs = None self.initial_layout = initial_layout - self._qubit_to_int = None self._int_to_qubit = None def run(self, dag): @@ -124,7 +123,6 @@ def run(self, dag): self.initial_layout = Layout.generate_trivial_layout(canonical_register) # Qubit indices are used to assign an integer to each virtual qubit during the routing: it's # a mapping of {virtual: virtual}, for converting between Python and Rust forms. - self._qubit_to_int = {bit: idx for idx, bit in enumerate(dag.qubits)} self._int_to_qubit = tuple(dag.qubits) self.qregs = dag.qregs @@ -133,7 +131,7 @@ def run(self, dag): new_dag = self._mapper(dag, self.coupling_map, trials=self.trials) return new_dag - def _layer_permutation(self, layer_partition, layout, qubit_subset, coupling, trials): + def _layer_permutation(self, dag, layer_partition, layout, qubit_subset, coupling, trials): """Find a swap circuit that implements a permutation for this layer. The goal is to swap qubits such that qubits in the same two-qubit gates @@ -202,18 +200,18 @@ def _layer_permutation(self, layer_partition, layout, qubit_subset, coupling, tr cdist2 = coupling._dist_matrix**2 int_qubit_subset = np.fromiter( - (self._qubit_to_int[bit] for bit in qubit_subset), + (dag.find_bit(bit).index for bit in qubit_subset), dtype=np.uintp, count=len(qubit_subset), ) int_gates = np.fromiter( - (self._qubit_to_int[bit] for gate in gates for bit in gate), + (dag.find_bit(bit).index for gate in gates for bit in gate), dtype=np.uintp, count=2 * len(gates), ) - layout_mapping = {self._qubit_to_int[k]: v for k, v in layout.get_virtual_bits().items()} + layout_mapping = {dag.find_bit(k).index: v for k, v in layout.get_virtual_bits().items()} int_layout = nlayout.NLayout(layout_mapping, num_qubits, coupling.size()) trial_circuit = DAGCircuit() # SWAP circuit for slice of swaps in this trial @@ -313,7 +311,7 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): # First try and compute a route for the entire layer in one go. if not layer["graph"].op_nodes(op=ControlFlowOp): success_flag, best_circuit, best_depth, best_layout = self._layer_permutation( - layer["partition"], layout, qubit_subset, coupling_graph, trials + circuit_graph, layer["partition"], layout, qubit_subset, coupling_graph, trials ) logger.debug("mapper: layer %d", i) @@ -341,7 +339,12 @@ def _mapper(self, circuit_graph, coupling_graph, trials=20): ) else: (success_flag, best_circuit, best_depth, best_layout) = self._layer_permutation( - serial_layer["partition"], layout, qubit_subset, coupling_graph, trials + circuit_graph, + serial_layer["partition"], + layout, + qubit_subset, + coupling_graph, + trials, ) logger.debug("mapper: layer %d, sublayer %d", i, j) logger.debug( diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index 9f851d56b7e0..6bcf1f5e9e18 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -11,7 +11,6 @@ # that they have been altered from the originals. """Base circuit scheduling pass.""" - from typing import Dict from qiskit.transpiler import InstructionDurations from qiskit.transpiler.basepasses import TransformationPass diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index 6de3e49b5923..9183e816a4b0 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -198,14 +198,14 @@ def run(self, dag): index_sequence_duration_map[physical_qubit] = dd_sequence_duration new_dag = dag.copy_empty_like() - qubit_index_map = {qubit: index for index, qubit in enumerate(new_dag.qubits)} + for nd in dag.topological_op_nodes(): if not isinstance(nd.op, Delay): new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue dag_qubit = nd.qargs[0] - physical_qubit = qubit_index_map[dag_qubit] + physical_qubit = dag.find_bit(dag_qubit).index if physical_qubit not in self._qubits: # skip unwanted qubits new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) continue diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index 16d58dbae25e..d38a0b0e8661 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -102,7 +102,6 @@ def run(self, dag: DAGCircuit): new_dag.calibrations = dag.calibrations new_dag.global_phase = dag.global_phase - bit_indices = {q: index for index, q in enumerate(dag.qubits)} idle_after = {bit: 0 for bit in dag.qubits} # Compute fresh circuit duration from the node start time dictionary and op duration. @@ -125,7 +124,7 @@ def run(self, dag: DAGCircuit): for bit in node.qargs: # Fill idle time with some sequence - if t0 - idle_after[bit] > 0 and self.__delay_supported(bit_indices[bit]): + if t0 - idle_after[bit] > 0 and self.__delay_supported(dag.find_bit(bit).index): # Find previous node on the wire, i.e. always the latest node on the wire prev_node = next(new_dag.predecessors(new_dag.output_map[bit])) self._pad( @@ -148,7 +147,9 @@ def run(self, dag: DAGCircuit): # Add delays until the end of circuit. for bit in new_dag.qubits: - if circuit_duration - idle_after[bit] > 0 and self.__delay_supported(bit_indices[bit]): + if circuit_duration - idle_after[bit] > 0 and self.__delay_supported( + dag.find_bit(bit).index + ): node = new_dag.output_map[bit] prev_node = next(new_dag.predecessors(node)) self._pad( diff --git a/qiskit/transpiler/passes/scheduling/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/scheduling/asap.py index 1b0463d17387..bec66a994a28 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/asap.py @@ -46,7 +46,7 @@ def run(self, dag): node_start_time = {} idle_after = {q: 0 for q in dag.qubits + dag.clbits} - bit_indices = {q: index for index, q in enumerate(dag.qubits)} + bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in dag.topological_op_nodes(): op_duration = self._get_node_duration(node, bit_indices, dag) diff --git a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py index 682fb667c90f..01227350919e 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py @@ -13,7 +13,6 @@ """Base circuit scheduling pass.""" import warnings - from typing import Dict from qiskit.transpiler import InstructionDurations from qiskit.transpiler.basepasses import AnalysisPass diff --git a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py index 57c9587316b7..a176f0aa5c9a 100644 --- a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py +++ b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py @@ -94,12 +94,11 @@ def run(self, dag: DAGCircuit): ) # Make units consistent - bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in dag.op_nodes(): try: node.op = node.op.copy() node.op.duration = self.inst_durations.get( - node.op, [bit_indices[qarg] for qarg in node.qargs], unit=time_unit + node.op, [dag.find_bit(qarg).index for qarg in node.qargs], unit=time_unit ) node.op.unit = time_unit except TranspilerError: diff --git a/qiskit/transpiler/passes/utils/check_gate_direction.py b/qiskit/transpiler/passes/utils/check_gate_direction.py index 7a32800ab882..1ddfd40124b5 100644 --- a/qiskit/transpiler/passes/utils/check_gate_direction.py +++ b/qiskit/transpiler/passes/utils/check_gate_direction.py @@ -44,6 +44,7 @@ def _coupling_map_visit(self, dag, wire_map, edges=None): inner_wire_map = { inner: wire_map[outer] for outer, inner in zip(node.qargs, block.qubits) } + if not self._coupling_map_visit(circuit_to_dag(block), inner_wire_map, edges): return False elif ( diff --git a/releasenotes/notes/add-method-for-mapping-qubit-clbit-to-positional-index-6cd43a42f56eb549.yaml b/releasenotes/notes/add-method-for-mapping-qubit-clbit-to-positional-index-6cd43a42f56eb549.yaml new file mode 100644 index 000000000000..50200ff8e94f --- /dev/null +++ b/releasenotes/notes/add-method-for-mapping-qubit-clbit-to-positional-index-6cd43a42f56eb549.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + A new method, :meth:`~qiskit.dagcircuit.dag.find_bit`, has + been added to the :class:`~qiskit.dagcircuit.DagCircuit` class. + This is a method to get mapping of Qubit and Clbit to positional index. + The method takes a Bit as input, checks whether it's a Qubit or a Clbit, + and then returns the corresponding BitLocations from the respective index + dictionary (_qubit_indices or _clbit_indices). + The BitLocations is a namedtuple which includes the positional index of the + bit in the circuit and a list of registers containing the bit. If the bit + cannot be found or is of an unknown type, it raises a DAGCircuitError. \ No newline at end of file diff --git a/test/python/dagcircuit/test_dagcircuit.py b/test/python/dagcircuit/test_dagcircuit.py index 71a30497784c..c9fda40f4c17 100644 --- a/test/python/dagcircuit/test_dagcircuit.py +++ b/test/python/dagcircuit/test_dagcircuit.py @@ -253,6 +253,60 @@ def test_adding_individual_bit(self): self.assertEqual(dag.qubits, list(qr) + [new_bit]) self.assertEqual(list(dag.qregs.values()), [qr]) + def test_find_bit_with_registers(self): + """Test find_bit with a register.""" + qr = QuantumRegister(3, "qr") + dag = DAGCircuit() + dag.add_qreg(qr) + res = dag.find_bit(qr[2]) + self.assertEqual(res.index, 2) + self.assertEqual(res.registers, [(qr, 2)]) + + def test_find_bit_with_registers_and_standalone(self): + """Test find_bit with a register and standalone bit.""" + qr = QuantumRegister(3, "qr") + dag = DAGCircuit() + dag.add_qreg(qr) + new_bit = Qubit() + dag.add_qubits([new_bit]) + res = dag.find_bit(qr[2]) + self.assertEqual(res.index, 2) + bit_res = dag.find_bit(new_bit) + self.assertEqual(bit_res.index, 3) + self.assertEqual(bit_res.registers, []) + + def test_find_bit_with_classical_registers_and_standalone(self): + """Test find_bit with a register and standalone bit.""" + qr = QuantumRegister(3, "qr") + cr = ClassicalRegister(3, "C") + dag = DAGCircuit() + dag.add_qreg(qr) + dag.add_creg(cr) + new_bit = Qubit() + new_clbit = Clbit() + dag.add_qubits([new_bit]) + dag.add_clbits([new_clbit]) + res = dag.find_bit(qr[2]) + self.assertEqual(res.index, 2) + bit_res = dag.find_bit(new_bit) + self.assertEqual(bit_res.index, 3) + self.assertEqual(bit_res.registers, []) + classical_res = dag.find_bit(cr[2]) + self.assertEqual(classical_res.index, 2) + self.assertEqual(classical_res.registers, [(cr, 2)]) + single_cl_bit_res = dag.find_bit(new_clbit) + self.assertEqual(single_cl_bit_res.index, 3) + self.assertEqual(single_cl_bit_res.registers, []) + + def test_find_bit_missing(self): + """Test error when find_bit is called with missing bit.""" + qr = QuantumRegister(3, "qr") + dag = DAGCircuit() + dag.add_qreg(qr) + new_bit = Qubit() + with self.assertRaises(DAGCircuitError): + dag.find_bit(new_bit) + class TestDagWireRemoval(QiskitTestCase): """Test removal of registers and idle wires."""