Skip to content

Commit

Permalink
Token swapper permutation synthesis plugin (#10657)
Browse files Browse the repository at this point in the history
* trying to implement permutation synthesis plugin based on token swapper

* improving plugin and tests

* pylint fixes

* exposing seed and parallel_threshold

* release notes

* clarification comment

* improved support for disconnected coupling maps

* unused import

* fix arxiv reference

* minor fix

* fix merge

* more merge fixes

* fixing imports

* updating toml file

* additional fixes

* better way to find the position in the circuit

* bump rustworkx version to 0.14.0

* doc and autosummary improvements

* Update plugin docs configuration

* Remove autosummary for available plugins list

This commit removes the autosummary directives for building the
documentation for the plugin classes. In the interest of time and
combining it with the existing aqc docs we'll do this in a follow up
for 1.0.0 after 1.0.0rc1 has been tagged.

---------

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
  • Loading branch information
alexanderivrii and mtreinish authored Jan 30, 2024
1 parent d033e8a commit 738d3e4
Show file tree
Hide file tree
Showing 6 changed files with 344 additions and 6 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ sk = "qiskit.transpiler.passes.synthesis.solovay_kitaev_synthesis:SolovayKitaevS
"permutation.kms" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:KMSSynthesisPermutation"
"permutation.basic" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:BasicSynthesisPermutation"
"permutation.acg" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:ACGSynthesisPermutation"
"permutation.token_swapper" = "qiskit.transpiler.passes.synthesis.high_level_synthesis:TokenSwapperSynthesisPermutation"

[project.entry-points."qiskit.transpiler.init"]
default = "qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultInitPassManager"
Expand Down
82 changes: 81 additions & 1 deletion qiskit/transpiler/passes/synthesis/high_level_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
"""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:
# 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
31 changes: 30 additions & 1 deletion qiskit/transpiler/passes/synthesis/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,36 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **
will return a list of all the installed Clifford synthesis plugins.
Available Plugins
-----------------
High-level synthesis plugins that are directly available in Qiskit include plugins
for synthesizing :class:`.Clifford` objects, :class:`.LinearFunction` objects, and
:class:`.PermutationGate` objects.
Some of these plugins implicitly target all-to-all connectivity. This is not a
practical limitation since
:class:`~qiskit.transpiler.passes.synthesis.high_level_synthesis.HighLevelSynthesis`
typically runs before layout and routing, which will ensure that the final circuit
adheres to the device connectivity by inserting additional SWAP gates. A good example
is the permutation synthesis plugin ``ACGSynthesisPermutation`` which can synthesize
any permutation with at most 2 layers of SWAP gates.
On the other hand, some plugins implicitly target linear connectivity.
Typically, the synthesizing circuits have larger depth and the number of gates,
however no additional SWAP gates would be inserted if the following layout pass chose a
consecutive line of qubits inside the topology of the device. A good example of this is
the permutation synthesis plugin ``KMSSynthesisPermutation`` which can synthesize any
permutation of ``n`` qubits in depth ``n``. Typically, it is difficult to know in advance
which of the two approaches: synthesizing circuits for all-to-all connectivity and
inserting SWAP gates vs. synthesizing circuits for linear connectivity and inserting less
or no SWAP gates lead a better final circuit, so it likely makes sense to try both and
see which gives better results.
Finally, some plugins can target a given connectivity, and hence should be run after the
layout is set. In this case the synthesized circuit automatically adheres to
the topology of the device. A good example of this is the permutation synthesis plugin
``TokenSwapperSynthesisPermutation`` which is able to synthesize arbitrary permutations
with respect to arbitrary coupling maps.
For more detail, please refer to description of each individual plugin.
Plugin API
==========
Expand All @@ -306,7 +336,6 @@ def run(self, high_level_object, coupling_map=None, target=None, qubits=None, **
HighLevelSynthesisPlugin
HighLevelSynthesisPluginManager
high_level_synthesis_plugin_names
"""

import abc
Expand Down
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)
2 changes: 1 addition & 1 deletion requirements.txt
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
Expand Down
Loading

0 comments on commit 738d3e4

Please sign in to comment.