-
Notifications
You must be signed in to change notification settings - Fork 2.9k
nonuniform cost of physical swaps in sabre #9904
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
Changes from all commits
ed529f1
9a1e49b
9a247bb
9b0c174
5484e3c
8d979c9
1f244b8
d2e7135
f9b04ca
be3ec18
1ed4a68
26f71be
7505c24
86b00f0
109e4bd
5c081c2
737b6d8
12f3ae6
bd1c69f
4077598
9c8fbe3
27434c3
5e2dc23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,8 @@ | |
| NeighborTable, | ||
| ) | ||
| from qiskit.transpiler.passes.routing.sabre_swap import process_swaps, apply_gate | ||
| from qiskit.transpiler.target import Target | ||
| from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition | ||
| from qiskit.tools.parallel import CPU_COUNT | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
@@ -86,7 +88,7 @@ def __init__( | |
| """SabreLayout initializer. | ||
|
|
||
| Args: | ||
| coupling_map (Coupling): directed graph representing a coupling map. | ||
| coupling_map (Union[CouplingMap, Target]): directed graph representing a coupling map. | ||
| routing_pass (BasePass): the routing pass to use while iterating. | ||
| If specified this pass operates as an :class:`~.AnalysisPass` and | ||
| will only populate the ``layout`` field in the property set and | ||
|
|
@@ -124,17 +126,13 @@ def __init__( | |
| both ``routing_pass`` and ``layout_trials`` are specified | ||
| """ | ||
| super().__init__() | ||
| self.coupling_map = coupling_map | ||
| if isinstance(coupling_map, Target): | ||
| self.target = coupling_map | ||
| self.coupling_map = self.target.build_coupling_map() | ||
| else: | ||
| self.target = None | ||
| self.coupling_map = coupling_map | ||
| self._neighbor_table = None | ||
| if self.coupling_map is not None: | ||
| if not self.coupling_map.is_symmetric: | ||
| # deepcopy is needed here to avoid modifications updating | ||
| # shared references in passes which require directional | ||
| # constraints | ||
| self.coupling_map = copy.deepcopy(self.coupling_map) | ||
| self.coupling_map.make_symmetric() | ||
| self._neighbor_table = NeighborTable(rx.adjacency_matrix(self.coupling_map.graph)) | ||
|
|
||
| if routing_pass is not None and (swap_trials is not None or layout_trials is not None): | ||
| raise TranspilerError("Both routing_pass and swap_trials can't be set at the same time") | ||
| self.routing_pass = routing_pass | ||
|
|
@@ -150,6 +148,14 @@ def __init__( | |
| else: | ||
| self.layout_trials = layout_trials | ||
| self.skip_routing = skip_routing | ||
| if self.coupling_map is not None: | ||
| if not self.coupling_map.is_symmetric: | ||
| # deepcopy is needed here to avoid modifications updating | ||
| # shared references in passes which require directional | ||
| # constraints | ||
| self.coupling_map = copy.deepcopy(self.coupling_map) | ||
| self.coupling_map.make_symmetric() | ||
| self._neighbor_table = NeighborTable(rx.adjacency_matrix(self.coupling_map.graph)) | ||
|
|
||
| def run(self, dag): | ||
| """Run the SabreLayout pass on `dag`. | ||
|
|
@@ -210,7 +216,18 @@ def run(self, dag): | |
| self.property_set["layout"] = initial_layout | ||
| self.routing_pass.fake_run = False | ||
| return dag | ||
|
|
||
| dist_matrix = self.coupling_map.distance_matrix | ||
| print('original distance matrix') | ||
| print(dist_matrix) | ||
|
|
||
| if self.target: | ||
| dist_matrix = rx.digraph_floyd_warshall_numpy( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think that it makes sense to wrap this in a try/except if the target basis isn't known to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah you are right i still need to write test cases for the different cases we may encounter |
||
| self.coupling_map.graph, weight_fn=lambda x: self._swap_cost(x) | ||
| ) | ||
| print('new distance matrix') | ||
| print(dist_matrix) | ||
|
|
||
| original_qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)} | ||
| original_clbit_indices = {bit: index for index, bit in enumerate(dag.clbits)} | ||
|
|
||
|
|
@@ -322,3 +339,38 @@ def _compose_layouts(self, initial_layout, pass_final_layout, qregs): | |
| qubit_map = Layout.combine_into_edge_map(initial_layout, trivial_layout) | ||
| final_layout = {v: pass_final_layout._v2p[qubit_map[v]] for v in initial_layout._v2p} | ||
| return Layout(final_layout) | ||
|
|
||
| def _swap_cost(self, instructions): | ||
| """Get the minimum cost of a SWAP on a pair of physical qubits given a list | ||
| of available instructions on those qubits. | ||
|
|
||
| TODO: this function only considers certain known basis for which Qiskit knows | ||
| analytical decompositions. This can be generalized to arbitrary basis using the | ||
| tools of monodromy polytopes. | ||
|
|
||
| Args: | ||
| instructions (list[dict]): mappings of gate name to properties for that gate. | ||
|
|
||
| Return: | ||
| float: minimum cost of performing SWAP using the available physical instructions. | ||
| """ | ||
| swap_cost = np.inf | ||
| for key, value in instructions.items(): | ||
| weyl = TwoQubitWeylDecomposition(self.target.operation_from_name(key)) | ||
| a, b, c = weyl.a, weyl.b, weyl.c | ||
| if a == b == c == np.pi / 4: # 1 x SWAP | ||
| gates_per_swap = 1 | ||
| elif b == c == 0: # k x controlled XX(theta) | ||
| gates_per_swap = 3 * (np.pi / 2 / (2 * a)) | ||
| elif a == np.pi / 4 and c == 0: # 3 x supercontrolled | ||
| gates_per_swap = 3 | ||
| elif a == b == abs(c) == np.pi / 8: # 2 x sqrt(SWAP) | ||
| gates_per_swap = 2 | ||
| elif a == b == np.pi / 8 and c == 0: # 2 x sqrt(iSWAP) | ||
| gates_per_swap = 3 | ||
| elif (a, b, c) == (np.pi / 4, np.pi / 8, 0): # 2 x B | ||
| gates_per_swap = 2 | ||
|
|
||
| if gates_per_swap * value.error < swap_cost: | ||
| swap_cost = gates_per_swap * value.error | ||
| return swap_cost | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to put this in an else condition because this will probably compute the distance matrix (it is cached so if it was used before it won't) which can be expensive depending on the size of the graph. We probably don't want to do build two distance matrices if we're only going to use one.