Skip to content

Commit

Permalink
Add unitary synthesis plugin interface
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mtreinish committed Mar 31, 2021
1 parent 6226c05 commit ad04aaa
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 42 deletions.
37 changes: 25 additions & 12 deletions qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ def transpile(circuits: Union[QuantumCircuit, List[QuantumCircuit]],
pass_manager: Optional[PassManager] = None,
callback: Optional[Callable[[BasePass, DAGCircuit, float,
PropertySet, int], Any]] = None,
output_name: Optional[Union[str, List[str]]] = None) -> Union[QuantumCircuit,
List[QuantumCircuit]]:
output_name: Optional[Union[str, List[str]]] = None,
unitary_synthesis_method: Optional[str] = None) -> Union[QuantumCircuit,
List[QuantumCircuit]]:
"""Transpile one or more circuits, according to some desired transpilation targets.
All arguments may be given as either a singleton or list. In case of a list,
Expand Down Expand Up @@ -215,6 +216,7 @@ def callback_func(**kwargs):
routing_method=routing_method,
translation_method=translation_method,
approximation_degree=approximation_degree,
unitary_synthesis_method=unitary_synthesis_method,
backend=backend)

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

_check_circuits_coupling_map(circuits, transpile_args, backend)

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

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


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


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

import abc

import stevedore

class UnitarySynthesisPlugin(abc.ABC):
"""Abstract plugin Synthesis plugin class
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)
84 changes: 61 additions & 23 deletions qiskit/transpiler/passes/synthesis/unitary_synthesis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
from qiskit.converters import circuit_to_dag
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.dagcircuit.dagcircuit import DAGCircuit
from qiskit.exceptions import QiskitError
from qiskit.extensions.quantum_initializer import isometry
from qiskit.quantum_info.synthesis import one_qubit_decompose
from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitBasisDecomposer
from qiskit.circuit.library.standard_gates import (iSwapGate, CXGate, CZGate,
RXXGate, ECRGate)
from qiskit.transpiler.passes.synthesis import plugin


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

def __init__(self,
basis_gates: List[str],
approximation_degree: float = 1):
approximation_degree: float = 1,
coupling_map = None,
method: str = None):
"""
Synthesize unitaries over some basis gates.
Expand All @@ -75,6 +79,9 @@ def __init__(self,
super().__init__()
self._basis_gates = basis_gates
self._approximation_degree = approximation_degree
self.method = method
self._coupling_map = coupling_map
self.plugins = plugin.UnitarySynthesisPluginManager()

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


class DefaultUnitarySynthesis(plugin.UnitarySynthesisPlugin):

def supports_basis_gates(self):
return True

def supports_coupling_map(self):
return False

def supports_approximation_degree(self):
return True

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

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

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

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

dag.substitute_node_with_dag(node, synth_dag)

return dag
if unitary.shape == (2, 2):
if decomposer1q is None:
return None
synth_dag = circuit_to_dag(decomposer1q._decompose(unitary))
elif unitary.shape == (4, 4):
if decomposer2q is None:
return None
synth_dag = circuit_to_dag(decomposer2q(unitary,
basis_fidelity=approximation_degree))
else:
synth_dag = circuit_to_dag(
isometry.Isometry(unitary.op.to_matrix(), 0, 0).definition)
return synth_dag
7 changes: 6 additions & 1 deletion qiskit/transpiler/passmanager_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ def __init__(self,
instruction_durations=None,
backend_properties=None,
approximation_degree=None,
seed_transpiler=None):
seed_transpiler=None,
unitary_synthesis_method=None):
"""Initialize a PassManagerConfig object
Args:
Expand All @@ -53,6 +54,9 @@ def __init__(self,
(1.0=no approximation, 0.0=maximal approximation)
seed_transpiler (int): Sets random seed for the stochastic parts of
the transpiler.
unitary_synthesis_method (str): The string method to use for the
:class:`~qiskit.transpiler.passes.UnitarySynthesis` pass. Will
search installed plugins for a valid method.
"""
self.initial_layout = initial_layout
self.basis_gates = basis_gates
Expand All @@ -65,3 +69,4 @@ def __init__(self,
self.backend_properties = backend_properties
self.approximation_degree = approximation_degree
self.seed_transpiler = seed_transpiler
self.unitary_synthesis_method = unitary_synthesis_method
4 changes: 3 additions & 1 deletion qiskit/transpiler/preset_passmanagers/level0.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager:
seed_transpiler = pass_manager_config.seed_transpiler
backend_properties = pass_manager_config.backend_properties
approximation_degree = pass_manager_config.approximation_degree
unitary_synthesis_method = pass_manager_config.unitary_synthesis_method

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

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

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

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

0 comments on commit ad04aaa

Please sign in to comment.