Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add full path transpile() support for disjoint backends #9840

Merged
merged 14 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 47 additions & 38 deletions qiskit/transpiler/passes/layout/dense_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`.
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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,
kevinhartman marked this conversation as resolved.
Show resolved Hide resolved
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()
Expand All @@ -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:
Expand All @@ -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 = {
Expand All @@ -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
15 changes: 15 additions & 0 deletions qiskit/transpiler/passes/layout/disjoint_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
kevinhartman marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
5 changes: 0 additions & 5 deletions qiskit/transpiler/passes/layout/trivial_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
)
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/routing/basic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/routing/bip_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/routing/lookahead_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/routing/sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions qiskit/transpiler/passes/routing/stochastic_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"]
Expand Down
Loading