From ad04aaa7db4984709e933414242be1040f2ff9ab Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 30 Mar 2021 18:07:15 -0400 Subject: [PATCH 01/37] Add unitary synthesis plugin interface This commit adds the initial steps for a unitary synthesis plugin interface. It enables external packages to ship plugin packages that then integrate cleanly into qiskit's transpiler without any need for extra imports or qiskit changes. The user can then just specify the 'unitary_synthesis_method' kwarg on the transpile() call and use the name of the external plugin and the UnitarySynthesis pass will leverage that plugin for synthesizing the unitary. --- qiskit/compiler/transpiler.py | 37 +++++--- qiskit/transpiler/passes/synthesis/plugin.py | 49 +++++++++++ .../passes/synthesis/unitary_synthesis.py | 84 ++++++++++++++----- qiskit/transpiler/passmanager_config.py | 7 +- .../transpiler/preset_passmanagers/level0.py | 4 +- .../transpiler/preset_passmanagers/level1.py | 4 +- .../transpiler/preset_passmanagers/level2.py | 4 +- .../transpiler/preset_passmanagers/level3.py | 7 +- requirements.txt | 1 + setup.py | 7 +- 10 files changed, 162 insertions(+), 42 deletions(-) create mode 100644 qiskit/transpiler/passes/synthesis/plugin.py diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index a8849669d317..8684160039ac 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -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, @@ -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. " @@ -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) @@ -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. @@ -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) @@ -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], @@ -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 @@ -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 diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py new file mode 100644 index 000000000000..58e68614652b --- /dev/null +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -0,0 +1,49 @@ + +import abc + +import stevedore + +class UnitarySynthesisPlugin(abc.ABC): + """Abstract plugin Synthesis plugin class + + 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): + """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) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 1f4a7dcf3c81..3ff87bd0c268 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -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): @@ -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, + method: str = None): """ Synthesize unitaries over some basis gates. @@ -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`. @@ -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 diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index ee0f50cb11ed..5585b5511cfb 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -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: @@ -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 @@ -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 diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index b543055ea4c5..73179cc278b0 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -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) @@ -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) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 26d5db19a62b..5f8081ae80e1 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -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) @@ -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) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 0a5fa68aa156..10692285a65b 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -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) @@ -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) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 41081b89de95..ed4bc714c9f2 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -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() @@ -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) @@ -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(), ] diff --git a/requirements.txt b/requirements.txt index e75908889a19..753369fc8b97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ dill>=0.3 fastjsonschema>=2.10 python-constraint>=1.4 python-dateutil>=2.8.0 +stevedore>=3.0.0 diff --git a/setup.py b/setup.py index dcfd4e1b617d..72e7ca0bc28e 100755 --- a/setup.py +++ b/setup.py @@ -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', + ] + }, ) From 1fb017b2a2aff8629cf6a23afe1aaf8302fff207 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 13 Apr 2021 10:25:37 -0400 Subject: [PATCH 02/37] Also add qubits in payload with coupling map --- qiskit/transpiler/passes/synthesis/unitary_synthesis.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 3ff87bd0c268..3b73052d70a5 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -107,6 +107,11 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: kwargs['basis_gates'] = self._basis_gates if plugin.supports_coupling_map: kwargs['coupling_map'] = self._coupling_map + kwargs['qubits'] = None + layout = self.property_set.get('layout') + if layout: + qubit_map = layout.get_virtual_bits() + kwargs['qubits'] = [qubit_map[x] for x in node.op.qargs] if plugin.supports_approximation_degree: kwargs['approximation_degree'] = self._approximation_degree unitary = node.op.to_matrix() From 1da2d8753c7cae83df6d69be952377b6e6bfc8a0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 13 Apr 2021 10:31:07 -0400 Subject: [PATCH 03/37] Fix lint --- qiskit/compiler/transpiler.py | 29 +++++++++++-------- qiskit/transpiler/passes/synthesis/plugin.py | 15 ++++++++-- .../passes/synthesis/unitary_synthesis.py | 4 +-- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 8684160039ac..b08c1a27bc16 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -186,6 +186,10 @@ def callback_func(**kwargs): output_name: A list with strings to identify the output circuits. The length of the list should be exactly the length of the ``circuits`` parameter. + unitary_synthesis_method (str): The name of the unitary synthesis + method to use. By default 'default' is used, which is the only + method included with qiskit. If you have installed any unitary + synthesis plugins you can use the name exported by the plugin. Returns: The transpiled circuit(s). @@ -448,18 +452,19 @@ def _parse_transpile_args(circuits, backend, 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], - initial_layout=args[3], - layout_method=args[4], - routing_method=args[5], - translation_method=args[6], - scheduling_method=args[7], - instruction_durations=args[8], - approximation_degree=args[9], - seed_transpiler=args[10], - unitary_synthesis_method=args[11]), + transpile_args = {'pass_manager_config': PassManagerConfig( + basis_gates=args[0], + coupling_map=args[1], + backend_properties=args[2], + initial_layout=args[3], + layout_method=args[4], + routing_method=args[5], + translation_method=args[6], + scheduling_method=args[7], + instruction_durations=args[8], + approximation_degree=args[9], + seed_transpiler=args[10], + unitary_synthesis_method=args[11]), 'optimization_level': args[12], 'output_name': args[13], 'callback': args[14], diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 58e68614652b..c8a8a3af8c82 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -1,13 +1,24 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# 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. import abc import stevedore + class UnitarySynthesisPlugin(abc.ABC): """Abstract plugin Synthesis plugin class - This class abstract class is - + This abstract class defines the interface for unitary synthesis plugins. """ @property diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 3b73052d70a5..06370ece60d8 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -63,7 +63,7 @@ class UnitarySynthesis(TransformationPass): def __init__(self, basis_gates: List[str], approximation_degree: float = 1, - coupling_map = None, + coupling_map=None, method: str = None): """ Synthesize unitaries over some basis gates. @@ -111,7 +111,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: layout = self.property_set.get('layout') if layout: qubit_map = layout.get_virtual_bits() - kwargs['qubits'] = [qubit_map[x] for x in node.op.qargs] + kwargs['qubits'] = [qubit_map[x] for x in node.qargs] if plugin.supports_approximation_degree: kwargs['approximation_degree'] = self._approximation_degree unitary = node.op.to_matrix() From 0532a1e0cda519518f02e7701a720d269b15074a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 13 Apr 2021 10:40:14 -0400 Subject: [PATCH 04/37] Fix logic --- .../passes/synthesis/unitary_synthesis.py | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 06370ece60d8..d5c67f9976cb 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -92,26 +92,25 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Returns: Output dag with UnitaryGates synthesized to target basis. """ + 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 + if plugin.supports_coupling_map: + dag_bit_indices = {bit: idx + for idx, bit in enumerate(dag.qubits)} 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 - kwargs['qubits'] = None - layout = self.property_set.get('layout') - if layout: - qubit_map = layout.get_virtual_bits() - kwargs['qubits'] = [qubit_map[x] for x in node.qargs] + kwargs['qubits'] = [dag_bit_indices[x] for x in node.qargs] if plugin.supports_approximation_degree: kwargs['approximation_degree'] = self._approximation_degree unitary = node.op.to_matrix() From 19bb0c8a3e96a6e829bc03d45a16077349aa14f6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 14 Apr 2021 17:10:14 -0400 Subject: [PATCH 05/37] Add documentation about the plugin interface --- qiskit/transpiler/passes/__init__.py | 7 + qiskit/transpiler/passes/synthesis/plugin.py | 148 ++++++++++++++++++- 2 files changed, 153 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index ac40d5f8c48e..b3d3b348849a 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -120,6 +120,13 @@ RemoveFinalMeasurements DAGFixedPoint FixedPoint + + +Transpiler Pass Plugins +======================= + +.. automodule:: qiskit.transpiler.passes.synthesis.plugin + """ # layout selection (placement) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index c8a8a3af8c82..9dbe49214fad 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -14,6 +14,124 @@ import stevedore +""" +################# +Synthesis Plugins +################# + +This module defines the plugin interfaces for the synthesis transpiler passes +in Qiskit. These provide a hook point for external python packages to implement +their own synthesis techniques and have them seamlessly exposed as opt-in +options to users when the run :func:`~qiskit.compiler.transpile`. + +The plugin interfaces are built using setuptools +`entry points `__ +which enable packages external to qiskit to advertise they include a synthesis +plugin. + +Writing Plugins +=============== + +Unitary Synthesis Plugins +------------------------- + +To write a unitary synthesis plugin there are 2 main steps. The first step is +to create a subclass of the abstract plugin class: +:class:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin`. +subclass. The plugin class defines the interface and contract for unitary synthesis +plugins. The primary method is +:meth:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin.run` +which takes in a single positional argument, a unitary matrix as a numpy array, +and is expects to return a :class:`~qiskit.dagcircuit.DAGCircuit` object +representing the synthesized circuit from that unitary matrix. Then to inform +the Qiskit transpiler about what information is necessary for the pass there +are several required property methods that need to be implemented, +``supports_basis_gates``, ``supports_coupling_map``, and +``supports_approximation_degree`` which return either ``True`` or ``False`` +depending on whether the plugin supports and/or requires that input to perform +synthesis. An example plugin class would look something like:: + + from qiskit.transpiler.passes.synthesis import plugin + + from qiskit_plugin_pkg.synthesis import generate_dag_circuit_from_matrix + + + class SpecialUnitarySynthesis(plugin.UnitarySynthesisPlugin): + + @property + def supports_basis_gates(self): + return True + + @property + def supports_coupling_map(self): + return False + + @property + def supports_approximation_degree(self): + return False + + def run(self, unitary, **options): + basis_gates = options['basis_gates'] + dag_circuit = generate_dag_circuit_from_matrix(unitary, basis_gates) + return dag_circuit + +If for some reason the available inputs to the +:meth:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin.run` +method are insufficient please open an issue and we can discuss expanding the +plugin interface with new opt-in inputs that can be added in a backwards +compatible manner for future releases. Do note though that this plugin interface +is considered stable and guaranteed to not change in a breaking manner. If +changes are needed (for example to expand the available optional input options) +it will be done in a way that will **not** require changes from existing +plugins. + +The second step is to expose the +:class:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin` as +a setuptools entry point in the package metadata. This is done by simply adding +an ``entry_points`` entry to the ``setuptools.setup`` call in the ``setup.py`` +for the plugin package with the necessary entry points under the +``qiskit.unitary_synthesis`` namespace. For example:: + + entry_points={ + 'qiskit.unitary_synthesis': [ + 'special = qiskit_plugin_pkg.module.plugin:SpecialUnitarySynthesis', + ] + }, + +(note that the entry point ``name = path`` is a single string not a Python +expression). There isn't a limit to the number of plugins a single package can +include as long as each plugin has a unique name. So a single package can +expose multiple plugins if necessary. + +Using Plugins +============= + +To use a plugin all you need to do is install the package that includes a +synthesis plugin. Then Qiskit will automatically discover the installed +plugins and expose them as valid options for the appropriate +:func:`~qiskit.compiler.transpiler` kwargs and pass constructors. If there are +any installed plugins which can't be loaded/imported this will be logged to +Python logging. + +To get the installed list of installed unitary synthesis plugins you can use the +:func:`qiskit.transpiler.passes.synthesis.plugin.get_unitary_synthesis_plugin_names` +function. + +Plugin API +========== + +Unitary Synthesis Plugins +------------------------- + +.. autosummary:: + :toctree: ../stubs/ + + UnitarySynthesisPlugin + UnitarySynthesisPluginManager + get_unitary_synthesis_plugin_names + +""" + class UnitarySynthesisPlugin(abc.ABC): """Abstract plugin Synthesis plugin class @@ -41,10 +159,23 @@ def supports_approximation_degree(self): @abc.abstractmethod def run(self, unitary, **options): - """Run synthesis for the given unitary + """Run synthesis for the given unitary matrix Args: - unitary (numpy.ndarray): The unitary + unitary (numpy.ndarray): The unitary matrix to synthesize to a + :class:`~qiskit.dagcircuit.DAGCircuit` object + options: The optional kwargs that are passed based on the output + of :meth:`supports_basis_gates`, :meth:`supports_coupling_map`, + and :meth:`supports_approximation_degree`. If + :meth:`supports_coupling_map` returns ``True`` a kwarg + ``coupling_map`` will be passed either containing ``None`` (if + there is no coupling map) or a + :class:`~qiskit.transpiler.CouplingMap` object. If + :meth:`supports_basis_gates` returns ``True`` then a kwarg + ``basis_gates`` will the list of basis gate names will be + passed. Finally if :meth:`supports_approximation_degree` + returns ``True`` a kwarg ``approximation_degree`` containing + a float for the approximation value will be passed. Returns: DAGCircuit: The dag circuit representation of the unitary @@ -58,3 +189,16 @@ def __init__(self): self.ext_plugins = stevedore.ExtensionManager( 'qiskit.unitary_synthesis', invoke_on_load=True, propagate_map_exceptions=True) + + +def get_unitary_synthesis_plugin_names(): + """Return a list of installed unitary synthesis plugin names + + Returns: + list: A list of the installed unitary synthesis plugin names. The + plugin names are valid values for the + :func:`~qiskit.compiler.transpile` kwarg + ``unitary_synthesis_method``. + """ + plugins = UnitarySynthesisPluginManager() + return plugins.ext_plugins.names() From fece87510952550323a929eacee4cb0c37f7c429 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 14 Apr 2021 17:12:37 -0400 Subject: [PATCH 06/37] Export get_unitary_synthesis_plugin_names from qiskit.transpiler.passes --- qiskit/transpiler/passes/__init__.py | 1 + qiskit/transpiler/passes/synthesis/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index b3d3b348849a..e6880afe6e1a 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -182,6 +182,7 @@ # synthesis from .synthesis import UnitarySynthesis +from .synthesis import get_unitary_synthesis_plugin_names # circuit scheduling from .scheduling import ALAPSchedule diff --git a/qiskit/transpiler/passes/synthesis/__init__.py b/qiskit/transpiler/passes/synthesis/__init__.py index 1e4dc6518fa4..f3383484ce82 100644 --- a/qiskit/transpiler/passes/synthesis/__init__.py +++ b/qiskit/transpiler/passes/synthesis/__init__.py @@ -13,3 +13,4 @@ """Module containing transpiler synthesis passes.""" from .unitary_synthesis import UnitarySynthesis +from .plugin import get_unitary_synthesis_plugin_names From b7678cf69d908341ef3b62da0f7bfa111c729da3 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 15 Apr 2021 06:50:32 -0400 Subject: [PATCH 07/37] Fix lint --- qiskit/transpiler/passes/__init__.py | 2 +- .../transpiler/passes/synthesis/__init__.py | 2 +- qiskit/transpiler/passes/synthesis/plugin.py | 27 ++++++++++++------- .../passes/synthesis/unitary_synthesis.py | 21 ++++++++++----- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index e6880afe6e1a..8286fa27dba8 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -182,7 +182,7 @@ # synthesis from .synthesis import UnitarySynthesis -from .synthesis import get_unitary_synthesis_plugin_names +from .synthesis import unitary_synthesis_plugin_names # circuit scheduling from .scheduling import ALAPSchedule diff --git a/qiskit/transpiler/passes/synthesis/__init__.py b/qiskit/transpiler/passes/synthesis/__init__.py index f3383484ce82..57e389f44992 100644 --- a/qiskit/transpiler/passes/synthesis/__init__.py +++ b/qiskit/transpiler/passes/synthesis/__init__.py @@ -13,4 +13,4 @@ """Module containing transpiler synthesis passes.""" from .unitary_synthesis import UnitarySynthesis -from .plugin import get_unitary_synthesis_plugin_names +from .plugin import unitary_synthesis_plugin_names diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 9dbe49214fad..ae59faebe0eb 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -10,14 +10,12 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -import abc - -import stevedore - """ -################# -Synthesis Plugins -################# +==================================================================== +Synthesis Plugins (:mod:`qiskit.transpiler.passes.synthesis.plugin`) +==================================================================== + +.. currentmodule:: qiskit.transpiler.passes.synthesis.plugin This module defines the plugin interfaces for the synthesis transpiler passes in Qiskit. These provide a hook point for external python packages to implement @@ -114,7 +112,7 @@ def run(self, unitary, **options): Python logging. To get the installed list of installed unitary synthesis plugins you can use the -:func:`qiskit.transpiler.passes.synthesis.plugin.get_unitary_synthesis_plugin_names` +:func:`qiskit.transpiler.passes.synthesis.plugin.unitary_synthesis_plugin_names` function. Plugin API @@ -128,10 +126,14 @@ def run(self, unitary, **options): UnitarySynthesisPlugin UnitarySynthesisPluginManager - get_unitary_synthesis_plugin_names + unitary_synthesis_plugin_names """ +import abc + +import stevedore + class UnitarySynthesisPlugin(abc.ABC): """Abstract plugin Synthesis plugin class @@ -184,6 +186,11 @@ def run(self, unitary, **options): class UnitarySynthesisPluginManager: + """Unitary Synthesis plugin manager class + + This class tracks the installed plugins, it has a single property, + ext_plugins which contains a list of stevedore plugin objects. + """ def __init__(self): self.ext_plugins = stevedore.ExtensionManager( @@ -191,7 +198,7 @@ def __init__(self): propagate_map_exceptions=True) -def get_unitary_synthesis_plugin_names(): +def unitary_synthesis_plugin_names(): """Return a list of installed unitary synthesis plugin names Returns: diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index d5c67f9976cb..30bcbd009d81 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -91,6 +91,11 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Returns: Output dag with UnitaryGates synthesized to target basis. + Raises: + QiskitError: if a 'method' was specified for the class and is not + found in the installed plugins list. The list of installed + plugins can be queried with + :func:`~qiskit.transpiler.passes.synthesis.plugins.unitary_synthesis_plugin_names` """ if not self.method: method = 'default' @@ -99,35 +104,39 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: 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 - if plugin.supports_coupling_map: + plugin_method = self.plugins.ext_plugins[method].obj + if plugin_method.supports_coupling_map: dag_bit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} for node in dag.named_nodes('unitary'): synth_dag = None kwargs = {} - if plugin.supports_basis_gates: + if plugin_method.supports_basis_gates: kwargs['basis_gates'] = self._basis_gates - if plugin.supports_coupling_map: + if plugin_method.supports_coupling_map: kwargs['coupling_map'] = self._coupling_map kwargs['qubits'] = [dag_bit_indices[x] for x in node.qargs] - if plugin.supports_approximation_degree: + if plugin_method.supports_approximation_degree: kwargs['approximation_degree'] = self._approximation_degree unitary = node.op.to_matrix() - synth_dag = plugin.run(unitary, **kwargs) + synth_dag = plugin_method.run(unitary, **kwargs) if synth_dag: dag.substitute_node_with_dag(node, synth_dag) return dag class DefaultUnitarySynthesis(plugin.UnitarySynthesisPlugin): + """The default unitary synthesis plugin.""" + @property def supports_basis_gates(self): return True + @property def supports_coupling_map(self): return False + @property def supports_approximation_degree(self): return True From 5622895a01d4a0aaa0b60eaa5e1960062bbab444 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 15 Apr 2021 07:48:05 -0400 Subject: [PATCH 08/37] Tweak doc organization slightly --- qiskit/transpiler/passes/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 8286fa27dba8..536373ea6266 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -125,7 +125,10 @@ Transpiler Pass Plugins ======================= -.. automodule:: qiskit.transpiler.passes.synthesis.plugin +.. autosummary:: + :toctree: ../stubs/ + + qiskit.transpiler.passes.synthesis.plugin """ From faa260a98ed0fb1cd1802bf3da4b9d28f3eff568 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 19 Apr 2021 11:05:47 -0400 Subject: [PATCH 09/37] Use UnitarySynthesis for unroll3q step The usefulness of the plugin for unitaries >2q in the default pass manager pipelines was limited by the fact that previously the pass managers were all setup to run unroll3q before the unitary synthesis pass is ever run. The unroll3q pass just calls the circuit.data to decompose gates >=3q which for unitary gate objects just calls an inlined equivalent of the 'default' plugin. The purpose of this is to ensure later passes only ever need to deal with 2q gates. However with plugins now if a plugin only works on > 2q we'll never actually be passing an >= 3q unitaries to the plugin in the default pipelines. To fix this issue, this commit calls unitary synthesis before unroll 3q to use the synthesis pass to unroll any unitary gates instead of relying on the gate class's internal decomposition method which will always use the qiskit decomposition techniques. --- qiskit/transpiler/preset_passmanagers/level0.py | 6 +++++- qiskit/transpiler/preset_passmanagers/level1.py | 6 +++++- qiskit/transpiler/preset_passmanagers/level2.py | 6 +++++- qiskit/transpiler/preset_passmanagers/level3.py | 5 ++++- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 4629591c4872..8c813a4fa68b 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -106,7 +106,10 @@ def _choose_layout_condition(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 3. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = Unroll3qOrMore() + _unroll3q = [UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method), + Unroll3qOrMore()] # 4. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -142,6 +145,7 @@ def _swap_condition(property_set): Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, + coupling_map=coupling_map, method=unitary_synthesis_method), ] else: diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 75095e1c027e..e7ac8b23ccde 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -123,7 +123,10 @@ def _not_perfect_yet(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 4. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = Unroll3qOrMore() + _unroll3q = [UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method), + Unroll3qOrMore()] # 5. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -159,6 +162,7 @@ def _swap_condition(property_set): Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, + coupling_map=coupling_map, method=unitary_synthesis_method), ] else: diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 2d05982bd32e..a0f5d1425ce4 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -151,7 +151,10 @@ def _csp_not_found_match(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 3. Unroll to 1q or 2q gates - _unroll3q = Unroll3qOrMore() + _unroll3q = [UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method), + Unroll3qOrMore()] # 4. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -187,6 +190,7 @@ def _swap_condition(property_set): Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, + coupling_map=coupling_map, method=unitary_synthesis_method), ] else: diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 59d7393ace02..61a08b47c5a7 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -101,7 +101,10 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: unitary_synthesis_method = pass_manager_config.unitary_synthesis_method # 1. Unroll to 1q or 2q gates - _unroll3q = Unroll3qOrMore() + _unroll3q = [UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method), + Unroll3qOrMore()] # 2. Layout on good qubits if calibration info available, otherwise on dense links _given_layout = SetLayout(initial_layout) From dd9211f0af3e4895893b7368edc95cf2f0f84a57 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 28 May 2021 08:11:14 -0400 Subject: [PATCH 10/37] Run black --- qiskit/compiler/transpiler.py | 198 +++++++++++------- qiskit/transpiler/passes/synthesis/plugin.py | 4 +- .../passes/synthesis/unitary_synthesis.py | 43 ++-- qiskit/transpiler/passmanager_config.py | 28 +-- .../transpiler/preset_passmanagers/level0.py | 22 +- .../transpiler/preset_passmanagers/level1.py | 22 +- .../transpiler/preset_passmanagers/level2.py | 22 +- .../transpiler/preset_passmanagers/level3.py | 25 ++- 8 files changed, 225 insertions(+), 139 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 63b8cff7d0d8..5b4063fdc758 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -43,27 +43,27 @@ logger = logging.getLogger(__name__) -def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]], - backend: Optional[Union[Backend, BaseBackend]] = None, - basis_gates: Optional[List[str]] = None, - coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None, - backend_properties: Optional[BackendProperties] = None, - initial_layout: Optional[Union[Layout, Dict, List]] = None, - layout_method: Optional[str] = None, - routing_method: Optional[str] = None, - translation_method: Optional[str] = None, - scheduling_method: Optional[str] = None, - instruction_durations: Optional[InstructionDurationsType] = None, - dt: Optional[float] = None, - approximation_degree: Optional[float] = None, - seed_transpiler: Optional[int] = None, - optimization_level: Optional[int] = None, - pass_manager: Optional[PassManager] = None, - callback: Optional[Callable[[BasePass, DAGCircuit, float, - PropertySet, int], Any]] = None, - output_name: Optional[Union[str, List[str]]] = None, - unitary_synthesis_method: Optional[str] = None) -> Union[QuantumCircuit, - List[QuantumCircuit]]: +def transpile( + circuits: Union[QuantumCircuit, List[QuantumCircuit]], + backend: Optional[Union[Backend, BaseBackend]] = None, + basis_gates: Optional[List[str]] = None, + coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None, + backend_properties: Optional[BackendProperties] = None, + initial_layout: Optional[Union[Layout, Dict, List]] = None, + layout_method: Optional[str] = None, + routing_method: Optional[str] = None, + translation_method: Optional[str] = None, + scheduling_method: Optional[str] = None, + instruction_durations: Optional[InstructionDurationsType] = None, + dt: Optional[float] = None, + approximation_degree: Optional[float] = None, + seed_transpiler: Optional[int] = None, + optimization_level: Optional[int] = None, + pass_manager: Optional[PassManager] = None, + callback: Optional[Callable[[BasePass, DAGCircuit, float, PropertySet, int], Any]] = None, + 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, @@ -214,19 +214,28 @@ def callback_func(**kwargs): return circuits[0] if pass_manager is not None: - _check_conflicting_argument(optimization_level=optimization_level, basis_gates=basis_gates, - coupling_map=coupling_map, seed_transpiler=seed_transpiler, - backend_properties=backend_properties, - initial_layout=initial_layout, layout_method=layout_method, - 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. " - "The preferred way to tranpile a circuit using a custom pass manager is" - " pass_manager.run(circuit)", DeprecationWarning, stacklevel=2) + _check_conflicting_argument( + optimization_level=optimization_level, + basis_gates=basis_gates, + coupling_map=coupling_map, + seed_transpiler=seed_transpiler, + backend_properties=backend_properties, + initial_layout=initial_layout, + layout_method=layout_method, + 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. " + "The preferred way to tranpile a circuit using a custom pass manager is" + " pass_manager.run(circuit)", + DeprecationWarning, + stacklevel=2, + ) return pass_manager.run(circuits, output_name=output_name, callback=callback) if optimization_level is None: @@ -242,13 +251,26 @@ def callback_func(**kwargs): ) # Get transpile_args to configure the circuit transpilation job(s) - transpile_args = _parse_transpile_args(circuits, backend, basis_gates, coupling_map, - backend_properties, initial_layout, - layout_method, routing_method, translation_method, - scheduling_method, instruction_durations, dt, - approximation_degree, seed_transpiler, - optimization_level, callback, output_name, - unitary_synthesis_method) + transpile_args = _parse_transpile_args( + circuits, + backend, + basis_gates, + coupling_map, + backend_properties, + initial_layout, + layout_method, + routing_method, + translation_method, + scheduling_method, + instruction_durations, + dt, + approximation_degree, + seed_transpiler, + optimization_level, + callback, + output_name, + unitary_synthesis_method, + ) _check_circuits_coupling_map(circuits, transpile_args, backend) @@ -412,12 +434,26 @@ def _remap_layout_faulty_backend(layout, faulty_qubits_map): return new_layout -def _parse_transpile_args(circuits, backend, - basis_gates, coupling_map, backend_properties, - initial_layout, layout_method, routing_method, translation_method, - scheduling_method, instruction_durations, dt, - approximation_degree, seed_transpiler, optimization_level, - callback, output_name, unitary_synthesis_method) -> List[Dict]: +def _parse_transpile_args( + circuits, + backend, + basis_gates, + coupling_map, + backend_properties, + initial_layout, + layout_method, + routing_method, + translation_method, + scheduling_method, + instruction_durations, + dt, + approximation_degree, + seed_transpiler, + optimization_level, + 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. @@ -448,8 +484,9 @@ 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) + 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) @@ -463,29 +500,46 @@ 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, 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], - initial_layout=args[3], - layout_method=args[4], - routing_method=args[5], - translation_method=args[6], - scheduling_method=args[7], - instruction_durations=args[8], - approximation_degree=args[9], - 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]} + 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, + 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], + initial_layout=args[3], + layout_method=args[4], + routing_method=args[5], + translation_method=args[6], + scheduling_method=args[7], + instruction_durations=args[8], + approximation_degree=args[9], + 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 diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index ae59faebe0eb..161a931d39cd 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -194,8 +194,8 @@ class UnitarySynthesisPluginManager: def __init__(self): self.ext_plugins = stevedore.ExtensionManager( - 'qiskit.unitary_synthesis', invoke_on_load=True, - propagate_map_exceptions=True) + "qiskit.unitary_synthesis", invoke_on_load=True, propagate_map_exceptions=True + ) def unitary_synthesis_plugin_names(): diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index be35fbea1e65..2dd16fb72f00 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -22,8 +22,7 @@ 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.circuit.library.standard_gates import iSwapGate, CXGate, CZGate, RXXGate, ECRGate from qiskit.transpiler.passes.synthesis import plugin @@ -60,11 +59,13 @@ def _choose_euler_basis(basis_gates): class UnitarySynthesis(TransformationPass): """Synthesize gates according to their basis gates.""" - def __init__(self, - basis_gates: List[str], - approximation_degree: float = 1, - coupling_map=None, - method: str = None): + def __init__( + self, + basis_gates: List[str], + approximation_degree: float = 1, + coupling_map=None, + method: str = None, + ): """ Synthesize unitaries over some basis gates. @@ -98,26 +99,24 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: :func:`~qiskit.transpiler.passes.synthesis.plugins.unitary_synthesis_plugin_names` """ if not self.method: - method = 'default' + 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) + raise QiskitError("Specified method: %s not found in plugin list" % method) plugin_method = self.plugins.ext_plugins[method].obj if plugin_method.supports_coupling_map: - dag_bit_indices = {bit: idx - for idx, bit in enumerate(dag.qubits)} - for node in dag.named_nodes('unitary'): + dag_bit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} + for node in dag.named_nodes("unitary"): synth_dag = None kwargs = {} if plugin_method.supports_basis_gates: - kwargs['basis_gates'] = self._basis_gates + kwargs["basis_gates"] = self._basis_gates if plugin_method.supports_coupling_map: - kwargs['coupling_map'] = self._coupling_map - kwargs['qubits'] = [dag_bit_indices[x] for x in node.qargs] + kwargs["coupling_map"] = self._coupling_map + kwargs["qubits"] = [dag_bit_indices[x] for x in node.qargs] if plugin_method.supports_approximation_degree: - kwargs['approximation_degree'] = self._approximation_degree + kwargs["approximation_degree"] = self._approximation_degree unitary = node.op.to_matrix() synth_dag = plugin_method.run(unitary, **kwargs) if synth_dag: @@ -141,8 +140,8 @@ def supports_approximation_degree(self): return True def run(self, unitary, **options): - basis_gates = options['basis_gates'] - approximation_degree = options['approximation_degree'] + basis_gates = options["basis_gates"] + approximation_degree = options["approximation_degree"] euler_basis = _choose_euler_basis(basis_gates) kak_gate = _choose_kak_gate(basis_gates) @@ -158,9 +157,7 @@ def run(self, unitary, **options): elif unitary.shape == (4, 4): if decomposer2q is None: return None - synth_dag = circuit_to_dag(decomposer2q(unitary, - basis_fidelity=approximation_degree)) + 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) + synth_dag = circuit_to_dag(isometry.Isometry(unitary.op.to_matrix(), 0, 0).definition) return synth_dag diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 44cac2973c9f..aa3137cf020e 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -16,19 +16,21 @@ class PassManagerConfig: """Pass Manager Configuration.""" - def __init__(self, - initial_layout=None, - basis_gates=None, - coupling_map=None, - layout_method=None, - routing_method=None, - translation_method=None, - scheduling_method=None, - instruction_durations=None, - backend_properties=None, - approximation_degree=None, - seed_transpiler=None, - unitary_synthesis_method=None): + def __init__( + self, + initial_layout=None, + basis_gates=None, + coupling_map=None, + layout_method=None, + routing_method=None, + translation_method=None, + scheduling_method=None, + instruction_durations=None, + backend_properties=None, + approximation_degree=None, + seed_transpiler=None, + unitary_synthesis_method=None, + ): """Initialize a PassManagerConfig object Args: diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index c087b31ad804..f3d9194e73e0 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -106,10 +106,15 @@ def _choose_layout_condition(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 3. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = [UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method), - Unroll3qOrMore()] + _unroll3q = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + ), + Unroll3qOrMore(), + ] # 4. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -149,9 +154,12 @@ def _swap_condition(property_set): Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method), + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + ), ] else: raise TranspilerError("Invalid translation method %s." % translation_method) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index b90bbb9e87a1..6acc21b3ff66 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -126,10 +126,15 @@ def _not_perfect_yet(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 4. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = [UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method), - Unroll3qOrMore()] + _unroll3q = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + ), + Unroll3qOrMore(), + ] # 5. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -169,9 +174,12 @@ def _swap_condition(property_set): Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method), + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + ), ] else: raise TranspilerError("Invalid translation method %s." % translation_method) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 9b21c613abc8..1e0167356730 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -160,10 +160,15 @@ def _csp_not_found_match(property_set): _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] # 3. Unroll to 1q or 2q gates - _unroll3q = [UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method), - Unroll3qOrMore()] + _unroll3q = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + ), + Unroll3qOrMore(), + ] # 4. Swap to fit the coupling map _swap_check = CheckMap(coupling_map) @@ -203,9 +208,12 @@ def _swap_condition(property_set): Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method), + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + ), ] else: raise TranspilerError("Invalid translation method %s." % translation_method) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 05ec3655420d..c5dd2d8272cb 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -101,10 +101,15 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: unitary_synthesis_method = pass_manager_config.unitary_synthesis_method # 1. Unroll to 1q or 2q gates - _unroll3q = [UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method), - Unroll3qOrMore()] + _unroll3q = [ + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + method=unitary_synthesis_method, + ), + Unroll3qOrMore(), + ] # 2. Layout on good qubits if calibration info available, otherwise on dense links _given_layout = SetLayout(initial_layout) @@ -206,8 +211,11 @@ def _swap_condition(property_set): Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - method=unitary_synthesis_method), + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + method=unitary_synthesis_method, + ), ] else: raise TranspilerError("Invalid translation method %s." % translation_method) @@ -234,8 +242,9 @@ def _opt_control(property_set): _opt = [ Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree, - method=unitary_synthesis_method), + UnitarySynthesis( + basis_gates, approximation_degree=approximation_degree, method=unitary_synthesis_method + ), Optimize1qGatesDecomposition(basis_gates), CommutativeCancellation(), ] From 20c10b77a98c4e6f18a5aaabe9c35cb1ae1978d0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 28 May 2021 10:09:10 -0400 Subject: [PATCH 11/37] Tweak doc organization slightly --- docs/apidocs/terra.rst | 1 + docs/apidocs/transpiler_plugins.rst | 6 ++++++ qiskit/transpiler/passes/__init__.py | 10 ---------- 3 files changed, 7 insertions(+), 10 deletions(-) create mode 100644 docs/apidocs/transpiler_plugins.rst diff --git a/docs/apidocs/terra.rst b/docs/apidocs/terra.rst index 65ddd20199ac..c7816eb4ef3c 100644 --- a/docs/apidocs/terra.rst +++ b/docs/apidocs/terra.rst @@ -31,6 +31,7 @@ Qiskit Terra API Reference transpiler transpiler_passes transpiler_preset + transpiler_plugins utils validation visualization diff --git a/docs/apidocs/transpiler_plugins.rst b/docs/apidocs/transpiler_plugins.rst new file mode 100644 index 000000000000..33646fe2d58f --- /dev/null +++ b/docs/apidocs/transpiler_plugins.rst @@ -0,0 +1,6 @@ +.. _qiskit-transpiler-plugins: + +.. automodule:: qiskit.transpiler.passes.synthesis.plugin + :no-members: + :no-inherited-members: + :no-special-members: diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 9521c6c05fac..508ec2bd2af0 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -123,16 +123,6 @@ RemoveFinalMeasurements DAGFixedPoint FixedPoint - - -Transpiler Pass Plugins -======================= - -.. autosummary:: - :toctree: ../stubs/ - - qiskit.transpiler.passes.synthesis.plugin - """ # layout selection (placement) From 291457ad661386a89622e734bfc29e9bf38b9249 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 3 Aug 2021 15:59:50 -0400 Subject: [PATCH 12/37] Fix lint --- qiskit/transpiler/passes/synthesis/plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 9974ca0dd523..189edbe2b816 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -227,7 +227,6 @@ def supports_gate_errors(self): """ pass - @abc.abstractmethod def run(self, unitary, **options): """Run synthesis for the given unitary matrix From 9557aafc601d8cf96f9d9e636deadf5c2e007726 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 3 Aug 2021 16:52:21 -0400 Subject: [PATCH 13/37] Fix pylint errors --- .../transpiler/passes/synthesis/unitary_synthesis.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index f89d762bff02..f9d8ce643840 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -28,7 +28,6 @@ from qiskit.circuit.library.standard_gates import iSwapGate, CXGate, CZGate, RXXGate, ECRGate from qiskit.transpiler.passes.synthesis import plugin from qiskit.providers.models import BackendProperties -from qiskit.providers.exceptions import BackendPropertyError def _choose_kak_gate(basis_gates): @@ -237,15 +236,6 @@ def supports_gate_errors(self): return True def run(self, unitary, **options): - """ - Raises: - TranspilerError: - 1. pulse_optimize is True but pulse optimal decomposition is not known - for requested basis. - 2. pulse_optimize is True and natural_direction is True but a preferred - gate direction can't be determined from the coupling map or the - relative gate lengths. - """ basis_gates = options["basis_gates"] approximation_degree = options["approximation_degree"] coupling_map = options["coupling_map"] From 35709d54810a08b6ad58cd284181132fb78c5a85 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Aug 2021 09:27:45 -0400 Subject: [PATCH 14/37] Apply suggestions from code review Co-authored-by: Jake Lishman --- qiskit/transpiler/passes/synthesis/plugin.py | 16 +++++++--------- .../passes/synthesis/unitary_synthesis.py | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 189edbe2b816..33099967ad15 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -20,7 +20,7 @@ This module defines the plugin interfaces for the synthesis transpiler passes in Qiskit. These provide a hook point for external python packages to implement their own synthesis techniques and have them seamlessly exposed as opt-in -options to users when the run :func:`~qiskit.compiler.transpile`. +options to users when they run :func:`~qiskit.compiler.transpile`. The plugin interfaces are built using setuptools `entry points `__ @@ -36,11 +36,11 @@ To write a unitary synthesis plugin there are 2 main steps. The first step is to create a subclass of the abstract plugin class: :class:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin`. -subclass. The plugin class defines the interface and contract for unitary synthesis +The plugin class defines the interface and contract for unitary synthesis plugins. The primary method is :meth:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin.run` which takes in a single positional argument, a unitary matrix as a numpy array, -and is expects to return a :class:`~qiskit.dagcircuit.DAGCircuit` object +and is expected to return a :class:`~qiskit.dagcircuit.DAGCircuit` object representing the synthesized circuit from that unitary matrix. Then to inform the Qiskit transpiler about what information is necessary for the pass there are several required property methods that need to be implemented, @@ -50,12 +50,10 @@ synthesis. An example plugin class would look something like:: from qiskit.transpiler.passes.synthesis import plugin - from qiskit_plugin_pkg.synthesis import generate_dag_circuit_from_matrix class SpecialUnitarySynthesis(plugin.UnitarySynthesisPlugin): - @property def supports_basis_gates(self): return True @@ -106,7 +104,7 @@ def run(self, unitary, **options): for the plugin package with the necessary entry points under the ``qiskit.unitary_synthesis`` namespace. For example:: - entry_points={ + entry_points = { 'qiskit.unitary_synthesis': [ 'special = qiskit_plugin_pkg.module.plugin:SpecialUnitarySynthesis', ] @@ -249,9 +247,9 @@ def run(self, unitary, **options): Returns: DAGCircuit: The dag circuit representation of the unitary. Alternatively, - you can return a tuple of the form (dag, wires) where dag is the dag + you can return a tuple of the form ``(dag, wires)`` where ``dag`` is the dag circuit representation of the circuit representation of the unitary - and wires is the mapping wires to use for + and ``wires`` is the mapping wires to use for :meth:`qiskit.dagcircuit.DAGCircuit.substitute_node_with_dag`. """ pass @@ -261,7 +259,7 @@ class UnitarySynthesisPluginManager: """Unitary Synthesis plugin manager class This class tracks the installed plugins, it has a single property, - ext_plugins which contains a list of stevedore plugin objects. + ``ext_plugins`` which contains a list of stevedore plugin objects. """ def __init__(self): diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index f9d8ce643840..a8e3ebba265f 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -331,7 +331,7 @@ def _synth_natural_direction( physical_gate_fidelity = 1 - gate_error if natural_direction is True and preferred_direction is None: raise TranspilerError( - f"No preferred direction of gate on qubits f{qubits}" + f"No preferred direction of gate on qubits {qubits} " "could be determined from coupling map or " "gate lengths." ) From 78f84b4a11037d09b818bf43e762ae825e8aaa89 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Aug 2021 10:41:28 -0400 Subject: [PATCH 15/37] Update plugin interface docs --- qiskit/transpiler/passes/synthesis/plugin.py | 43 ++++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 33099967ad15..459a54b2a0e3 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -97,6 +97,14 @@ def run(self, unitary, **options): it will be done in a way that will **not** require changes from existing plugins. +.. note:: + + All methods prefixed with ``supports_`` are reserved on a + ``UnitarySynthesisPlugin`` derived class for part of the interface. You + should not define any custom ``supports_*`` methods on a subclass that + are not defined in the abstract class. + + The second step is to expose the :class:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin` as a setuptools entry point in the package metadata. This is done by simply adding @@ -113,7 +121,8 @@ def run(self, unitary, **options): (note that the entry point ``name = path`` is a single string not a Python expression). There isn't a limit to the number of plugins a single package can include as long as each plugin has a unique name. So a single package can -expose multiple plugins if necessary. +expose multiple plugins if necessary. The name ``default`` is used by Qiskit +itself and can't be used in a plugin. Using Plugins ============= @@ -158,19 +167,37 @@ class UnitarySynthesisPlugin(abc.ABC): @property @abc.abstractmethod def supports_basis_gates(self): - """Return whether the plugin supports taking ``basis_gates``""" + """Return whether the plugin supports taking ``basis_gates`` + + If this returns ``True`` the plugin's ``run()`` method will be + passed a ``basis_gates`` kwarg with a list of gate names the target + backend supports. For example, ``['sx', 'x', 'cx', 'id', 'rz']``.""" pass @property @abc.abstractmethod def supports_coupling_map(self): - """Return whether the plugin supports taking ``coupling_map``""" + """Return whether the plugin supports taking ``coupling_map`` + + If this returns ``True`` the plugin's ``run()`` method will receive + two kwargs ``coupling_map`` and ``qubots``. The ``coupling_map`` kwarg + will be a :class:`~qiskit.transpiler.CouplingMap` object representing + the qubit connectivity of the target backend. The ``qubits`` kwarg will + recive a list of integers that represent the qubit indices in the + coupling map that unitary is on. + """ pass @property @abc.abstractmethod def supports_approximation_degree(self): - """Return whether the plugin supports taking ``approximation_degree``""" + """Return whether the plugin supports taking ``approximation_degree`` + + If this returns ``True`` the plugin's ``run()`` method will receive + a ``approximation_degree`` kwarg with a float value between 0 and 1 + representing the closeness of the approximation to use (0: lowest, + 1: highest). + """ pass @property @@ -207,6 +234,10 @@ def supports_gate_lengths(self): 'sx': {(0,): 0.0006149355812506126, (1,): 0.0006149355812506126}, 'cx': {(0, 1): 0.012012477900732316, (1, 0): 5.191111111111111e-07} } + + Do note that this dictionary might not be complete or could be empty + as it depends on the target backend reporting gate lengths on every + gate for each qubit. """ pass @@ -222,6 +253,10 @@ def supports_gate_errors(self): 'sx': {(0,): 0.0006149355812506126, (1,): 0.0006149355812506126}, 'cx': {(0, 1): 0.012012477900732316, (1, 0): 5.191111111111111e-07} } + + Do note that this dictionary might not be complete or could be empty + as it depends on the target backend reporting gate errors on every + gate for each qubit. """ pass From 1dd25b49650e2a00a164544c510f798b30e42229 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Aug 2021 10:56:00 -0400 Subject: [PATCH 16/37] Review comment updates on UnitarySynthesis pass --- .../passes/synthesis/unitary_synthesis.py | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index a8e3ebba265f..6db8f9c67ba2 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -13,7 +13,7 @@ """Synthesize UnitaryGates.""" from math import pi, inf -from typing import List, Union +from typing import List, Union, Optional from copy import deepcopy from qiskit.converters import circuit_to_dag @@ -21,7 +21,6 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError 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 @@ -72,7 +71,7 @@ def __init__( pulse_optimize: Union[bool, None] = None, natural_direction: Union[bool, None] = None, synth_gates: Union[List[str], None] = None, - method: str = None, + method: Optional[str] = None, ): """Synthesize unitaries over some basis gates. @@ -142,7 +141,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: Output dag with UnitaryGates synthesized to target basis. Raises: - QiskitError: if a 'method' was specified for the class and is not + TranspilerError: if a 'method' was specified for the class and is not found in the installed plugins list. The list of installed plugins can be queried with :func:`~qiskit.transpiler.passes.synthesis.plugins.unitary_synthesis_plugin_names` @@ -152,7 +151,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: else: method = self.method if method not in self.plugins.ext_plugins: - raise QiskitError("Specified method: %s not found in plugin list" % method) + raise TranspilerError("Specified method: %s not found in plugin list" % method) plugin_method = self.plugins.ext_plugins[method].obj if plugin_method.supports_coupling_map: dag_bit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} @@ -171,29 +170,9 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if plugin_method.supports_pulse_optimize: kwargs["pulse_optimize"] = self._pulse_optimize if plugin_method.supports_gate_lengths: - gate_lengths = {} - if self._backend_props: - for gate in self._backend_props._gates: - gate_lengths[gate] = {} - for k, v in self._backend_props._gates[gate].items(): - length = v.get("gate_length") - if length: - gate_lengths[gate][k] = length[0] - if not gate_lengths[gate]: - del gate_lengths[gate] - kwargs["gate_lengths"] = gate_lengths + kwargs["gate_lengths"] = _build_gate_lengths(self._backend_props) if plugin_method.supports_gate_errors: - gate_errors = {} - if self._backend_props: - for gate in self._backend_props._gates: - gate_errors[gate] = {} - for k, v in self._backend_props._gates[gate].items(): - error = v.get("gate_error") - if error: - gate_errors[gate][k] = error[0] - if not gate_errors[gate]: - del gate_errors[gate] - kwargs["gate_errors"] = gate_errors + kwargs["gate_errors"] = _build_gate_errors(self._backend_props) unitary = node.op.to_matrix() synth_dag = plugin_method.run(unitary, **kwargs) if synth_dag: @@ -204,6 +183,34 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: return dag +def _build_gate_lengths(props): + gate_lengths = {} + if props: + for gate in props._gates: + gate_lengths[gate] = {} + for k, v in props._gates[gate].items(): + length = v.get("gate_length") + if length: + gate_lengths[gate][k] = length[0] + if not gate_lengths[gate]: + del gate_lengths[gate] + return gate_lengths + + +def _build_gate_errors(props): + gate_errors = {} + if props: + for gate in props._gates: + gate_errors[gate] = {} + for k, v in props._gates[gate].items(): + error = v.get("gate_error") + if error: + gate_errors[gate][k] = error[0] + if not gate_errors[gate]: + del gate_errors[gate] + return gate_errors + + class DefaultUnitarySynthesis(plugin.UnitarySynthesisPlugin): """The default unitary synthesis plugin.""" @@ -335,7 +342,10 @@ def _synth_natural_direction( "could be determined from coupling map or " "gate lengths." ) - basis_fidelity = approximation_degree or physical_gate_fidelity + if approximation_degree is not None: + basis_fidelity = approximation_degree + else: + basis_fidelity = physical_gate_fidelity synth_circ = decomposer2q(su4_mat, basis_fidelity=basis_fidelity) synth_dag = circuit_to_dag(synth_circ) From 935ac012ae192f58f043cb58c11ba2cb715e6960 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 26 Aug 2021 16:21:29 +0100 Subject: [PATCH 17/37] Fix typos --- qiskit/transpiler/passes/synthesis/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 459a54b2a0e3..72138eb3ef0e 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -180,10 +180,10 @@ def supports_coupling_map(self): """Return whether the plugin supports taking ``coupling_map`` If this returns ``True`` the plugin's ``run()`` method will receive - two kwargs ``coupling_map`` and ``qubots``. The ``coupling_map`` kwarg + two kwargs ``coupling_map`` and ``qubits``. The ``coupling_map`` kwarg will be a :class:`~qiskit.transpiler.CouplingMap` object representing the qubit connectivity of the target backend. The ``qubits`` kwarg will - recive a list of integers that represent the qubit indices in the + receive a list of integers that represent the qubit indices in the coupling map that unitary is on. """ pass From c161b9d1931f42b3abb315005eb9e86a24565be0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Aug 2021 13:04:36 -0400 Subject: [PATCH 18/37] Include release notes --- ...ary-synthesis-plugin-a5ec21a1906149fa.yaml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 releasenotes/notes/unitary-synthesis-plugin-a5ec21a1906149fa.yaml diff --git a/releasenotes/notes/unitary-synthesis-plugin-a5ec21a1906149fa.yaml b/releasenotes/notes/unitary-synthesis-plugin-a5ec21a1906149fa.yaml new file mode 100644 index 000000000000..660d3e85d236 --- /dev/null +++ b/releasenotes/notes/unitary-synthesis-plugin-a5ec21a1906149fa.yaml @@ -0,0 +1,32 @@ +--- +features: + - | + Introduced a new unitary synthesis plugin interface which is used to enable + using alternative synthesis techniques included in external packages + seamlessly with the :class:`~qiskit.transpiler.passes.UnitarySynthesis` + transpiler pass. Users can select a plugin to use when calling + :func:`~qiskit.compiler.transpile` by setting the + ``unitary_synthesis_method`` kwarg to the plugin's name. A full list of + installed plugins can be found using the + :func:`qiskit.transpiler.passes.synthesis.plugin.unitary_synthesis_plugin_names` + function. For example, if you installed a package that includes a synthesis + plugin named ``special_synth`` you could use it with:: + + from qiskit import transpile + + transpile(qc, unitary_synthesis_method='special_synth', optimization_level=3) + + this will replace all uses of the :class:`~qiskit.transpiler.passes.UnitarySynthesis` + with the method included in the external package that exports the ``special_synth`` + plugin. + + The plugin interface is built around setuptools + `entry points `__ + which enables packages external to Qiskit to advertise they include a + synthesis plugin. For details on writing a new plugin refer to the + :mod:`qiskit.transpiler.passes.synthesis.plugin` module documentation. +upgrade: + - | + A new dependency `stevedore `__ has + been added to the requirements list. This is required by qiskit-terra as + it's used to build the unitary synthesis plugin interface. From 373f43dd32791875fb1a3a3c5253e1145a21d08e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 Aug 2021 13:05:44 -0400 Subject: [PATCH 19/37] Fix docstring typo --- qiskit/transpiler/passes/synthesis/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 72138eb3ef0e..4ae89060efd1 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -130,7 +130,7 @@ def run(self, unitary, **options): To use a plugin all you need to do is install the package that includes a synthesis plugin. Then Qiskit will automatically discover the installed plugins and expose them as valid options for the appropriate -:func:`~qiskit.compiler.transpiler` kwargs and pass constructors. If there are +:func:`~qiskit.compiler.transpile` kwargs and pass constructors. If there are any installed plugins which can't be loaded/imported this will be logged to Python logging. From def0a662a6aea0b632975717b238c23704568257 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 30 Aug 2021 13:24:31 -0400 Subject: [PATCH 20/37] Update qiskit/transpiler/passes/synthesis/unitary_synthesis.py Co-authored-by: Kevin Krsulich --- qiskit/transpiler/passes/synthesis/unitary_synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 6db8f9c67ba2..29a2545e9ed9 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -111,7 +111,7 @@ def __init__( `pulse_optimize` is False or None, default to ['unitary']. If None and `pulse_optimzie` == True, default to ['unitary', 'swap'] - method (str): THe unitary synthesis method plugin to use. + method (str): The unitary synthesis method plugin to use. """ super().__init__() From f83c841ebf57991814884cad6cf08e0b7a04a92c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 20 Sep 2021 07:42:39 -0400 Subject: [PATCH 21/37] Ignore deprecation warnings from importlib metadata Since the importlib metadata 4.8.0 release in late August 2021, stevedore <= 4.8.0 has raised a deprecation warning around using tuple based access (which was deprecated in 4.8.1 after 4.8.0 removed it and broke everything using stevedore). While this has been fixed on the master branch of stevedore (see: https://opendev.org/openstack/stevedore/commit/11da137e3fc34861be8ac3e664ee60a22e66b44c ) Until that fix is released this commit adds a deprecation warning ignore because there isn't anything we can do about the warning in qiskit. Once stevedore has a release that includes this fix we can remove the ignore from the tests. --- qiskit/test/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/test/base.py b/qiskit/test/base.py index af9708083375..b1181a9a5be9 100644 --- a/qiskit/test/base.py +++ b/qiskit/test/base.py @@ -221,6 +221,7 @@ def setUpClass(cls): "test.python.quantum_info.operators.channel.test_stinespring", "test.python.quantum_info.operators.symplectic.test_sparse_pauli_op", "test.python.quantum_info.operators.channel.test_ptm", + "importlib_metadata", ] for mod in allow_DeprecationWarning_modules: warnings.filterwarnings("default", category=DeprecationWarning, module=mod) From 2e1726cc26a36104161880dd7d107261663660c9 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 20 Sep 2021 07:56:45 -0400 Subject: [PATCH 22/37] Pin importlib-metadata The previous commit tried to fix the failures in CI around the deprecation warnings but while I'm unable to reproduce those locally with the warning ignore in place it still fails in CI. I expect there is a similar incompatibility around the 4 frequent importlib-metadata releases >=4.7.0 (2 of which were yanked) causing issues in the CI environment. Since things were working fine for the several months this PR was open prior to 4.7.0 this commit just pins the version in the constraints file to fix CI. --- constraints.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/constraints.txt b/constraints.txt index b0df01557586..eca8cf84de48 100644 --- a/constraints.txt +++ b/constraints.txt @@ -4,3 +4,4 @@ decorator==4.4.2 jax==0.2.13 jaxlib==0.1.67 networkx==2.5 +importlib-metadata==4.6.4 From 6b9d550a31b92bee8ef7218cc04534b5252570b8 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 21 Sep 2021 15:53:51 -0400 Subject: [PATCH 23/37] Add length units to docstring --- qiskit/transpiler/passes/synthesis/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 4ae89060efd1..6d958b7eda51 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -235,6 +235,8 @@ def supports_gate_lengths(self): 'cx': {(0, 1): 0.012012477900732316, (1, 0): 5.191111111111111e-07} } + Where the ``length`` value is in unites of seconds. + Do note that this dictionary might not be complete or could be empty as it depends on the target backend reporting gate lengths on every gate for each qubit. From db38b8dd3bdc59db0ab512ab4423d133772e1988 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 21 Sep 2021 16:24:09 -0400 Subject: [PATCH 24/37] Add min and max qubit abstract properties This commit adds 2 new required properties to the plugin class interface for min and max qubits. These enable a plugin author to specify the number of qubits the plugin supports and if the unitary to be synthesized is outside that range it will just fallback to using the ``default`` plugin. --- qiskit/transpiler/passes/synthesis/plugin.py | 24 +++++++++++++++++++ .../passes/synthesis/unitary_synthesis.py | 17 ++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 6d958b7eda51..76619c97fac6 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -164,6 +164,30 @@ class UnitarySynthesisPlugin(abc.ABC): This abstract class defines the interface for unitary synthesis plugins. """ + @property + @abc.abstractmethod + def max_qubits(self): + """Return the maximum number of qubits the unitary synthesis plugin supports. + + If the size of the unitary to be synthesized exceeds this value the + ``default`` plugin will be used. If there is no upper bound return + ``None`` and all unitaries ( ``> min_qubits`` if it's defined) will be + passed to this plugin when it's enabled. + """ + pass + + @property + @abc.abstractmethod + def min_qubits(self): + """Return the minimum number of qubits the unitary synthesis plugin supports. + + If the size of the unitary to be synthesized is below this value the + ``default`` plugin will be used. If there is no lower bound return + ``None`` and all unitaries ( ``< max_qubits`` if it's defined) will be + passed to this plugin when it's enabled. + """ + pass + @property @abc.abstractmethod def supports_basis_gates(self): diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 29a2545e9ed9..41f5d160dd60 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -152,6 +152,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: method = self.method if method not in self.plugins.ext_plugins: raise TranspilerError("Specified method: %s not found in plugin list" % method) + default_method = self.plugins.ext_plugins["default"].obj plugin_method = self.plugins.ext_plugins[method].obj if plugin_method.supports_coupling_map: dag_bit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} @@ -174,7 +175,13 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if plugin_method.supports_gate_errors: kwargs["gate_errors"] = _build_gate_errors(self._backend_props) unitary = node.op.to_matrix() - synth_dag = plugin_method.run(unitary, **kwargs) + n_qubits = len(node.qargs) + if (plugin_method.max_qubits is not None and n_qubits > plugin_method.max_qubits) or ( + plugin_method.min_qubits is not None and n_qubits < plugin_method.min_qubits + ): + synth_dag = default_method.run(unitary, **kwargs) + else: + synth_dag = plugin_method.run(unitary, **kwargs) if synth_dag: if isinstance(synth_dag, tuple): dag.substitute_node_with_dag(node, synth_dag[0], wires=synth_dag[1]) @@ -242,6 +249,14 @@ def supports_gate_lengths(self): def supports_gate_errors(self): return True + @property + def max_qubits(self): + return None + + @property + def min_qubits(self): + return None + def run(self, unitary, **options): basis_gates = options["basis_gates"] approximation_degree = options["approximation_degree"] From 3d9b7837e9b8c73275b850e214767e390844435e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 21 Sep 2021 18:16:50 -0400 Subject: [PATCH 25/37] Add option to automatically find basis for a run() call --- qiskit/transpiler/passes/synthesis/plugin.py | 29 ++++++++++ .../passes/synthesis/unitary_synthesis.py | 55 ++++++++++++++----- 2 files changed, 70 insertions(+), 14 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 76619c97fac6..bfb5213dcbe4 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -82,6 +82,18 @@ def supports_gate_lengths(self): def supports_gate_errors(self): return False + @property + def min_qubits(self): + return None + + @property + def max_qubits(self): + return None + + @property + def supported_basis(self): + return None + def run(self, unitary, **options): basis_gates = options['basis_gates'] dag_circuit = generate_dag_circuit_from_matrix(unitary, basis_gates) @@ -286,6 +298,23 @@ def supports_gate_errors(self): """ pass + @property + @abc.abstractmethod + def supported_basis(self): + """Returns a dictionary of supported basis for synthesis + + This is expected to return a dictionary where the key is a string + basis and the value is a list of gate names that the basis works in. + If the synthesis method doesn't support multiple basis this should + return ``None``. + + If a dictionary is returned by this method the run kwargs will be + passed a parameter ``matched_basis`` which contains a list of the + basis strings which match the target basis gate set for the + transpilation. + """ + pass + @abc.abstractmethod def run(self, unitary, **options): """Run synthesis for the given unitary matrix diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 41f5d160dd60..43431409a7cd 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -53,12 +53,31 @@ def _choose_euler_basis(basis_gates): basis_set = set(basis_gates or []) for basis, gates in one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES.items(): + if set(gates).issubset(basis_set): return basis return None +def _choose_basis(basis_gates, basis_dict=None): + """Find all matching basis in the specified basis gates.""" + if basis_gates is None: + basis_set = set() + else: + basis_set = set(basis_gates) + + if basis_dict is None: + basis_dict = one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES + + out_basis = [] + for basis, gates in basis_dict.items(): + if set(gates).issubset(basis_set): + return out_basis.append(basis) + + return out_basis + + class UnitarySynthesis(TransformationPass): """Synthesize gates according to their basis gates.""" @@ -156,24 +175,28 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: plugin_method = self.plugins.ext_plugins[method].obj if plugin_method.supports_coupling_map: dag_bit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} + kwargs = {} + if plugin_method.supports_basis_gates: + kwargs["basis_gates"] = self._basis_gates + if plugin_method.supports_approximation_degree: + kwargs["approximation_degree"] = self._approximation_degree + if plugin_method.supports_natural_direction: + kwargs["natural_direction"] = self._natural_direction + if plugin_method.supports_pulse_optimize: + kwargs["pulse_optimize"] = self._pulse_optimize + if plugin_method.supports_gate_lengths: + kwargs["gate_lengths"] = _build_gate_lengths(self._backend_props) + if plugin_method.supports_gate_errors: + kwargs["gate_errors"] = _build_gate_errors(self._backend_props) + supported_basis = plugin_method.supported_basis() + if supported_basis is not None: + kwargs["matched_basis"] = _choose_basis(self._basis_gates, supported_basis) + for node in dag.named_nodes(*self._synth_gates): - synth_dag = None - kwargs = {} - if plugin_method.supports_basis_gates: - kwargs["basis_gates"] = self._basis_gates if plugin_method.supports_coupling_map: kwargs["coupling_map"] = self._coupling_map kwargs["qubits"] = [dag_bit_indices[x] for x in node.qargs] - if plugin_method.supports_approximation_degree: - kwargs["approximation_degree"] = self._approximation_degree - if plugin_method.supports_natural_direction: - kwargs["natural_direction"] = self._natural_direction - if plugin_method.supports_pulse_optimize: - kwargs["pulse_optimize"] = self._pulse_optimize - if plugin_method.supports_gate_lengths: - kwargs["gate_lengths"] = _build_gate_lengths(self._backend_props) - if plugin_method.supports_gate_errors: - kwargs["gate_errors"] = _build_gate_errors(self._backend_props) + synth_dag = None unitary = node.op.to_matrix() n_qubits = len(node.qargs) if (plugin_method.max_qubits is not None and n_qubits > plugin_method.max_qubits) or ( @@ -257,6 +280,10 @@ def max_qubits(self): def min_qubits(self): return None + @property + def supported_basis(self): + return None + def run(self, unitary, **options): basis_gates = options["basis_gates"] approximation_degree = options["approximation_degree"] From e65c01a756a50a1c2d8c573bb1d829ca37312cf6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 23 Sep 2021 07:27:58 -0400 Subject: [PATCH 26/37] Fix typo --- qiskit/transpiler/passes/synthesis/unitary_synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 43431409a7cd..795021a33327 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -188,7 +188,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: kwargs["gate_lengths"] = _build_gate_lengths(self._backend_props) if plugin_method.supports_gate_errors: kwargs["gate_errors"] = _build_gate_errors(self._backend_props) - supported_basis = plugin_method.supported_basis() + supported_basis = plugin_method.supported_basis if supported_basis is not None: kwargs["matched_basis"] = _choose_basis(self._basis_gates, supported_basis) From 6f1eb05b5cf6b5c2ac83c9f32ca45ea506caea81 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 28 Sep 2021 11:50:29 -0400 Subject: [PATCH 27/37] Update gate errors docstring --- qiskit/transpiler/passes/synthesis/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index bfb5213dcbe4..caeddd52b233 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -294,7 +294,9 @@ def supports_gate_errors(self): Do note that this dictionary might not be complete or could be empty as it depends on the target backend reporting gate errors on every - gate for each qubit. + gate for each qubit. The gate error rates reported in ``gate_errors`` + are provided by the target device ``Backend`` object and the exact + meaning might be different depending on the backend. """ pass From cc9fe27fec20bd6257a01a067a6465024bd3ccc6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 28 Sep 2021 12:10:53 -0400 Subject: [PATCH 28/37] Remove approximation degree from plugin interface --- qiskit/transpiler/passes/synthesis/plugin.py | 28 +++++-------------- .../passes/synthesis/unitary_synthesis.py | 19 ++++++++----- 2 files changed, 19 insertions(+), 28 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index caeddd52b233..725ccc526764 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -43,11 +43,13 @@ and is expected to return a :class:`~qiskit.dagcircuit.DAGCircuit` object representing the synthesized circuit from that unitary matrix. Then to inform the Qiskit transpiler about what information is necessary for the pass there -are several required property methods that need to be implemented, -``supports_basis_gates``, ``supports_coupling_map``, and -``supports_approximation_degree`` which return either ``True`` or ``False`` -depending on whether the plugin supports and/or requires that input to perform -synthesis. An example plugin class would look something like:: +are several required property methods that need to be implemented such as +``supports_basis_gates`` and ``supports_coupling_map`` depending on whether the +plugin supports and/or requires that input to perform synthesis. For the full +details refer to the +:class:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin` +documentation for all the required fields. An example plugin class would look +something like:: from qiskit.transpiler.passes.synthesis import plugin from qiskit_plugin_pkg.synthesis import generate_dag_circuit_from_matrix @@ -62,10 +64,6 @@ def supports_basis_gates(self): def supports_coupling_map(self): return False - @property - def supports_approximation_degree(self): - return False - @property def supports_natural_direction(self): return False @@ -224,18 +222,6 @@ def supports_coupling_map(self): """ pass - @property - @abc.abstractmethod - def supports_approximation_degree(self): - """Return whether the plugin supports taking ``approximation_degree`` - - If this returns ``True`` the plugin's ``run()`` method will receive - a ``approximation_degree`` kwarg with a float value between 0 and 1 - representing the closeness of the approximation to use (0: lowest, - 1: highest). - """ - pass - @property @abc.abstractmethod def supports_natural_direction(self): diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 795021a33327..aac0f4b21c76 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -178,8 +178,6 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: kwargs = {} if plugin_method.supports_basis_gates: kwargs["basis_gates"] = self._basis_gates - if plugin_method.supports_approximation_degree: - kwargs["approximation_degree"] = self._approximation_degree if plugin_method.supports_natural_direction: kwargs["natural_direction"] = self._natural_direction if plugin_method.supports_pulse_optimize: @@ -192,6 +190,13 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: if supported_basis is not None: kwargs["matched_basis"] = _choose_basis(self._basis_gates, supported_basis) + # Handle approximation degree as a special case for backwards compatibility, it's + # not part of the plugin interface and only something needed for the default + # pass. + default_method._approximation_degree = self._approximation_degree + if method == "default": + plugin_method._approximation_degree = self._approximation_degree + for node in dag.named_nodes(*self._synth_gates): if plugin_method.supports_coupling_map: kwargs["coupling_map"] = self._coupling_map @@ -252,10 +257,6 @@ def supports_basis_gates(self): def supports_coupling_map(self): return True - @property - def supports_approximation_degree(self): - return True - @property def supports_natural_direction(self): return True @@ -285,8 +286,12 @@ def supported_basis(self): return None def run(self, unitary, **options): + # Approximation degree is set directly as an attribute on the + # instance by the UnitarySynthesis pass here as it's not part of + # plugin interface. However if for some reason it's not set assume + # it's 1. + approximation_degree = getattr(self, "_approximation_degree", 1) basis_gates = options["basis_gates"] - approximation_degree = options["approximation_degree"] coupling_map = options["coupling_map"] natural_direction = options["natural_direction"] pulse_optimize = options["pulse_optimize"] From d57351e5396cce560093336b239c9a2438fa5475 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 28 Sep 2021 16:35:08 -0400 Subject: [PATCH 29/37] Fix docstring typos Co-authored-by: Eric Peterson --- qiskit/transpiler/passes/synthesis/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 725ccc526764..4905711203a8 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -257,7 +257,7 @@ def supports_gate_lengths(self): 'cx': {(0, 1): 0.012012477900732316, (1, 0): 5.191111111111111e-07} } - Where the ``length`` value is in unites of seconds. + where the ``length`` value is in units of seconds. Do note that this dictionary might not be complete or could be empty as it depends on the target backend reporting gate lengths on every From 0e06b7d908766bb4312a6351fc7f112af8c4ef67 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 29 Sep 2021 13:30:01 -0400 Subject: [PATCH 30/37] Apply suggestions from code review Co-authored-by: Jake Lishman --- qiskit/transpiler/passes/synthesis/plugin.py | 22 +++++++++----------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 4905711203a8..7b5634de41b5 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -169,7 +169,7 @@ def run(self, unitary, **options): class UnitarySynthesisPlugin(abc.ABC): - """Abstract plugin Synthesis plugin class + """Abstract unitary synthesis plugin class This abstract class defines the interface for unitary synthesis plugins. """ @@ -181,7 +181,7 @@ def max_qubits(self): If the size of the unitary to be synthesized exceeds this value the ``default`` plugin will be used. If there is no upper bound return - ``None`` and all unitaries ( ``> min_qubits`` if it's defined) will be + ``None`` and all unitaries (``>= min_qubits`` if it's defined) will be passed to this plugin when it's enabled. """ pass @@ -193,7 +193,7 @@ def min_qubits(self): If the size of the unitary to be synthesized is below this value the ``default`` plugin will be used. If there is no lower bound return - ``None`` and all unitaries ( ``< max_qubits`` if it's defined) will be + ``None`` and all unitaries (``<= max_qubits`` if it's defined) will be passed to this plugin when it's enabled. """ pass @@ -324,11 +324,11 @@ def run(self, unitary, **options): a float for the approximation value will be passed. Returns: - DAGCircuit: The dag circuit representation of the unitary. Alternatively, - you can return a tuple of the form ``(dag, wires)`` where ``dag`` is the dag - circuit representation of the circuit representation of the unitary - and ``wires`` is the mapping wires to use for - :meth:`qiskit.dagcircuit.DAGCircuit.substitute_node_with_dag`. + DAGCircuit: The dag circuit representation of the unitary. Alternatively, you can return + a tuple of the form ``(dag, wires)`` where ``dag`` is the dag circuit representation of + the circuit representation of the unitary and ``wires`` is the mapping wires to use for + :meth:`qiskit.dagcircuit.DAGCircuit.substitute_node_with_dag`. + """ pass @@ -350,10 +350,8 @@ def unitary_synthesis_plugin_names(): """Return a list of installed unitary synthesis plugin names Returns: - list: A list of the installed unitary synthesis plugin names. The - plugin names are valid values for the - :func:`~qiskit.compiler.transpile` kwarg - ``unitary_synthesis_method``. + list: A list of the installed unitary synthesis plugin names. The plugin names are valid + values for the :func:`~qiskit.compiler.transpile` kwarg ``unitary_synthesis_method``. """ plugins = UnitarySynthesisPluginManager() return plugins.ext_plugins.names() From d9f6884df06bf1974da5305287ef5fa2d485dfc3 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 29 Sep 2021 13:51:19 -0400 Subject: [PATCH 31/37] Update qiskit/transpiler/passes/synthesis/unitary_synthesis.py Co-authored-by: Jake Lishman --- qiskit/transpiler/passes/synthesis/unitary_synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index aac0f4b21c76..df0e20217634 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -163,7 +163,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: TranspilerError: if a 'method' was specified for the class and is not found in the installed plugins list. The list of installed plugins can be queried with - :func:`~qiskit.transpiler.passes.synthesis.plugins.unitary_synthesis_plugin_names` + :func:`~qiskit.transpiler.passes.synthesis.plugin.unitary_synthesis_plugin_names` """ if not self.method: method = "default" From 5d8539ac37300473270b470574aa8879c975e2f9 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 29 Sep 2021 14:26:05 -0400 Subject: [PATCH 32/37] Update docstrings and naming and default to 'default' plugin in kwarg This commit updates several aspects of the documentation to make it more clear how to use the plugin interface. It also updates the naming of supported_basis to be supported_bases as it's actually plural. The last change in this commit is making the default kwarg value for the unitary synthesis method default to 'default' (for the default built-in method) instead of None. This simplifies the logic in the unitary synthesis pass. --- qiskit/compiler/transpiler.py | 2 +- qiskit/transpiler/passes/synthesis/plugin.py | 33 ++++++++++--------- .../passes/synthesis/unitary_synthesis.py | 28 +++++++--------- qiskit/transpiler/passmanager_config.py | 2 +- 4 files changed, 31 insertions(+), 34 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 7c1d5fc8ace2..8f8a2e2f621e 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -65,7 +65,7 @@ def transpile( pass_manager: Optional[PassManager] = None, callback: Optional[Callable[[BasePass, DAGCircuit, float, PropertySet, int], Any]] = None, output_name: Optional[Union[str, List[str]]] = None, - unitary_synthesis_method: Optional[str] = None, + unitary_synthesis_method: str = "default", ) -> Union[QuantumCircuit, List[QuantumCircuit]]: """Transpile one or more circuits, according to some desired transpilation targets. diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 7b5634de41b5..2f3501331e28 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -288,18 +288,23 @@ def supports_gate_errors(self): @property @abc.abstractmethod - def supported_basis(self): + def supported_bases(self): """Returns a dictionary of supported basis for synthesis This is expected to return a dictionary where the key is a string basis and the value is a list of gate names that the basis works in. If the synthesis method doesn't support multiple basis this should - return ``None``. + return ``None``. For example:: + + { + "XZX": ["rz", "rx"], + "XYX": ["rx", "ry"], + } If a dictionary is returned by this method the run kwargs will be passed a parameter ``matched_basis`` which contains a list of the - basis strings which match the target basis gate set for the - transpilation. + basis strings (ie keys in the dictionary) which match the target basis + gate set for the transpilation. """ pass @@ -311,23 +316,19 @@ def run(self, unitary, **options): unitary (numpy.ndarray): The unitary matrix to synthesize to a :class:`~qiskit.dagcircuit.DAGCircuit` object options: The optional kwargs that are passed based on the output - of :meth:`supports_basis_gates`, :meth:`supports_coupling_map`, - and :meth:`supports_approximation_degree`. If - :meth:`supports_coupling_map` returns ``True`` a kwarg - ``coupling_map`` will be passed either containing ``None`` (if - there is no coupling map) or a - :class:`~qiskit.transpiler.CouplingMap` object. If - :meth:`supports_basis_gates` returns ``True`` then a kwarg - ``basis_gates`` will the list of basis gate names will be - passed. Finally if :meth:`supports_approximation_degree` - returns ``True`` a kwarg ``approximation_degree`` containing - a float for the approximation value will be passed. + the ``support_*`` methods on the class. Refer to the + documentation for these methods on + :class:`~qiskit.transpiler.passes.synthesis.plugin.UnitarySynthesisPlugin` + to see what the keys and values are. Returns: DAGCircuit: The dag circuit representation of the unitary. Alternatively, you can return a tuple of the form ``(dag, wires)`` where ``dag`` is the dag circuit representation of the circuit representation of the unitary and ``wires`` is the mapping wires to use for - :meth:`qiskit.dagcircuit.DAGCircuit.substitute_node_with_dag`. + :meth:`qiskit.dagcircuit.DAGCircuit.substitute_node_with_dag`. If you return a tuple + and ``wires`` is ``None`` this will behave just as if only a + :class:`~qiskit.dagcircuit.DAGCircuit` was returned. Additionally if this returns + ``None`` no substitution will be made. """ pass diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index df0e20217634..9a90d58473a3 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -60,8 +60,8 @@ def _choose_euler_basis(basis_gates): return None -def _choose_basis(basis_gates, basis_dict=None): - """Find all matching basis in the specified basis gates.""" +def _choose_bases(basis_gates, basis_dict=None): + """Find the matching basis string keys from the list of basis gates from the backend.""" if basis_gates is None: basis_set = set() else: @@ -90,7 +90,7 @@ def __init__( pulse_optimize: Union[bool, None] = None, natural_direction: Union[bool, None] = None, synth_gates: Union[List[str], None] = None, - method: Optional[str] = None, + method: str = "default", ): """Synthesize unitaries over some basis gates. @@ -165,14 +165,10 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: plugins can be queried with :func:`~qiskit.transpiler.passes.synthesis.plugin.unitary_synthesis_plugin_names` """ - if not self.method: - method = "default" - else: - method = self.method - if method not in self.plugins.ext_plugins: - raise TranspilerError("Specified method: %s not found in plugin list" % method) + if self.method not in self.plugins.ext_plugins: + raise TranspilerError("Specified method: %s not found in plugin list" % self.method) default_method = self.plugins.ext_plugins["default"].obj - plugin_method = self.plugins.ext_plugins[method].obj + plugin_method = self.plugins.ext_plugins[self.method].obj if plugin_method.supports_coupling_map: dag_bit_indices = {bit: idx for idx, bit in enumerate(dag.qubits)} kwargs = {} @@ -186,15 +182,15 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: kwargs["gate_lengths"] = _build_gate_lengths(self._backend_props) if plugin_method.supports_gate_errors: kwargs["gate_errors"] = _build_gate_errors(self._backend_props) - supported_basis = plugin_method.supported_basis - if supported_basis is not None: - kwargs["matched_basis"] = _choose_basis(self._basis_gates, supported_basis) + supported_bases = plugin_method.supported_bases + if supported_bases is not None: + kwargs["matched_basis"] = _choose_bases(self._basis_gates, supported_bases) # Handle approximation degree as a special case for backwards compatibility, it's # not part of the plugin interface and only something needed for the default # pass. default_method._approximation_degree = self._approximation_degree - if method == "default": + if self.method == "default": plugin_method._approximation_degree = self._approximation_degree for node in dag.named_nodes(*self._synth_gates): @@ -210,7 +206,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: synth_dag = default_method.run(unitary, **kwargs) else: synth_dag = plugin_method.run(unitary, **kwargs) - if synth_dag: + if synth_dag is not None: if isinstance(synth_dag, tuple): dag.substitute_node_with_dag(node, synth_dag[0], wires=synth_dag[1]) else: @@ -282,7 +278,7 @@ def min_qubits(self): return None @property - def supported_basis(self): + def supported_bases(self): return None def run(self, unitary, **options): diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index ea5728260787..1da250f71e1a 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -31,7 +31,7 @@ def __init__( approximation_degree=None, seed_transpiler=None, timing_constraints=None, - unitary_synthesis_method=None, + unitary_synthesis_method="default", ): """Initialize a PassManagerConfig object From aa2af0fd0f84d0181118d433aa8b4fc77df8aac0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 29 Sep 2021 14:46:32 -0400 Subject: [PATCH 33/37] Make coupling_map run() kwarg a tuple --- qiskit/transpiler/passes/synthesis/plugin.py | 12 +++++++----- .../transpiler/passes/synthesis/unitary_synthesis.py | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 2f3501331e28..d5fb1858658d 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -214,11 +214,13 @@ def supports_coupling_map(self): """Return whether the plugin supports taking ``coupling_map`` If this returns ``True`` the plugin's ``run()`` method will receive - two kwargs ``coupling_map`` and ``qubits``. The ``coupling_map`` kwarg - will be a :class:`~qiskit.transpiler.CouplingMap` object representing - the qubit connectivity of the target backend. The ``qubits`` kwarg will - receive a list of integers that represent the qubit indices in the - coupling map that unitary is on. + one kwarg ``coupling_map``. The ``coupling_map`` kwarg will be set to a + tuple with the first element being a + :class:`~qiskit.transpiler.CouplingMap` object representing the qubit + connectivity of the target backend, the second element will be a list + of integers that represent the qubit indices in the coupling map that + unitary is on. Note that if the target backend doesn't have a coupling + map set the ``coupling_map`` kwarg's value will be ``None``. """ pass diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index 9a90d58473a3..cd930be2d401 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -13,7 +13,7 @@ """Synthesize UnitaryGates.""" from math import pi, inf -from typing import List, Union, Optional +from typing import List, Union from copy import deepcopy from qiskit.converters import circuit_to_dag @@ -195,8 +195,10 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: for node in dag.named_nodes(*self._synth_gates): if plugin_method.supports_coupling_map: - kwargs["coupling_map"] = self._coupling_map - kwargs["qubits"] = [dag_bit_indices[x] for x in node.qargs] + kwargs["coupling_map"] = ( + self._coupling_map, + [dag_bit_indices[x] for x in node.qargs], + ) synth_dag = None unitary = node.op.to_matrix() n_qubits = len(node.qargs) @@ -288,12 +290,12 @@ def run(self, unitary, **options): # it's 1. approximation_degree = getattr(self, "_approximation_degree", 1) basis_gates = options["basis_gates"] - coupling_map = options["coupling_map"] + coupling_map = options["coupling_map"][0] natural_direction = options["natural_direction"] pulse_optimize = options["pulse_optimize"] gate_lengths = options["gate_lengths"] gate_errors = options["gate_errors"] - qubits = options["qubits"] + qubits = options["coupling_map"][1] euler_basis = _choose_euler_basis(basis_gates) kak_gate = _choose_kak_gate(basis_gates) From ba6a4a0ca67e6a43c85f13d0975509b7f39c3272 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 29 Sep 2021 14:59:17 -0400 Subject: [PATCH 34/37] Add comment on why we don't use a shared global instance --- qiskit/transpiler/passes/synthesis/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index d5fb1858658d..0d14ec88f722 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -356,5 +356,7 @@ def unitary_synthesis_plugin_names(): list: A list of the installed unitary synthesis plugin names. The plugin names are valid values for the :func:`~qiskit.compiler.transpile` kwarg ``unitary_synthesis_method``. """ + # NOTE: This is not a shared global instance to avoid an import cycle + # at load time for the default plugin. plugins = UnitarySynthesisPluginManager() return plugins.ext_plugins.names() From 66bb76381f39518444318808545ef14fe09f059a Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 30 Sep 2021 10:46:12 +0100 Subject: [PATCH 35/37] Fix straggler basis->bases typos --- qiskit/transpiler/passes/synthesis/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 0d14ec88f722..5e2eae7ec47c 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -89,7 +89,7 @@ def max_qubits(self): return None @property - def supported_basis(self): + def supported_bases(self): return None def run(self, unitary, **options): @@ -291,11 +291,11 @@ def supports_gate_errors(self): @property @abc.abstractmethod def supported_bases(self): - """Returns a dictionary of supported basis for synthesis + """Returns a dictionary of supported bases for synthesis This is expected to return a dictionary where the key is a string basis and the value is a list of gate names that the basis works in. - If the synthesis method doesn't support multiple basis this should + If the synthesis method doesn't support multiple bases this should return ``None``. For example:: { @@ -305,7 +305,7 @@ def supported_bases(self): If a dictionary is returned by this method the run kwargs will be passed a parameter ``matched_basis`` which contains a list of the - basis strings (ie keys in the dictionary) which match the target basis + basis strings (i.e. keys in the dictionary) which match the target basis gate set for the transpilation. """ pass From 7240065e0cdee42f4e769ace51acca84edd780f6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 30 Sep 2021 12:40:34 -0400 Subject: [PATCH 36/37] Apply suggestions from code review Co-authored-by: Jake Lishman --- qiskit/transpiler/passes/synthesis/plugin.py | 2 +- qiskit/transpiler/passes/synthesis/unitary_synthesis.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 5e2eae7ec47c..c2c0c7c30adb 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -220,7 +220,7 @@ def supports_coupling_map(self): connectivity of the target backend, the second element will be a list of integers that represent the qubit indices in the coupling map that unitary is on. Note that if the target backend doesn't have a coupling - map set the ``coupling_map`` kwarg's value will be ``None``. + map set, the ``coupling_map`` kwarg's value will be ``(None, qubit_indices)``. """ pass diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index cd930be2d401..2f8739488e10 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -73,7 +73,7 @@ def _choose_bases(basis_gates, basis_dict=None): out_basis = [] for basis, gates in basis_dict.items(): if set(gates).issubset(basis_set): - return out_basis.append(basis) + out_basis.append(basis) return out_basis From 9ca591a6bc6cd32101775edd0f20bd4546770c64 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 30 Sep 2021 12:47:33 -0400 Subject: [PATCH 37/37] Document supported_bases handling of no matching basis --- qiskit/transpiler/passes/synthesis/plugin.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index c2c0c7c30adb..8d8fdc585c46 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -306,7 +306,10 @@ def supported_bases(self): If a dictionary is returned by this method the run kwargs will be passed a parameter ``matched_basis`` which contains a list of the basis strings (i.e. keys in the dictionary) which match the target basis - gate set for the transpilation. + gate set for the transpilation. If no entry in the dictionary matches + the target basis gate set then the ``matched_basis`` kwarg will be set + to an empty list, and a plugin can choose how to deal with the target + basis gate set not matching the plugin's capabilities. """ pass