Skip to content

Commit

Permalink
Alternative construction mechanism for HLS config (#9413)
Browse files Browse the repository at this point in the history
* Alternative construction mechanism for HLS config

* improved how synthesis methods can be specified

* fixes

* pass over release notes

* adding tests for short form

* Updating the documentation of HLSConfig and HighLevelSynthesis

* removing unnecessary import in documentation

---------

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>
  • Loading branch information
alexanderivrii and 1ucian0 committed Mar 14, 2023
1 parent 6bb4957 commit fa4a136
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 18 deletions.
66 changes: 50 additions & 16 deletions qiskit/transpiler/passes/synthesis/high_level_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,23 @@ class HLSConfig:
of higher-level-objects. A higher-level object is an object of type
:class:`~.Operation` (e.g., "clifford", "linear_function", etc.), and the list
of applicable synthesis methods is strictly tied to the name of the operation.
In the config, each method is represented by a pair consisting of a name of the synthesis
algorithm and of a dictionary providing additional arguments for this algorithm.
In the config, each method is specified as a tuple consisting of the name of the
synthesis algorithm and of a dictionary providing additional arguments for this
algorithm. Additionally, a synthesis method can be specified as a tuple consisting
of an instance of :class:`.HighLevelSynthesisPlugin` and additional arguments.
Moreover, when there are no additional arguments, a synthesis
method can be specified simply by name or by an instance
of :class:`.HighLevelSynthesisPlugin`. The following example illustrates different
ways how a config file can be created::
from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
from qiskit.transpiler.passes.synthesis.high_level_synthesis import ACGSynthesisPermutation
# All the ways to specify hls_config are equivalent
hls_config = HLSConfig(permutation=[("acg", {})])
hls_config = HLSConfig(permutation=["acg"])
hls_config = HLSConfig(permutation=[(ACGSynthesisPermutation(), {})])
hls_config = HLSConfig(permutation=[ACGSynthesisPermutation()])
The names of the synthesis algorithms should be declared in ``entry_points`` for
``qiskit.synthesis`` in ``setup.py``, in the form
Expand All @@ -44,7 +59,7 @@ class HLSConfig:
To avoid synthesizing a given higher-level-object, one can give it an empty list of methods.
For an explicit example of creating and using such config files, refer to the
For an explicit example of using such config files, refer to the
documentation for :class:`~.HighLevelSynthesis`.
"""

Expand Down Expand Up @@ -73,9 +88,10 @@ def set_methods(self, hls_name, hls_methods):


class HighLevelSynthesis(TransformationPass):
"""Synthesize higher-level objects by choosing the appropriate synthesis method
based on the object's name and the high-level-synthesis config of type
:class:`~.HLSConfig` (if provided).
"""Synthesize higher-level objects using synthesis plugins.
Synthesis plugins apply synthesis methods specified in the high-level-synthesis
config (refer to the documentation for :class:`~.HLSConfig`).
As an example, let us assume that ``op_a`` and ``op_b`` are names of two higher-level objects,
that ``op_a``-objects have two synthesis methods ``default`` which does require any additional
Expand Down Expand Up @@ -127,20 +143,38 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
# the operation's name does not appear in the user-specified config,
# we use the "default" method when instructed to do so and the "default"
# method is available
methods = [("default", {})]
methods = ["default"]
else:
methods = []

for method in methods:
plugin_name, plugin_args = method

if plugin_name not in hls_plugin_manager.method_names(node.name):
raise TranspilerError(
"Specified method: %s not found in available plugins for %s"
% (plugin_name, node.name)
)

plugin_method = hls_plugin_manager.method(node.name, plugin_name)
# There are two ways to specify a synthesis method. The more explicit
# way is to specify it as a tuple consisting of a synthesis algorithm and a
# list of additional arguments, e.g.,
# ("kms", {"all_mats": 1, "max_paths": 100, "orig_circuit": 0}), or
# ("pmh", {}).
# When the list of additional arguments is empty, one can also specify
# just the synthesis algorithm, e.g.,
# "pmh".
if isinstance(method, tuple):
plugin_specifier, plugin_args = method
else:
plugin_specifier = method
plugin_args = {}

# There are two ways to specify a synthesis algorithm being run,
# either by name, e.g. "kms" (which then should be specified in entry_points),
# or directly as a class inherited from HighLevelSynthesisPlugin (which then
# does not need to be specified in entry_points).
if isinstance(plugin_specifier, str):
if plugin_specifier not in hls_plugin_manager.method_names(node.name):
raise TranspilerError(
"Specified method: %s not found in available plugins for %s"
% (plugin_specifier, node.name)
)
plugin_method = hls_plugin_manager.method(node.name, plugin_specifier)
else:
plugin_method = plugin_specifier

# ToDo: similarly to UnitarySynthesis, we should pass additional parameters
# e.g. coupling_map to the synthesis algorithm.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
features:
- |
Added an alternative way to specify in :class:`~.HLSConfig` the list of
synthesis methods used for a given high-level-object.
As before, a synthesis method can be specified as a tuple consisting of
the name of the method and additional arguments. Additionally, a synthesis method
can be specified as a tuple consisting of an instance of :class:`.HighLevelSynthesisPlugin`
and additional arguments. Moreover, when there are no additional arguments, a synthesis
method can be specified simply by name or by an instance of :class:`.HighLevelSynthesisPlugin`.
The following example illustrates the new functionality::
from qiskit import QuantumCircuit
from qiskit.circuit.library.generalized_gates import PermutationGate
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig, HighLevelSynthesis
from qiskit.transpiler.passes.synthesis.high_level_synthesis import ACGSynthesisPermutation
qc = QuantumCircuit(6)
qc.append(PermutationGate([1, 2, 3, 0]), [1, 2, 3, 4])
# All of the ways to specify hls_config are equivalent
hls_config = HLSConfig(permutation=[("acg", {})])
hls_config = HLSConfig(permutation=["acg"])
hls_config = HLSConfig(permutation=[(ACGSynthesisPermutation(), {})])
hls_config = HLSConfig(permutation=[ACGSynthesisPermutation()])
# The hls_config can then be passed as an argument to HighLevelSynthesis
pm = PassManager(HighLevelSynthesis(hls_config=hls_config))
qc_synthesized = pm.run(qc)
79 changes: 77 additions & 2 deletions test/python/transpiler/test_high_level_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,33 @@ def run(self, high_level_object, **options):


class OpBSimpleSynthesisPlugin(HighLevelSynthesisPlugin):
"""The simple synthesis for opB"""
"""The simple synthesis for OpB"""

def run(self, high_level_object, **options):
qc = QuantumCircuit(2)
qc.cx(0, 1)
return qc


class OpBAnotherSynthesisPlugin(HighLevelSynthesisPlugin):
"""Another synthesis plugin for OpB objects.
This plugin is not registered in MockPluginManager, and is used to
illustrate the alternative construction mechanism using raw classes.
"""

def __init__(self, num_swaps=1):
self.num_swaps = num_swaps

def run(self, high_level_object, **options):
num_swaps = options.get("num_swaps", self.num_swaps)

qc = QuantumCircuit(2)
for _ in range(num_swaps):
qc.swap(0, 1)
return qc


class MockPluginManager:
"""Mocks the functionality of HighLevelSynthesisPluginManager,
without actually depending on the stevedore extension manager.
Expand Down Expand Up @@ -127,7 +146,7 @@ def method(self, op_name, method_name):
return self.plugins[plugin_name]()


class TestHighLeverSynthesis(QiskitTestCase):
class TestHighLeverSynthesisInterface(QiskitTestCase):
"""Tests for the synthesis plugin interface."""

def create_circ(self):
Expand Down Expand Up @@ -289,6 +308,62 @@ def test_multiple_methods(self):
self.assertEqual(ops["id"], 2)
self.assertIn("op_b", ops.keys())

def test_multiple_methods_short_form(self):
"""Check that when there are two synthesis methods specified,
and the first returns None, then the second method gets used.
In this example, the list of methods is specified without
explicitly listing empty argument lists.
"""

qc = self.create_circ()
mock_plugin_manager = MockPluginManager
with unittest.mock.patch(
"qiskit.transpiler.passes.synthesis.high_level_synthesis.HighLevelSynthesisPluginManager",
wraps=mock_plugin_manager,
):
hls_config = HLSConfig(op_a=["repeat", "default"])
pm = PassManager([HighLevelSynthesis(hls_config=hls_config)])
tqc = pm.run(qc)
ops = tqc.count_ops()
# The repeat method for OpA without "n" specified returns None.
self.assertNotIn("op_a", ops.keys())
self.assertEqual(ops["id"], 2)
self.assertIn("op_b", ops.keys())

def test_synthesis_using_alternate_form(self):
"""Test alternative form of specifying synthesis methods."""

qc = self.create_circ()
mock_plugin_manager = MockPluginManager
with unittest.mock.patch(
"qiskit.transpiler.passes.synthesis.high_level_synthesis.HighLevelSynthesisPluginManager",
wraps=mock_plugin_manager,
):
# synthesis using raw class, without extension manager
plugin = OpBAnotherSynthesisPlugin(num_swaps=6)
hls_config = HLSConfig(op_b=[(plugin, {})])
pm = PassManager([HighLevelSynthesis(hls_config=hls_config)])
tqc = pm.run(qc)
ops = tqc.count_ops()
self.assertEqual(ops["swap"], 6)

def test_synthesis_using_alternate_short_form(self):
"""Test alternative form of specifying synthesis methods."""

qc = self.create_circ()
mock_plugin_manager = MockPluginManager
with unittest.mock.patch(
"qiskit.transpiler.passes.synthesis.high_level_synthesis.HighLevelSynthesisPluginManager",
wraps=mock_plugin_manager,
):
# synthesis using raw class, without extension manager
plugin = OpBAnotherSynthesisPlugin(num_swaps=6)
hls_config = HLSConfig(op_b=[plugin])
pm = PassManager([HighLevelSynthesis(hls_config=hls_config)])
tqc = pm.run(qc)
ops = tqc.count_ops()
self.assertEqual(ops["swap"], 6)


if __name__ == "__main__":
unittest.main()

0 comments on commit fa4a136

Please sign in to comment.