-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Token swapper permutation synthesis plugin #10657
Changes from 28 commits
4181428
ace8693
3f948d2
1d6ee58
5858b3d
74ccc4b
a689b02
e725b37
e3e2c3f
816ac1e
a4b97c7
2eb7075
8677dcb
e15d739
ecd793e
9dd1f85
73e80a7
3389c31
fb8956d
e111e63
ef4e877
e9b4766
24ae130
8e65d74
f06dc65
5e9ff47
7886221
c3bd53b
b617f6f
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 |
---|---|---|
|
@@ -15,6 +15,8 @@ | |
|
||
from typing import Optional, Union, List, Tuple | ||
|
||
import rustworkx as rx | ||
|
||
from qiskit.circuit.operation import Operation | ||
from qiskit.converters import circuit_to_dag, dag_to_circuit | ||
from qiskit.transpiler.basepasses import TransformationPass | ||
|
@@ -25,6 +27,7 @@ | |
from qiskit.transpiler.coupling import CouplingMap | ||
from qiskit.dagcircuit.dagcircuit import DAGCircuit | ||
from qiskit.transpiler.exceptions import TranspilerError | ||
from qiskit.transpiler.passes.routing.algorithms import ApproximateTokenSwapper | ||
|
||
from qiskit.circuit.annotated_operation import ( | ||
AnnotatedOperation, | ||
|
@@ -297,7 +300,7 @@ def _recursively_handle_op( | |
|
||
# Try to apply plugin mechanism | ||
decomposition = self._synthesize_op_using_plugins(op, qubits) | ||
if decomposition: | ||
if decomposition is not None: | ||
return decomposition, True | ||
|
||
# Handle annotated operations | ||
|
@@ -644,3 +647,80 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, ** | |
"""Run synthesis for the given Permutation.""" | ||
decomposition = synth_permutation_acg(high_level_object.pattern) | ||
return decomposition | ||
|
||
|
||
class TokenSwapperSynthesisPermutation(HighLevelSynthesisPlugin): | ||
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. We need to add this class somewhere in the toctree to render the docstring here. Right now this class (nor any of the HLS plugins) is not being included in the documentation builds. I was talking to @Cryoris offline about this a bit the other day as there isn't a unified place to document this right now things are spread out a bit too much in the organizational structure. I think for right now if you added a HLS Plugins section to the module docstring in |
||
"""The permutation synthesis plugin based on the token swapper algorithm. | ||
|
||
This plugin name is :``permutation.token_swapper`` which can be used as the key on | ||
an :class:`~.HLSConfig` object to use this method with :class:`~.HighLevelSynthesis`. | ||
|
||
In more detail, this plugin is used to synthesize objects of type `PermutationGate`. | ||
When synthesis succeeds, the plugin outputs a quantum circuit consisting only of swap | ||
gates. When synthesis does not succeed, the plugin outputs `None`. | ||
|
||
If either `coupling_map` or `qubits` is None, then the synthesized circuit | ||
is not required to adhere to connectivity constraints, as is the case | ||
when the synthesis is done before layout/routing. | ||
|
||
On the other hand, if both `coupling_map` and `qubits` are specified, the synthesized | ||
circuit is supposed to adhere to connectivity constraints. At the moment, the | ||
plugin only creates swap gates between qubits in `qubits`, i.e. it does not use | ||
any other qubits in the coupling map (if such synthesis is not possible, the | ||
plugin outputs `None`). | ||
|
||
The plugin supports the following plugin-specific options: | ||
|
||
* trials: The number of trials for the token swapper to perform the mapping. The | ||
circuit with the smallest number of SWAPs is returned. | ||
* seed: The argument to the token swapper specifying the seed for random trials. | ||
* parallel_threshold: The argument to the token swapper specifying the number of nodes | ||
in the graph beyond which the algorithm will use parallel processing. | ||
|
||
For more details on the token swapper algorithm, see to the paper: | ||
`arXiv:1902.09102 <https://arxiv.org/abs/1902.09102>`__. | ||
|
||
""" | ||
|
||
def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **options): | ||
"""Run synthesis for the given Permutation.""" | ||
|
||
trials = options.get("trials", 5) | ||
seed = options.get("seed", 0) | ||
parallel_threshold = options.get("parallel_threshold", 50) | ||
|
||
pattern = high_level_object.pattern | ||
pattern_as_dict = {j: i for i, j in enumerate(pattern)} | ||
|
||
# When the plugin is called from the HighLevelSynthesis transpiler pass, | ||
# the coupling map already takes target into account. | ||
if coupling_map is None or qubits is None: | ||
mtreinish marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# The abstract synthesis uses a fully connected coupling map, allowing | ||
# arbitrary connections between qubits. | ||
used_coupling_map = CouplingMap.from_full(len(pattern)) | ||
else: | ||
# The concrete synthesis uses the coupling map restricted to the set of | ||
# qubits over which the permutation gate is defined. If we allow using other | ||
# qubits in the coupling map, replacing the node in the DAGCircuit that | ||
# defines this PermutationGate by the DAG corresponding to the constructed | ||
# decomposition becomes problematic. Note that we allow the reduced | ||
# coupling map to be disconnected. | ||
used_coupling_map = coupling_map.reduce(qubits, check_if_connected=False) | ||
|
||
graph = used_coupling_map.graph.to_undirected() | ||
swapper = ApproximateTokenSwapper(graph, seed=seed) | ||
|
||
try: | ||
swapper_result = swapper.map( | ||
pattern_as_dict, trials, parallel_threshold=parallel_threshold | ||
) | ||
except rx.InvalidMapping: | ||
swapper_result = None | ||
|
||
if swapper_result is not None: | ||
decomposition = QuantumCircuit(len(graph.node_indices())) | ||
for swap in swapper_result: | ||
decomposition.swap(*swap) | ||
return decomposition | ||
|
||
return None |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
--- | ||
upgrade: | ||
- | | ||
Qiskit 1.0 now requires version 0.14.0 of ``rustworkx``. | ||
features: | ||
- | | ||
Added a new :class:`.HighLevelSynthesisPlugin` for :class:`.PermutationGate` | ||
objects based on Qiskit's token swapper algorithm. To use this plugin, | ||
specify ``token_swapper`` when defining high-level-synthesis config. | ||
|
||
This synthesis plugin is able to run before or after the layout is set. | ||
When synthesis succeeds, the plugin outputs a quantum circuit consisting only of | ||
swap gates. When synthesis does not succeed, the plugin outputs `None`. | ||
|
||
The following code illustrates how the new plugin can be run:: | ||
|
||
from qiskit.circuit import QuantumCircuit | ||
from qiskit.circuit.library import PermutationGate | ||
from qiskit.transpiler import PassManager, CouplingMap | ||
from qiskit.transpiler.passes.synthesis.high_level_synthesis import HighLevelSynthesis, HLSConfig | ||
|
||
# This creates a circuit with a permutation gate. | ||
qc = QuantumCircuit(8) | ||
perm_gate = PermutationGate([0, 1, 4, 3, 2]) | ||
qc.append(perm_gate, [3, 4, 5, 6, 7]) | ||
|
||
# This defines the coupling map. | ||
coupling_map = CouplingMap.from_ring(8) | ||
|
||
# This high-level-synthesis config specifies that we want to use | ||
# the "token_swapper" plugin for synthesizing permutation gates, | ||
# with the option to use 10 trials. | ||
synthesis_config = HLSConfig(permutation=[("token_swapper", {"trials": 10})]) | ||
|
||
# This creates the pass manager that runs high-level-synthesis on our circuit. | ||
# The option use_qubit_indices=True indicates that synthesis run after the layout is set, | ||
# and hence should preserve the specified coupling map. | ||
pm = PassManager( | ||
HighLevelSynthesis( | ||
synthesis_config, coupling_map=coupling_map, target=None, use_qubit_indices=True | ||
) | ||
) | ||
|
||
qc_transpiled = pm.run(qc) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
rustworkx>=0.13.0 | ||
rustworkx>=0.14.0 | ||
numpy>=1.17,<2 | ||
scipy>=1.5 | ||
sympy>=1.3 | ||
|
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.
This probably fixes a minor bug in the existing code, though previously after defining an empty circuit
qc = Quantum(3)
, the checkif qc:
would returnTrue
, but now it returnsFalse
.