Source code for qiskit.transpiler.passes.layout.dense_layout
# 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.
"""Choose a Layout by finding the most connected subset of qubits."""
import numpy as np
import rustworkx
from qiskit.transpiler.layout import Layout
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit._accelerate.dense_layout import best_subset
[docs]class DenseLayout(AnalysisPass):
"""Choose a Layout by finding the most connected subset of qubits.
This pass associates a physical qubit (int) to each virtual qubit
of the circuit (Qubit).
Note:
Even though a 'layout' is not strictly a property of the DAG,
in the transpiler architecture it is best passed around between passes
by being set in `property_set`.
"""
def __init__(self, coupling_map=None, backend_prop=None, target=None):
"""DenseLayout initializer.
Args:
coupling_map (Coupling): directed graph representing a coupling map.
backend_prop (BackendProperties): backend properties object
target (Target): A target representing the target backend.
"""
super().__init__()
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
)
[docs] def run(self, dag):
"""Run the DenseLayout pass on `dag`.
Pick a convenient layout depending on the best matching
qubit connectivity, and set the property `layout`.
Args:
dag (DAGCircuit): DAG to find layout for.
Raises:
TranspilerError: if dag wider than self.coupling_map
"""
if self.coupling_map is None:
raise TranspilerError(
"A coupling_map or target with constrained qargs is necessary to run the pass."
)
num_dag_qubits = len(dag.qubits)
if num_dag_qubits > self.coupling_map.size():
raise TranspilerError("Number of qubits greater than device.")
num_cx = 0
num_meas = 0
if self.target is not None:
num_cx = 1
num_meas = 1
else:
# Get avg number of cx and meas per qubit
ops = dag.count_ops(recurse=True)
if "cx" in ops.keys():
num_cx = ops["cx"]
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
def _best_subset(self, num_qubits, num_meas, num_cx):
"""Computes the qubit mapping with the best connectivity.
Args:
num_qubits (int): Number of subset qubits to consider.
Returns:
ndarray: Array of qubits to use for best connectivity mapping.
"""
from scipy.sparse import coo_matrix, csgraph
if num_qubits == 1:
return np.array([0])
if num_qubits == 0:
return []
rows, cols, best_map = best_subset(
num_qubits,
self.adjacency_matrix,
num_meas,
num_cx,
self._use_error,
self.coupling_map.is_symmetric,
self.error_mat,
)
data = [1] * len(rows)
sp_sub_graph = coo_matrix((data, (rows, cols)), shape=(num_qubits, num_qubits)).tocsr()
perm = csgraph.reverse_cuthill_mckee(sp_sub_graph)
best_map = best_map[perm]
return best_map
def _build_error_matrix(num_qubits, 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:
for qargs in target.qargs:
# Ignore gates over 2q DenseLayout only works with 2q
if len(qargs) > 2:
continue
error = 0.0
ops = target.operation_names_for_qargs(qargs)
for op in ops:
props = target[op].get(qargs, None)
if props is not None and props.error is not None:
# Use max error rate to represent operation error
# on a qubit(s). If there is more than 1 operation available
# we don't know what will be used on the qubits eventually
# so we take the highest error operation as a proxy for
# the possible worst case.
error = max(error, props.error)
max_error = error
# TODO: Factor in T1 and T2 to error matrix after #7736
if len(qargs) == 1:
qubit = qargs[0]
error_mat[qubit][qubit] = max_error
use_error = True
elif len(qargs) == 2:
error_mat[qargs[0]][qargs[1]] = max_error
use_error = True
elif backend_prop and coupling_map:
error_dict = {
tuple(gate.qubits): gate.parameters[0].value
for gate in backend_prop.gates
if len(gate.qubits) == 2
}
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
use_error = True
for index, qubit_data in enumerate(backend_prop.qubits):
# Handle faulty qubits edge case
if index >= num_qubits:
break
for item in qubit_data:
if item.name == "readout_error":
error_mat[index][index] = item.value
use_error = True
return error_mat, use_error