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 40 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
7 changes: 5 additions & 2 deletions qiskit/circuit/commutation_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ def commute(
"""
# We don't support commutation of conditional gates for now due to bugs in
# CommutativeCancellation. See gh-8553.
if getattr(op1, "condition") is not None or getattr(op2, "condition") is not None:
if (
getattr(op1, "condition", None) is not None
or getattr(op2, "condition", None) is not None
):
return False

# These lines are adapted from dag_dependency and say that two gates over
Expand All @@ -103,7 +106,7 @@ def commute(
if (
getattr(op, "_directive", False)
or op.name in {"measure", "reset", "delay"}
or op.is_parameterized()
or (getattr(op, "is_parameterized", False) and op.is_parameterized())
):
return False

Expand Down
10 changes: 10 additions & 0 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.instruction_durations import InstructionDurations, InstructionDurationsType
from qiskit.transpiler.passes import ApplyLayout
from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
from qiskit.transpiler.passmanager_config import PassManagerConfig
from qiskit.transpiler.preset_passmanagers import (
level_0_pass_manager,
Expand Down Expand Up @@ -80,6 +81,7 @@ def transpile(
unitary_synthesis_method: str = "default",
unitary_synthesis_plugin_config: dict = None,
target: Target = None,
hls_config: Optional[HLSConfig] = None,
init_method: str = None,
optimization_method: str = None,
) -> Union[QuantumCircuit, List[QuantumCircuit]]:
Expand Down Expand Up @@ -260,6 +262,11 @@ 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: An optional configuration class
:class:`~qiskit.transpiler.passes.synthesis.HLSConfig` that will be passed directly
to :class:`~qiskit.transpiler.passes.synthesis.HighLevelSynthesis` transformation pass.
This configuration class allows to specify for various high-level objects the lists of
synthesis algorithms and their parameters.
alexanderivrii marked this conversation as resolved.
Show resolved Hide resolved
init_method: The plugin name to use for the ``init`` stage. By default an external
plugin is not used. You can see a list of installed plugins by
using :func:`~.list_stage_plugins` with ``"init"`` for the stage
Expand Down Expand Up @@ -334,6 +341,7 @@ def callback_func(**kwargs):
unitary_synthesis_method,
unitary_synthesis_plugin_config,
target,
hls_config,
init_method,
optimization_method,
)
Expand Down Expand Up @@ -586,6 +594,7 @@ def _parse_transpile_args(
unitary_synthesis_method,
unitary_synthesis_plugin_config,
target,
hls_config,
init_method,
optimization_method,
) -> Tuple[List[Dict], Dict]:
Expand Down Expand Up @@ -676,6 +685,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
3 changes: 2 additions & 1 deletion qiskit/converters/circuit_to_dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# that they have been altered from the originals.

"""Helper function for converting a circuit to a dag"""
import copy

from qiskit.dagcircuit.dagcircuit import DAGCircuit

Expand Down Expand Up @@ -60,7 +61,7 @@ def circuit_to_dag(circuit):

for instruction in circuit.data:
dagcircuit.apply_operation_back(
instruction.operation.copy(), instruction.qubits, instruction.clbits
copy.deepcopy(instruction.operation), instruction.qubits, instruction.clbits
)

dagcircuit.duration = circuit.duration
Expand Down
3 changes: 2 additions & 1 deletion qiskit/converters/dag_to_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# that they have been altered from the originals.

"""Helper function for converting a dag to a circuit."""
import copy

from qiskit.circuit import QuantumCircuit, CircuitInstruction

Expand Down Expand Up @@ -59,7 +60,7 @@ def dag_to_circuit(dag):
circuit.calibrations = dag.calibrations

for node in dag.topological_op_nodes():
circuit._append(CircuitInstruction(node.op.copy(), node.qargs, node.cargs))
circuit._append(CircuitInstruction(copy.deepcopy(node.op), node.qargs, node.cargs))

circuit.duration = dag.duration
circuit.unit = dag.unit
Expand Down
2 changes: 1 addition & 1 deletion qiskit/transpiler/passes/synthesis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
from .unitary_synthesis import UnitarySynthesis
from .plugin import unitary_synthesis_plugin_names
from .linear_functions_synthesis import LinearFunctionsSynthesis, LinearFunctionsToPermutations
from .high_level_synthesis import HighLevelSynthesis
from .high_level_synthesis import HighLevelSynthesis, HLSConfig
155 changes: 141 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,162 @@
# that they have been altered from the originals.


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


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
from qiskit.quantum_info import decompose_clifford
from qiskit.transpiler.synthesis import cnot_synth
from .plugin import HighLevelSynthesisPluginManager, HighLevelSynthesisPlugin


class HLSConfig:
"""The high-level-synthesis config allows to specify a list of "methods" used by
:class:`~.HighLevelSynthesis` transformation pass to synthesize different types
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.

The names of the synthesis algorithms should be declared in ``entry_points`` for
``qiskit.synthesis`` in ``setup.py``, in the form
<higher-level-object-name>.<synthesis-method-name>.

The standard higher-level-objects are recommended to have a synthesis method
called "default", which would be called automatically when synthesizing these objects,
without having to explicitly set these methods in the config.

To avoid synthesizing a given higher-level-object, one can give it an empty list of methods.
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment here on how the pass will handle cases where an Operation could be of multiple types when an HLSConfig is provided? e.g. A LinearFunction operation could be synthesized by any of the available synthesis methods for a Clifford.

My expectation is that when you specify an HLSConfig, the list of applicable synthesis methods is strictly tied to the name of the type of the Operation, but it would be good to clarify this. (I also want to leave room for the case when a user doesn't specify an HLSConfig, for the pass to automatically select applicable synthesis methods which may include those from other types.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right now the list of applicable synthesis methods is indeed strictly tried to the name of the Operation (I have tried to clarify this in the documentation following your comment).

I completely agree that we want to have a way to synthesize LinearFunctions with synthesis methods for Cliffords, but at the moment I don't see a perfect solution for this. Internally, LinearFunctions are represented very differently from Cliffords (the former is a nxn binary linear matrix, the latter is represented using stabilizer/destabilizer tableaux -- or maybe this has changed recently), so we can't apply Clifford methods to LinearFunctions without first converting LinearFunctions into Cliffords. Do we want to have a transpiler pass for that? Or do we want to synthesize linear functions into CX-gates and then re-collect these as Clifford gates (with possibly additional adjacent Clifford gates)? And what should eventually be added to the optimization loop in terms of resynthesizing linear and clifford subcircuits?

Copy link
Member

Choose a reason for hiding this comment

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

For now, it's sufficient to clarify the behavior of the current pass.

Moving forward, we'll likely need some investigation to weigh the relative cost of converting from the binary linear matrix we use for LinearFunctions to a stabilzer tableau and the likelihood of finding an improved synthesis by using one of the available Clifford methods over what is known for LinearFunctions. I could imagine the implementations being different depending on the answers there (and likewise for anywhere there is overlap of available synthesis methods, e.g. LinearFunctions -> Clifford, Clifford -> Unitary, etc.)


For an explicit example of creating and using such config files, refer to the
documentation for :class:`~.HighLevelSynthesis`.
"""

def __init__(self, use_default_on_unspecified=True, **kwargs):
"""Creates a high-level-synthesis config.

Args:
use_default_on_unspecified (bool): if True, every higher-level-object without an
explicitly specified list of methods will be synthesized using the "default"
algorithm if it exists.
kwargs: a dictionary mapping higher-level-objects to lists of synthesis methods.
"""
self.use_default_on_unspecified = use_default_on_unspecified
self.methods = dict()
kdk marked this conversation as resolved.
Show resolved Hide resolved

for key, value in kwargs.items():
self.set_methods(key, value)

def set_methods(self, hls_name, hls_methods):
"""Sets the list of synthesis methods for a given higher-level-object. This overwrites
the lists of methods if also set previously."""
self.methods[hls_name] = hls_methods


# ToDo: Do we have a way to specify optimization criteria (e.g., 2q gate count vs. depth)?


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 object's name and the high-level-synthesis config of type
:class:`~.HLSConfig` (if provided).

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
parameters and ``other`` with two optional integer parameters ``option_1`` and ``option_2``,
that ``op_b``-objects have a single synthesis method ``default``, and ``qc`` is a quantum
circuit containing ``op_a`` and ``op_b`` objects. The following code snippet::

hls_config = HLSConfig(op_b=[("other", {"option_1": 7, "option_2": 4})])
pm = PassManager([HighLevelSynthesis(hls_config=hls_config)])
transpiled_qc = pm.run(qc)

shows how to run the alternative synthesis method ``other`` for ``op_b``-objects, while using the
``default`` methods for all other high-level objects, including ``op_a``-objects.
"""

# 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).

Raises:
TranspilerError: when the specified synthesis method is not available.
"""

for node in dag.named_nodes("clifford"):
decomposition = circuit_to_dag(decompose_clifford(node.op))
dag.substitute_node_with_dag(node, decomposition)
hls_plugin_manager = HighLevelSynthesisPluginManager()

for node in dag.op_nodes():
kdk marked this conversation as resolved.
Show resolved Hide resolved

if node.name in self.hls_config.methods.keys():
# the operation's name appears in the user-provided config,
# we use the list of methods provided by the user
methods = self.hls_config.methods[node.name]
elif (
self.hls_config.use_default_on_unspecified
and "default" in hls_plugin_manager.method_names(node.name)
):
# 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", {})]
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)

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

# The synthesis methods that are not suited for the given higher-level-object
# will return None, in which case the next method in the list will be used.
if decomposition is not None:
dag.substitute_node_with_dag(node, circuit_to_dag(decomposition))
break

return dag


class DefaultSynthesisClifford(HighLevelSynthesisPlugin):
"""The default clifford synthesis plugin."""

def run(self, high_level_object, **options):
"""Run synthesis for the given Clifford."""
decomposition = decompose_clifford(high_level_object)
return decomposition


class DefaultSynthesisLinearFunction(HighLevelSynthesisPlugin):
"""The default linear function synthesis plugin."""

def run(self, high_level_object, **options):
"""Run synthesis for the given LinearFunction."""
decomposition = cnot_synth(high_level_object.linear)
return decomposition
Loading