Skip to content
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

Implementing pluggable HighLevelSynthesis #8548

Merged
merged 42 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e62c79e
Initial implementation of pluggable HighLevelSynthesis
alexanderivrii Aug 15, 2022
530ac33
typo
alexanderivrii Aug 16, 2022
975d0de
black
alexanderivrii Aug 16, 2022
f2e1380
Merge branch 'main' into pluggable_high_level_synthesis
alexanderivrii Sep 4, 2022
c139421
fix merge error
alexanderivrii Sep 4, 2022
dea4544
Reorganizing entry points for high-level-synthesis plugins based on t…
alexanderivrii Sep 4, 2022
2d55fa4
fixing some typos
alexanderivrii Sep 5, 2022
51acb33
HLSConfig now accepts a list of methods for each operation; also allo…
alexanderivrii Sep 9, 2022
b0f8c87
running black
alexanderivrii Sep 9, 2022
a63d112
removing hls_config from __str__ in PassManagerConfig
alexanderivrii Sep 9, 2022
15e22b7
Adding abc interface for high-level-synthesis plugins
alexanderivrii Sep 9, 2022
e52eb16
improving docs and comments
alexanderivrii Sep 10, 2022
206d87e
Merge branch 'main' into pluggable_high_level_synthesis
alexanderivrii Sep 10, 2022
ba0edb5
fix
alexanderivrii Sep 11, 2022
0fd32f2
adding tests; removing print statements
alexanderivrii Sep 11, 2022
6abbf07
removing external test file
alexanderivrii Sep 11, 2022
9b28157
remove redundant print
alexanderivrii Sep 11, 2022
233d798
Passing HLSConfig in all transpiler optimization levels
alexanderivrii Sep 11, 2022
3d06999
Fix for is_parameterized as Operations don't have to support this
alexanderivrii Sep 11, 2022
b625cc5
Merge branch 'main' into pluggable_high_level_synthesis
alexanderivrii Sep 12, 2022
4186bb2
Replacing node.op.copy by copy.deepcopy(node.op) to avoid Operations …
alexanderivrii Sep 13, 2022
4abaff3
release notes
alexanderivrii Sep 13, 2022
8a38922
Merge branch 'main' into pluggable_high_level_synthesis
alexanderivrii Sep 13, 2022
054125b
Merge branch 'main' into pluggable_high_level_synthesis
alexanderivrii Sep 21, 2022
738ed62
adding a link to HLSConfig class
alexanderivrii Sep 22, 2022
def3b50
Temporarily removing high-level-synthesis from optimization loop
alexanderivrii Sep 22, 2022
4f6c95c
Adding example from release notes to HighLevelSynthesis documentation
alexanderivrii Sep 22, 2022
0ffd981
fixing random typo
alexanderivrii Sep 22, 2022
5fa8387
adding references to documentation:
alexanderivrii Sep 22, 2022
bb680ca
Further trying to improve documentation following the review comments
alexanderivrii Sep 22, 2022
5b382e0
removing usused import
alexanderivrii Sep 22, 2022
9c06042
minor docs changes
alexanderivrii Sep 22, 2022
af9d498
Merge branch 'main' into pluggable_high_level_synthesis
alexanderivrii Sep 27, 2022
517007b
Merge branch 'main' into pluggable_high_level_synthesis
alexanderivrii Sep 29, 2022
bda0ac4
Docs consolidation following review comments
alexanderivrii Sep 29, 2022
ed35b50
added test
alexanderivrii Sep 29, 2022
ba96369
fixing title underline in docs
alexanderivrii Sep 29, 2022
f65cfac
Merge branch 'main' into pluggable_high_level_synthesis
alexanderivrii Sep 29, 2022
5cee799
minor output tweaks
alexanderivrii Sep 29, 2022
cba889d
Merge branch 'pluggable_high_level_synthesis' of github.com:alexander…
alexanderivrii Sep 29, 2022
9e22d1b
Update setup.py
alexanderivrii Sep 30, 2022
0a41fba
Merge branch 'main' into pluggable_high_level_synthesis
mergify[bot] Sep 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def transpile(
unitary_synthesis_method: str = "default",
unitary_synthesis_plugin_config: dict = None,
target: Target = None,
hls_config=None,
) -> Union[QuantumCircuit, List[QuantumCircuit]]:
"""Transpile one or more circuits, according to some desired transpilation targets.

Expand Down Expand Up @@ -246,6 +247,7 @@ def callback_func(**kwargs):
the ``backend`` argument, but if you have manually constructed a
:class:`~qiskit.transpiler.Target` object you can specify it manually here.
This will override the target from ``backend``.
hls_config: high-level-synthesis config
Returns:
The transpiled circuit(s).

Expand Down Expand Up @@ -310,6 +312,7 @@ def callback_func(**kwargs):
unitary_synthesis_method,
unitary_synthesis_plugin_config,
target,
hls_config,
)
# Get transpile_args to configure the circuit transpilation job(s)
if coupling_map in unique_transpile_args:
Expand Down Expand Up @@ -560,6 +563,7 @@ def _parse_transpile_args(
unitary_synthesis_method,
unitary_synthesis_plugin_config,
target,
hls_config,
) -> Tuple[List[Dict], Dict]:
"""Resolve the various types of args allowed to the transpile() function through
duck typing, overriding args, etc. Refer to the transpile() docstring for details on
Expand Down Expand Up @@ -646,6 +650,7 @@ def _parse_transpile_args(
"unitary_synthesis_method": unitary_synthesis_method,
"unitary_synthesis_plugin_config": unitary_synthesis_plugin_config,
"target": target,
"hls_config": hls_config,
}.items():
if isinstance(value, list):
unique_dict[key] = value
Expand Down
112 changes: 98 additions & 14 deletions qiskit/transpiler/passes/synthesis/high_level_synthesis.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
# (C) Copyright IBM 2022.
#
# 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
Expand All @@ -11,35 +11,119 @@
# that they have been altered from the originals.


"""Synthesize high-level objects."""
"""Synthesize higher-level objects."""

import stevedore

from qiskit.converters import circuit_to_dag
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.dagcircuit.dagcircuit import DAGCircuit
from qiskit.quantum_info.synthesis.clifford_decompose import decompose_clifford
from qiskit.transpiler.exceptions import TranspilerError


# ToDo: possibly choose a better name for this data structure.
class HLSConfig:
"""
To each higher-level-object (e.g., "clifford", "linear_function", etc.) we associate a
pair consisting of the name of a synthesis algorithm and additional arguments it requires.
There are two special values for the name:
"default" is the default synthesis algorithm (chosen by Qiskit developers)
"none" is to skip synthesizing this object
All other names should be declared in entry points in setup.
"""

def __init__(self, default_or_none: True):
"""
Creates a high-level-synthesis config.
Args:
default_or_none: specifies the default method for every higher-level object,
either "default" (use the "default" synthesize method if available),
or "none" (skip synthesizing)
"""
self.default_or_none = default_or_none
self.methods = dict()
kdk marked this conversation as resolved.
Show resolved Hide resolved

def set_method(self, hls_name: str, method_name: str, method_args=None):
"""Sets the synthesis method for a given higher-level-object."""
if method_args is None:
method_args = dict()
self.methods[hls_name] = (method_name, method_args)

def print(self):
"""This is temporary for debugging."""
print("HLS CONFIG:")
print(f"default_or_none = {self.default_or_none}")
for hls_name in self.methods:
print(f" name = {hls_name}, method = {self.methods[hls_name]}")


# ToDo [1]: Make sure that plugin_method is an instance of a certain API
# (for instance, every plugin for Unitary Synthesis derives from UnitarySynthesisPlugin).
# It probably makes sense to create a more general HigherLevelSynthesisPlugin
# (which is exactly what UnitarySynthesisPlugin is right now), and to rename/inherit
# UnitarySynthesisPlugin from that.
# ToDo [2]: Do we have a way to specify optimization criteria (e.g., 2q gate count vs. depth)?
# ToDo [3]: Do we also need a PluginManager, similar to UnitarySynthesisPluginManager?
class HighLevelSynthesis(TransformationPass):
"""Synthesize high-level objects by choosing the appropriate synthesis method based on
the object's name.
"""Synthesize higher-level objects by choosing the appropriate synthesis method
based on the node's name and the HLS-config (if provided).
"""

# TODO: Currently, this class only contains the minimal functionality required to transpile
# Cliffords. In the near future, this class will be expanded to cover other higher-level
# objects (as these become available). Additionally, the plan is to make HighLevelSynthesis
# "pluggable", so that the users would be able to "plug in" their own synthesis methods
# for higher-level objects (which would be called during transpilation).
def __init__(self, hls_config=None):
super().__init__()

if hls_config is not None:
self.hls_config = hls_config
else:
# When the config file is not provided, we will use the "default" method
# to synthesize Operations (when available).
self.hls_config = HLSConfig(True)

def run(self, dag: DAGCircuit) -> DAGCircuit:
"""Run the HighLevelSynthesis pass on `dag`.
Args:
dag: input dag.
Returns:
Output dag with high level objects synthesized.
Output dag with certain Operations synthesized (as specified by
the hls_config).
"""

for node in dag.named_nodes("clifford"):
decomposition = circuit_to_dag(decompose_clifford(node.op))
dag.substitute_node_with_dag(node, decomposition)
for node in dag.op_nodes():
kdk marked this conversation as resolved.
Show resolved Hide resolved
# Get available plugins corresponding to the name of the current operation (node)
# ToDo [4]: check how (un)efficient it is to call stevedore for every gate in the circuit
available_plugins = stevedore.ExtensionManager(
"qiskit.synthesis." + node.name, invoke_on_load=True, propagate_map_exceptions=True
)

if node.name in self.hls_config.methods.keys():
# the operation's name appears in the user-provided config
# note that the plugin might be "default" or "none"
plugin_name, plugin_args = self.hls_config.methods[node.name]
elif self.hls_config.default_or_none and "default" in available_plugins.names():
# the operation's name does not appear in the user-specified config,
# but we should use the "default" method when possible
plugin_name, plugin_args = "default", {}
else:
# the operation's name does not appear in the user-specified config,
# and either the "default" method is not available or we should skip these
plugin_name, plugin_args = "none", {}

if plugin_name == "none":
# don't synthesize this operation
continue

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

# print(f" Using method {plugin_name} for {node.name}")
plugin_method = available_plugins[plugin_name].obj

# ToDo: [5] similarly to UnitarySynthesis, we should pass additional parameters
# e.g. coupling_map to the synthesis algorithm.
decomposition = plugin_method.run(node.op, **plugin_args)
dag.substitute_node_with_dag(node, circuit_to_dag(decomposition))

return dag
35 changes: 35 additions & 0 deletions qiskit/transpiler/passes/synthesis/high_level_synthesis_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# 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.

"""
Default plugins for synthesizing high-level-objects in Qiskit.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this file be part of qiskit/transpiler/passes/synthesis/plugin.py?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also makes sense, especially if we want to generalize the documentation to cover both cases. Though let me redirect this question to @mtreinish, to see if he agrees with this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this idea, we should integrate the docs into one module docstring. I think the details are sufficiently different we'll want 2 sections for each interface, mostly because implementing the plugins are different. But the pieces about using entry points to expose plugins are sufficiently the same we can abstract that if you think it's best. FWIW, this is why the section hierarchy has the top header as Writing Plugins and the subsection is Unitary Synthesis Plugins. My thinking at the time was we could easily add any other synthesis interfaces to that list with a new subsection. So in this case add High Level Synthesis as a subsection there

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (see the previous comment for more details).

"""
from qiskit.quantum_info import decompose_clifford
from qiskit.transpiler.synthesis import cnot_synth


class DefaultSynthesisClifford:
def run(self, clifford, **options):
"""Run synthesis for the given Clifford."""

print(f" -> Running DefaultSynthesisClifford")
decomposition = decompose_clifford(clifford)
return decomposition


class DefaultSynthesisLinearFunction:
def run(self, linear_function, **options):
"""Run synthesis for the given LinearFunction."""

print(f" -> Running DefaultSynthesisLinearFunction")
decomposition = cnot_synth(linear_function.linear)
return decomposition
4 changes: 4 additions & 0 deletions qiskit/transpiler/passmanager_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def __init__(
unitary_synthesis_method="default",
unitary_synthesis_plugin_config=None,
target=None,
hls_config=None,
):
"""Initialize a PassManagerConfig object

Expand Down Expand Up @@ -70,6 +71,7 @@ def __init__(
:class:`~qiskit.transpiler.passes.UnitarySynthesis` pass. Will
search installed plugins for a valid method.
target (Target): The backend target
hls_config (str): hls config
"""
self.initial_layout = initial_layout
self.basis_gates = basis_gates
Expand All @@ -87,6 +89,7 @@ def __init__(
self.unitary_synthesis_method = unitary_synthesis_method
self.unitary_synthesis_plugin_config = unitary_synthesis_plugin_config
self.target = target
self.hls_config = hls_config

@classmethod
def from_backend(cls, backend, **pass_manager_options):
Expand Down Expand Up @@ -166,4 +169,5 @@ def __str__(self):
f"\tunitary_synthesis_method: {self.unitary_synthesis_method}\n"
f"\tunitary_synthesis_plugin_config: {self.unitary_synthesis_plugin_config}\n"
f"\ttarget: {str(self.target).replace(newline, newline_tab)}\n"
f"\thls_config: {self.hls_config}\n"
)
3 changes: 3 additions & 0 deletions qiskit/transpiler/preset_passmanagers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def generate_preset_pass_manager(
seed_transpiler=None,
unitary_synthesis_method="default",
unitary_synthesis_plugin_config=None,
hls_config=None,
):
"""Generate a preset :class:`~.PassManager`

Expand Down Expand Up @@ -134,6 +135,7 @@ def generate_preset_pass_manager(
the ``unitary_synthesis`` argument. As this is custom for each
unitary synthesis plugin refer to the plugin documentation for how
to use this option.
hls_config: hls_config

Returns:
StagedPassManager: The preset pass manager for the given options
Expand Down Expand Up @@ -172,6 +174,7 @@ def generate_preset_pass_manager(
unitary_synthesis_method=unitary_synthesis_method,
unitary_synthesis_plugin_config=unitary_synthesis_plugin_config,
initial_layout=initial_layout,
hls_config=hls_config,
)

if backend is not None:
Expand Down
12 changes: 8 additions & 4 deletions qiskit/transpiler/preset_passmanagers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def generate_unroll_3q(
approximation_degree=None,
unitary_synthesis_method="default",
unitary_synthesis_plugin_config=None,
hls_config=None,
):
"""Generate an unroll >3q :class:`~qiskit.transpiler.PassManager`

Expand All @@ -68,6 +69,7 @@ def generate_unroll_3q(
unitary_synthesis_plugin_config (dict): The optional dictionary plugin
configuration, this is plugin specific refer to the specified plugin's
documenation for how to use.
hls_config: hls config

Returns:
PassManager: The unroll 3q or more pass manager
Expand All @@ -83,7 +85,7 @@ def generate_unroll_3q(
target=target,
)
)
unroll_3q.append(HighLevelSynthesis())
unroll_3q.append(HighLevelSynthesis(hls_config=hls_config))
unroll_3q.append(Unroll3qOrMore(target=target, basis_gates=basis_gates))
return unroll_3q

Expand Down Expand Up @@ -234,6 +236,7 @@ def generate_translation_passmanager(
backend_props=None,
unitary_synthesis_method="default",
unitary_synthesis_plugin_config=None,
hls_config=None,
):
"""Generate a basis translation :class:`~qiskit.transpiler.PassManager`

Expand All @@ -255,6 +258,7 @@ def generate_translation_passmanager(
backend_props (BackendProperties): Properties of a backend to
synthesize for (e.g. gate fidelities).
unitary_synthesis_method (str): The unitary synthesis method to use
hls_config: hls config

Returns:
PassManager: The basis translation pass manager
Expand All @@ -277,7 +281,7 @@ def generate_translation_passmanager(
method=unitary_synthesis_method,
target=target,
),
HighLevelSynthesis(),
HighLevelSynthesis(hls_config=hls_config),
UnrollCustomDefinitions(sel, basis_gates),
BasisTranslator(sel, basis_gates, target),
]
Expand All @@ -295,7 +299,7 @@ def generate_translation_passmanager(
min_qubits=3,
target=target,
),
HighLevelSynthesis(),
HighLevelSynthesis(hls_config=hls_config),
Unroll3qOrMore(target=target, basis_gates=basis_gates),
Collect2qBlocks(),
Collect1qRuns(),
Expand All @@ -309,7 +313,7 @@ def generate_translation_passmanager(
method=unitary_synthesis_method,
target=target,
),
HighLevelSynthesis(),
HighLevelSynthesis(hls_config=hls_config),
]
else:
raise TranspilerError("Invalid translation method %s." % method)
Expand Down
3 changes: 3 additions & 0 deletions qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config
timing_constraints = pass_manager_config.timing_constraints or TimingConstraints()
target = pass_manager_config.target
hls_config = pass_manager_config.hls_config

# Use trivial layout if no layout given
_given_layout = SetLayout(initial_layout)
Expand Down Expand Up @@ -201,6 +202,7 @@ def _opt_control(property_set):
approximation_degree,
unitary_synthesis_method,
unitary_synthesis_plugin_config,
hls_config,
)
layout = PassManager()
layout.append(_given_layout)
Expand Down Expand Up @@ -233,6 +235,7 @@ def _opt_control(property_set):
backend_properties,
unitary_synthesis_method,
unitary_synthesis_plugin_config,
hls_config,
)
pre_routing = None
if toqm_pass:
Expand Down
10 changes: 9 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@
"qiskit.unitary_synthesis": [
"default = qiskit.transpiler.passes.synthesis.unitary_synthesis:DefaultUnitarySynthesis",
"aqc = qiskit.transpiler.synthesis.aqc.aqc_plugin:AQCSynthesisPlugin",
]
],
"qiskit.synthesis.clifford": [
"default = qiskit.transpiler.passes.synthesis.high_level_synthesis_plugins:DefaultSynthesisClifford",
],
"qiskit.synthesis.linear_function": [
"default = qiskit.transpiler.passes.synthesis.high_level_synthesis_plugins:DefaultSynthesisLinearFunction",
"depth_opt = test_stuff:LinearFunctionSynthesisPluginForDepth",
"count_opt = test_stuff:LinearFunctionSynthesisPluginForCount",
],
},
)
Loading