Skip to content

Commit

Permalink
Add find_bit method to DAGCircuit to get mapping of bits to positiona…
Browse files Browse the repository at this point in the history
…l index (Qiskit#10128)

* Added locate_bit method

* Changed transpilers passes to use find_bit method

* New changes, still need to fix

* Adding find_bit method to transpiler passes

* Added DAGCircuit method to get mapping of Qubit and Clbit to positional index Issue#9389

* added release notes for issue#9389

* fixed some formatting issues

* fixes for run, lint and doc errors

* updated releasenote and bip_model.py

* variable fix in bip_model.py

* reverted changes of bip_model.py

* revised files for updated usage of find_bit

* added changes to files noted

* Add test coverage

* Update releasenotes/notes/add-method-for-mapping-qubit-clbit-to-positional-index-6cd43a42f56eb549.yaml

---------

Co-authored-by: Matthew Treinish <[email protected]>
  • Loading branch information
2 people authored and to24toro committed Aug 3, 2023
1 parent 876efc4 commit 478cebb
Show file tree
Hide file tree
Showing 24 changed files with 184 additions and 58 deletions.
81 changes: 77 additions & 4 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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):
"""
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/basis/basis_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
3 changes: 1 addition & 2 deletions qiskit/transpiler/passes/basis/unroll_custom_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down
5 changes: 1 addition & 4 deletions qiskit/transpiler/passes/basis/unroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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"
Expand Down
3 changes: 1 addition & 2 deletions qiskit/transpiler/passes/calibration/base_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions qiskit/transpiler/passes/layout/apply_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
5 changes: 2 additions & 3 deletions qiskit/transpiler/passes/layout/disjoint_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 1 addition & 4 deletions qiskit/transpiler/passes/optimization/optimize_1q_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading

0 comments on commit 478cebb

Please sign in to comment.