Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
ad04aaa
Add unitary synthesis plugin interface
mtreinish Mar 30, 2021
1fb017b
Also add qubits in payload with coupling map
mtreinish Apr 13, 2021
1da2d87
Fix lint
mtreinish Apr 13, 2021
0532a1e
Fix logic
mtreinish Apr 13, 2021
19bb0c8
Add documentation about the plugin interface
mtreinish Apr 14, 2021
fece875
Export get_unitary_synthesis_plugin_names from qiskit.transpiler.passes
mtreinish Apr 14, 2021
d9eda3b
Merge branch 'master' into synthesis-plugins
mtreinish Apr 14, 2021
b7678cf
Fix lint
mtreinish Apr 15, 2021
5622895
Tweak doc organization slightly
mtreinish Apr 15, 2021
566ebcf
Merge branch 'synthesis-plugins' of github.com:mtreinish/qiskit-core …
mtreinish Apr 15, 2021
0bb935e
Merge branch 'master' into synthesis-plugins
mtreinish Apr 15, 2021
faa260a
Use UnitarySynthesis for unroll3q step
mtreinish Apr 19, 2021
7808223
Merge remote-tracking branch 'origin/main' into synthesis-plugins
mtreinish May 28, 2021
dd9211f
Run black
mtreinish May 28, 2021
20c10b7
Tweak doc organization slightly
mtreinish May 28, 2021
bd5c2ae
Merge branch 'main' into synthesis-plugins
mtreinish May 28, 2021
8f44c92
Merge branch 'main' into synthesis-plugins
mtreinish Jun 14, 2021
bb10dcc
Merge remote-tracking branch 'origin/main' into synthesis-plugins
mtreinish Jul 28, 2021
04b72bc
Merge remote-tracking branch 'origin/main' into synthesis-plugins
mtreinish Aug 3, 2021
291457a
Fix lint
mtreinish Aug 3, 2021
9557aaf
Fix pylint errors
mtreinish Aug 3, 2021
b7a9873
Merge branch 'main' into synthesis-plugins
mtreinish Aug 5, 2021
bf8a6e5
Merge branch 'main' into synthesis-plugins
mtreinish Aug 9, 2021
4c52bfc
Merge branch 'main' into synthesis-plugins
mtreinish Aug 16, 2021
35709d5
Apply suggestions from code review
mtreinish Aug 26, 2021
449b6ac
Merge remote-tracking branch 'origin/main' into synthesis-plugins
mtreinish Aug 26, 2021
78f84b4
Update plugin interface docs
mtreinish Aug 26, 2021
1dd25b4
Review comment updates on UnitarySynthesis pass
mtreinish Aug 26, 2021
935ac01
Fix typos
jakelishman Aug 26, 2021
44a186c
Merge remote-tracking branch 'origin/main' into synthesis-plugins
mtreinish Aug 26, 2021
c161b9d
Include release notes
mtreinish Aug 26, 2021
373f43d
Fix docstring typo
mtreinish Aug 26, 2021
def0a66
Update qiskit/transpiler/passes/synthesis/unitary_synthesis.py
mtreinish Aug 30, 2021
46db9ec
Merge branch 'main' into synthesis-plugins
mtreinish Aug 30, 2021
9c64267
Merge branch 'main' into synthesis-plugins
mtreinish Sep 4, 2021
9a0b941
Merge remote-tracking branch 'ibm/main' into synthesis-plugins
jakelishman Sep 20, 2021
f83c841
Ignore deprecation warnings from importlib metadata
mtreinish Sep 20, 2021
2e1726c
Pin importlib-metadata
mtreinish Sep 20, 2021
6b9d550
Add length units to docstring
mtreinish Sep 21, 2021
db38b8d
Add min and max qubit abstract properties
mtreinish Sep 21, 2021
5e35056
Merge branch 'main' into synthesis-plugins
mtreinish Sep 21, 2021
3d9b783
Add option to automatically find basis for a run() call
mtreinish Sep 21, 2021
c5f6f7f
Merge branch 'synthesis-plugins' of github.com:mtreinish/qiskit-core …
mtreinish Sep 21, 2021
e65c01a
Fix typo
mtreinish Sep 23, 2021
ac9f85d
Merge remote-tracking branch 'origin/main' into synthesis-plugins
mtreinish Sep 23, 2021
6f1eb05
Update gate errors docstring
mtreinish Sep 28, 2021
303eb29
Merge remote-tracking branch 'origin/main' into synthesis-plugins
mtreinish Sep 28, 2021
cc9fe27
Remove approximation degree from plugin interface
mtreinish Sep 28, 2021
d57351e
Fix docstring typos
mtreinish Sep 28, 2021
43c46f5
Merge branch 'main' into synthesis-plugins
mtreinish Sep 28, 2021
0e06b7d
Apply suggestions from code review
mtreinish Sep 29, 2021
d9f6884
Update qiskit/transpiler/passes/synthesis/unitary_synthesis.py
mtreinish Sep 29, 2021
0e5c637
Merge branch 'main' into synthesis-plugins
mtreinish Sep 29, 2021
5d8539a
Update docstrings and naming and default to 'default' plugin in kwarg
mtreinish Sep 29, 2021
aa2af0f
Make coupling_map run() kwarg a tuple
mtreinish Sep 29, 2021
ba6a4a0
Add comment on why we don't use a shared global instance
mtreinish Sep 29, 2021
66bb763
Fix straggler basis->bases typos
jakelishman Sep 30, 2021
7240065
Apply suggestions from code review
mtreinish Sep 30, 2021
9ca591a
Document supported_bases handling of no matching basis
mtreinish Sep 30, 2021
9c12540
Merge remote-tracking branch 'ibm/main' into pr/6124
jakelishman Sep 30, 2021
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
37 changes: 25 additions & 12 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]],
pass_manager: Optional[PassManager] = None,
callback: Optional[Callable[[BasePass, DAGCircuit, float,
PropertySet, int], Any]] = None,
output_name: Optional[Union[str, List[str]]] = None) -> Union[QuantumCircuit,
List[QuantumCircuit]]:
output_name: Optional[Union[str, List[str]]] = None,
unitary_synthesis_method: Optional[str] = None) -> Union[QuantumCircuit,
List[QuantumCircuit]]:
"""Transpile one or more circuits, according to some desired transpilation targets.

All arguments may be given as either a singleton or list. In case of a list,
Expand Down Expand Up @@ -215,6 +216,7 @@ def callback_func(**kwargs):
routing_method=routing_method,
translation_method=translation_method,
approximation_degree=approximation_degree,
unitary_synthesis_method=unitary_synthesis_method,
backend=backend)

warnings.warn("The parameter pass_manager in transpile is being deprecated. "
Expand All @@ -238,7 +240,8 @@ def callback_func(**kwargs):
layout_method, routing_method, translation_method,
scheduling_method, instruction_durations, dt,
approximation_degree, seed_transpiler,
optimization_level, callback, output_name)
optimization_level, callback, output_name,
unitary_synthesis_method)

_check_circuits_coupling_map(circuits, transpile_args, backend)

Expand Down Expand Up @@ -395,7 +398,7 @@ def _parse_transpile_args(circuits, backend,
initial_layout, layout_method, routing_method, translation_method,
scheduling_method, instruction_durations, dt,
approximation_degree, seed_transpiler, optimization_level,
callback, output_name) -> List[Dict]:
callback, output_name, unitary_synthesis_method) -> List[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
what types of inputs are allowed.
Expand Down Expand Up @@ -427,6 +430,8 @@ def _parse_transpile_args(circuits, backend,
routing_method = _parse_routing_method(routing_method, num_circuits)
translation_method = _parse_translation_method(translation_method, num_circuits)
approximation_degree = _parse_approximation_degree(approximation_degree, num_circuits)
unitary_synthesis_method = _parse_unitary_synthesis_method(unitary_synthesis_method,
num_circuits)
seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits)
optimization_level = _parse_optimization_level(optimization_level, num_circuits)
output_name = _parse_output_name(output_name, circuits)
Expand All @@ -440,8 +445,9 @@ def _parse_transpile_args(circuits, backend,
list_transpile_args = []
for args in zip(basis_gates, coupling_map, backend_properties, initial_layout,
layout_method, routing_method, translation_method, scheduling_method,
durations, approximation_degree, seed_transpiler, optimization_level,
output_name, callback, backend_num_qubits, faulty_qubits_map):
durations, approximation_degree, seed_transpiler, unitary_synthesis_method,
optimization_level, output_name, callback, backend_num_qubits,
faulty_qubits_map):
transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0],
coupling_map=args[1],
backend_properties=args[2],
Expand All @@ -452,12 +458,13 @@ def _parse_transpile_args(circuits, backend,
scheduling_method=args[7],
instruction_durations=args[8],
approximation_degree=args[9],
seed_transpiler=args[10]),
'optimization_level': args[11],
'output_name': args[12],
'callback': args[13],
'backend_num_qubits': args[14],
'faulty_qubits_map': args[15]}
seed_transpiler=args[10],
unitary_synthesis_method=args[11]),
'optimization_level': args[12],
'output_name': args[13],
'callback': args[14],
'backend_num_qubits': args[15],
'faulty_qubits_map': args[16]}
list_transpile_args.append(transpile_args)

return list_transpile_args
Expand Down Expand Up @@ -689,6 +696,12 @@ def _parse_approximation_degree(approximation_degree, num_circuits):
return approximation_degree


def _parse_unitary_synthesis_method(unitary_synthesis_method, num_circuits):
if not isinstance(unitary_synthesis_method, list):
unitary_synthesis_method = [unitary_synthesis_method] * num_circuits
return unitary_synthesis_method


def _parse_seed_transpiler(seed_transpiler, num_circuits):
if not isinstance(seed_transpiler, list):
seed_transpiler = [seed_transpiler] * num_circuits
Expand Down
49 changes: 49 additions & 0 deletions qiskit/transpiler/passes/synthesis/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

import abc

import stevedore

class UnitarySynthesisPlugin(abc.ABC):
"""Abstract plugin Synthesis plugin class
Comment thread
mtreinish marked this conversation as resolved.
Outdated

This class abstract class is

"""

@property
@abc.abstractmethod
def supports_basis_gates(self):
"""Return whether the plugin supports taking basis_gates"""
pass

@property
@abc.abstractmethod
def supports_coupling_map(self):
"""Return whether the plugin supports taking coupling_map"""
pass

@property
@abc.abstractmethod
def supports_approximation_degree(self):
Comment thread
mtreinish marked this conversation as resolved.
Outdated
"""Return whether the plugin supports taking approximation_degree"""
pass

@abc.abstractmethod
def run(self, unitary, **options):
"""Run synthesis for the given unitary

Args:
unitary (numpy.ndarray): The unitary

Returns:
DAGCircuit: The dag circuit representation of the unitary
"""
pass


class UnitarySynthesisPluginManager:

def __init__(self):
self.ext_plugins = stevedore.ExtensionManager(
'qiskit.unitary_synthesis', invoke_on_load=True,
propagate_map_exceptions=True)
84 changes: 61 additions & 23 deletions qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
from qiskit.converters import circuit_to_dag
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.dagcircuit.dagcircuit import DAGCircuit
from qiskit.exceptions import QiskitError
from qiskit.extensions.quantum_initializer import isometry
from qiskit.quantum_info.synthesis import one_qubit_decompose
from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitBasisDecomposer
from qiskit.circuit.library.standard_gates import (iSwapGate, CXGate, CZGate,
RXXGate, ECRGate)
from qiskit.transpiler.passes.synthesis import plugin


def _choose_kak_gate(basis_gates):
Expand Down Expand Up @@ -60,7 +62,9 @@ class UnitarySynthesis(TransformationPass):

def __init__(self,
basis_gates: List[str],
approximation_degree: float = 1):
approximation_degree: float = 1,
coupling_map = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would it be better to pass a PassManagerConfig to the plugin to avoid extending parameters further and further (for example, in case of a potential noise-aware synthesis)? Maybe approximation_degree should be part of PassManagerConfig?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The issue with using a PassManagerConfig is that it is only used by transpile and the preset pass managers? If a user wants to create a custom pass manager or directly call this pass they'll have to go an instantiate this config object to pass to the pass constructor which nothing else requires. For the preset pass manager case I think it makes things simpler but I think we have to consider the standalone case too.

The one thing I thought about doing was making the api free form and just have the abstract run() signature take all kwargs and then the plugins can use them opportunistically so that gives us a bit more flexibility in the api (as we can add parameters over time). But I decided on being explicit like this so we can make the api a bit more well defined.

method: str = None):
"""
Synthesize unitaries over some basis gates.

Expand All @@ -75,6 +79,9 @@ def __init__(self,
super().__init__()
self._basis_gates = basis_gates
self._approximation_degree = approximation_degree
self.method = method
self._coupling_map = coupling_map
self.plugins = plugin.UnitarySynthesisPluginManager()

def run(self, dag: DAGCircuit) -> DAGCircuit:
"""Run the UnitarySynthesis pass on `dag`.
Expand All @@ -85,31 +92,62 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
Returns:
Output dag with UnitaryGates synthesized to target basis.
"""
euler_basis = _choose_euler_basis(self._basis_gates)
kak_gate = _choose_kak_gate(self._basis_gates)
for node in dag.named_nodes('unitary'):
synth_dag = None
if not self.method:
method = 'default'
else:
method = self.method
if method not in self.plugins.ext_plugins:
raise QiskitError(
'Specified method: %s not found in plugin list' % method)
plugin = self.plugins.ext_plugins[method].obj
kwargs = {}
if plugin.supports_basis_gates:
kwargs['basis_gates'] = self._basis_gates
if plugin.supports_coupling_map:
kwargs['coupling_map'] = self._coupling_map
if plugin.supports_approximation_degree:
kwargs['approximation_degree'] = self._approximation_degree
unitary = node.op.to_matrix()
synth_dag = plugin.run(unitary, **kwargs)
if synth_dag:
dag.substitute_node_with_dag(node, synth_dag)
return dag


class DefaultUnitarySynthesis(plugin.UnitarySynthesisPlugin):

def supports_basis_gates(self):
return True

def supports_coupling_map(self):
return False

def supports_approximation_degree(self):
return True

def run(self, unitary, **options):
basis_gates = options['basis_gates']
approximation_degree = options['approximation_degree']
euler_basis = _choose_euler_basis(basis_gates)
kak_gate = _choose_kak_gate(basis_gates)

decomposer1q, decomposer2q = None, None
if euler_basis is not None:
decomposer1q = one_qubit_decompose.OneQubitEulerDecomposer(euler_basis)
if kak_gate is not None:
decomposer2q = TwoQubitBasisDecomposer(kak_gate, euler_basis=euler_basis)

for node in dag.named_nodes('unitary'):

synth_dag = None
if len(node.qargs) == 1:
if decomposer1q is None:
continue
synth_dag = circuit_to_dag(decomposer1q._decompose(node.op.to_matrix()))
elif len(node.qargs) == 2:
if decomposer2q is None:
continue
synth_dag = circuit_to_dag(decomposer2q(node.op.to_matrix(),
basis_fidelity=self._approximation_degree))
else:
synth_dag = circuit_to_dag(
isometry.Isometry(node.op.to_matrix(), 0, 0).definition)

dag.substitute_node_with_dag(node, synth_dag)

return dag
if unitary.shape == (2, 2):
if decomposer1q is None:
return None
synth_dag = circuit_to_dag(decomposer1q._decompose(unitary))
elif unitary.shape == (4, 4):
if decomposer2q is None:
return None
synth_dag = circuit_to_dag(decomposer2q(unitary,
basis_fidelity=approximation_degree))
else:
synth_dag = circuit_to_dag(
isometry.Isometry(unitary.op.to_matrix(), 0, 0).definition)
return synth_dag
7 changes: 6 additions & 1 deletion qiskit/transpiler/passmanager_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def __init__(self,
instruction_durations=None,
backend_properties=None,
approximation_degree=None,
seed_transpiler=None):
seed_transpiler=None,
unitary_synthesis_method=None):
"""Initialize a PassManagerConfig object

Args:
Expand All @@ -53,6 +54,9 @@ def __init__(self,
(1.0=no approximation, 0.0=maximal approximation)
seed_transpiler (int): Sets random seed for the stochastic parts of
the transpiler.
unitary_synthesis_method (str): The string method to use for the
:class:`~qiskit.transpiler.passes.UnitarySynthesis` pass. Will
search installed plugins for a valid method.
"""
self.initial_layout = initial_layout
self.basis_gates = basis_gates
Expand All @@ -65,3 +69,4 @@ def __init__(self,
self.backend_properties = backend_properties
self.approximation_degree = approximation_degree
self.seed_transpiler = seed_transpiler
self.unitary_synthesis_method = unitary_synthesis_method
4 changes: 3 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
seed_transpiler = pass_manager_config.seed_transpiler
backend_properties = pass_manager_config.backend_properties
approximation_degree = pass_manager_config.approximation_degree
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method

# 1. Choose an initial layout if not set by user (default: trivial layout)
_given_layout = SetLayout(initial_layout)
Expand Down Expand Up @@ -140,7 +141,8 @@ def _swap_condition(property_set):
Unroll3qOrMore(),
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree,
method=unitary_synthesis_method),
]
else:
raise TranspilerError("Invalid translation method %s." % translation_method)
Expand Down
4 changes: 3 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level1.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
seed_transpiler = pass_manager_config.seed_transpiler
backend_properties = pass_manager_config.backend_properties
approximation_degree = pass_manager_config.approximation_degree
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method

# 1. Use trivial layout if no layout given
_given_layout = SetLayout(initial_layout)
Expand Down Expand Up @@ -157,7 +158,8 @@ def _swap_condition(property_set):
Unroll3qOrMore(),
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree,
method=unitary_synthesis_method),
]
else:
raise TranspilerError("Invalid translation method %s." % translation_method)
Expand Down
4 changes: 3 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level2.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
seed_transpiler = pass_manager_config.seed_transpiler
backend_properties = pass_manager_config.backend_properties
approximation_degree = pass_manager_config.approximation_degree
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method

# 1. Search for a perfect layout, or choose a dense layout, if no layout given
_given_layout = SetLayout(initial_layout)
Expand Down Expand Up @@ -153,7 +154,8 @@ def _swap_condition(property_set):
Unroll3qOrMore(),
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree,
method=unitary_synthesis_method),
]
else:
raise TranspilerError("Invalid translation method %s." % translation_method)
Expand Down
7 changes: 5 additions & 2 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
seed_transpiler = pass_manager_config.seed_transpiler
backend_properties = pass_manager_config.backend_properties
approximation_degree = pass_manager_config.approximation_degree
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method

# 1. Unroll to 1q or 2q gates
_unroll3q = Unroll3qOrMore()
Expand Down Expand Up @@ -156,7 +157,8 @@ def _swap_condition(property_set):
Unroll3qOrMore(),
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree,
method=unitary_synthesis_method),
]
else:
raise TranspilerError("Invalid translation method %s." % translation_method)
Expand All @@ -183,7 +185,8 @@ def _opt_control(property_set):
_opt = [
Collect2qBlocks(),
ConsolidateBlocks(basis_gates=basis_gates),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree),
UnitarySynthesis(basis_gates, approximation_degree=approximation_degree,
method=unitary_synthesis_method),
Optimize1qGatesDecomposition(basis_gates),
CommutativeCancellation(),
]
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ dill>=0.3
fastjsonschema>=2.10
python-constraint>=1.4
python-dateutil>=2.8.0
stevedore>=3.0.0
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,10 @@
"Source Code": "https://github.com/Qiskit/qiskit-terra",
},
ext_modules=cythonize(EXT_MODULES),
zip_safe=False
zip_safe=False,
entry_points={
'qiskit.unitary_synthesis': [
'default = qiskit.transpiler.passes.synthesis.unitary_synthesis:DefaultUnitarySynthesis',
]
},
)