From 8eea4cfb49bd84a4bd2b20c7d344af64960611c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 17 Oct 2024 10:09:41 +0200 Subject: [PATCH 01/25] First attempt at supporting a virtual target (FakeTarget) with no basis gates in the preset pass managers. --- .../passes/basis/basis_translator.py | 4 + qiskit/transpiler/passes/layout/vf2_utils.py | 2 +- .../preset_passmanagers/builtin_plugins.py | 182 +++++++++++------- .../transpiler/preset_passmanagers/common.py | 5 + .../generate_preset_pass_manager.py | 9 +- .../transpiler/preset_passmanagers/level0.py | 18 +- .../transpiler/preset_passmanagers/level1.py | 18 +- .../transpiler/preset_passmanagers/level2.py | 19 +- .../transpiler/preset_passmanagers/level3.py | 10 +- qiskit/transpiler/target.py | 110 +++++++---- test/python/compiler/test_transpiler.py | 3 +- .../transpiler/test_preset_passmanagers.py | 18 +- test/utils/base.py | 11 ++ 13 files changed, 275 insertions(+), 134 deletions(-) diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index a1d3e7f0d39c..e7b9cbe05746 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -124,6 +124,8 @@ def __init__(self, equivalence_library, target_basis, target=None, min_qubits=0) for qarg in self._target[gate]: self._qargs_with_non_global_operation[qarg].add(gate) + print("inside pas: target:", target, "target basis", target_basis) + def run(self, dag): """Translate an input DAGCircuit to the target basis. @@ -153,6 +155,8 @@ def run(self, dag): source_basis, qargs_local_source_basis = self._extract_basis_target(dag, qarg_indices) target_basis = set(target_basis).union(basic_instrs) + print("final target basis", target_basis) + # If the source basis is a subset of the target basis and we have no circuit # instructions on qargs that have non-global operations there is nothing to # translate and we can exit early. diff --git a/qiskit/transpiler/passes/layout/vf2_utils.py b/qiskit/transpiler/passes/layout/vf2_utils.py index 037ccc37155d..dcf8a5e2a80f 100644 --- a/qiskit/transpiler/passes/layout/vf2_utils.py +++ b/qiskit/transpiler/passes/layout/vf2_utils.py @@ -201,7 +201,7 @@ def build_average_error_map(target, properties, coupling_map): # running the fallback heuristic if not built and target is not None and coupling_map is None: coupling_map = target.build_coupling_map() - if not built and coupling_map is not None: + if not built and coupling_map is not None and num_qubits is not None: for qubit in range(num_qubits): avg_map.add_error( (qubit, qubit), diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 68e266a09e70..fba1f26b71f9 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -14,6 +14,7 @@ import os +from qiskit.transpiler.target import FakeTarget from qiskit.transpiler.passes.optimization.split_2q_unitaries import Split2QUnitaries from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.exceptions import TranspilerError @@ -94,6 +95,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana or ( pass_manager_config.target is not None and pass_manager_config.target.build_coupling_map() is not None + and not isinstance(pass_manager_config.target, FakeTarget) ) ): init = common.generate_unroll_3q( @@ -113,6 +115,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana or ( pass_manager_config.target is not None and pass_manager_config.target.build_coupling_map() is not None + and not isinstance(pass_manager_config.target, FakeTarget) ) ): init += common.generate_unroll_3q( @@ -144,15 +147,17 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) elif optimization_level in {2, 3}: - init = common.generate_unroll_3q( - pass_manager_config.target, - pass_manager_config.basis_gates, - pass_manager_config.approximation_degree, - pass_manager_config.unitary_synthesis_method, - pass_manager_config.unitary_synthesis_plugin_config, - pass_manager_config.hls_config, - pass_manager_config.qubits_initially_zero, - ) + init = PassManager() + if not isinstance(pass_manager_config.target, FakeTarget): + init = common.generate_unroll_3q( + pass_manager_config.target, + pass_manager_config.basis_gates, + pass_manager_config.approximation_degree, + pass_manager_config.unitary_synthesis_method, + pass_manager_config.unitary_synthesis_plugin_config, + pass_manager_config.hls_config, + pass_manager_config.qubits_initially_zero, + ) if pass_manager_config.routing_method != "none": init.append(ElidePermutations()) init.append(RemoveDiagonalGatesBeforeMeasure()) @@ -176,7 +181,12 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) init.append(CommutativeCancellation()) init.append(Collect2qBlocks()) - init.append(ConsolidateBlocks()) + if ( + pass_manager_config.basis_gates is not None + and len(pass_manager_config.basis_gates) > 0 + ): + print("HERE??", pass_manager_config.basis_gates) + init.append(ConsolidateBlocks()) # If approximation degree is None that indicates a request to approximate up to the # error rates in the target. However, in the init stage we don't yet know the target # qubits being used to figure out the fidelity so just use the default fidelity parameter @@ -209,7 +219,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana class UnitarySynthesisPassManager(PassManagerStagePlugin): - """Plugin class for translation stage with :class:`~.BasisTranslator`""" + """Plugin class for translation stage with :class:`~.UnitarySynthesis`""" def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: return common.generate_translation_passmanager( @@ -557,10 +567,18 @@ def _opt_control(property_set): ) if optimization_level == 1: # Steps for optimization level 1 - _opt = [ - Optimize1qGatesDecomposition( - basis=pass_manager_config.basis_gates, target=pass_manager_config.target - ), + if ( + pass_manager_config.basis_gates is not None + and len(pass_manager_config.basis_gates) > 0 + ): + _opt = [ + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ) + ] + else: + _opt = [] + _opt += [ InverseCancellation( [ CXGate(), @@ -580,60 +598,29 @@ def _opt_control(property_set): ] elif optimization_level == 2: - _opt = [ - Optimize1qGatesDecomposition( - basis=pass_manager_config.basis_gates, target=pass_manager_config.target - ), + if ( + pass_manager_config.basis_gates is not None + and len(pass_manager_config.basis_gates) > 0 + ): + _opt = [ + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ) + ] + else: + _opt = [] + _opt += [ CommutativeCancellation(target=pass_manager_config.target), ] elif optimization_level == 3: # Steps for optimization level 3 - _opt = [ - Collect2qBlocks(), - ConsolidateBlocks( - basis_gates=pass_manager_config.basis_gates, - target=pass_manager_config.target, - approximation_degree=pass_manager_config.approximation_degree, - ), - UnitarySynthesis( - pass_manager_config.basis_gates, - approximation_degree=pass_manager_config.approximation_degree, - coupling_map=pass_manager_config.coupling_map, - backend_props=pass_manager_config.backend_properties, - method=pass_manager_config.unitary_synthesis_method, - plugin_config=pass_manager_config.unitary_synthesis_plugin_config, - target=pass_manager_config.target, - ), - Optimize1qGatesDecomposition( - basis=pass_manager_config.basis_gates, target=pass_manager_config.target - ), - CommutativeCancellation(target=pass_manager_config.target), - ] - - def _opt_control(property_set): - return not property_set["optimization_loop_minimum_point"] - - else: - raise TranspilerError(f"Invalid optimization_level: {optimization_level}") - - unroll = translation.to_flow_controller() + _opt = [Collect2qBlocks()] - # Build nested Flow controllers - def _unroll_condition(property_set): - return not property_set["all_gates_in_basis"] - - # Check if any gate is not in the basis, and if so, run unroll passes - _unroll_if_out_of_basis = [ - GatesInBasis(pass_manager_config.basis_gates, target=pass_manager_config.target), - ConditionalController(unroll, condition=_unroll_condition), - ] - - if optimization_level == 3: - optimization.append(_minimum_point_check) - elif optimization_level == 2: - optimization.append( - [ - Collect2qBlocks(), + if ( + pass_manager_config.basis_gates is not None + and len(pass_manager_config.basis_gates) > 0 + ): + _opt += [ ConsolidateBlocks( basis_gates=pass_manager_config.basis_gates, target=pass_manager_config.target, @@ -648,8 +635,71 @@ def _unroll_condition(property_set): plugin_config=pass_manager_config.unitary_synthesis_plugin_config, target=pass_manager_config.target, ), + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ), ] - ) + + _opt += [CommutativeCancellation(target=pass_manager_config.target)] + + def _opt_control(property_set): + return not property_set["optimization_loop_minimum_point"] + + else: + raise TranspilerError(f"Invalid optimization_level: {optimization_level}") + + if ( + pass_manager_config.basis_gates is not None + and len(pass_manager_config.basis_gates) > 0 + ): + unroll = translation.to_flow_controller() + + # Build nested Flow controllers + def _unroll_condition(property_set): + return not property_set["all_gates_in_basis"] + + # Check if any gate is not in the basis, and if so, run unroll passes + _unroll_if_out_of_basis = [ + GatesInBasis( + pass_manager_config.basis_gates, target=pass_manager_config.target + ), + ConditionalController(unroll, condition=_unroll_condition), + ] + else: + _unroll_if_out_of_basis = [] + + if optimization_level == 3: + optimization.append(_minimum_point_check) + elif optimization_level == 2: + if ( + pass_manager_config.basis_gates is not None + and len(pass_manager_config.basis_gates) > 0 + ): + optimization.append( + [ + Collect2qBlocks(), + ConsolidateBlocks( + basis_gates=pass_manager_config.basis_gates, + target=pass_manager_config.target, + approximation_degree=pass_manager_config.approximation_degree, + ), + UnitarySynthesis( + pass_manager_config.basis_gates, + approximation_degree=pass_manager_config.approximation_degree, + coupling_map=pass_manager_config.coupling_map, + backend_props=pass_manager_config.backend_properties, + method=pass_manager_config.unitary_synthesis_method, + plugin_config=pass_manager_config.unitary_synthesis_plugin_config, + target=pass_manager_config.target, + ), + ] + ) + else: + optimization.append( + [ + Collect2qBlocks(), + ] + ) optimization.append(_depth_check + _size_check) else: optimization.append(_depth_check + _size_check) @@ -659,6 +709,8 @@ def _unroll_condition(property_set): else _opt + _unroll_if_out_of_basis + _depth_check + _size_check ) optimization.append(DoWhileController(opt_loop, do_while=_opt_control)) + print("opt", optimization._tasks) + return optimization else: return None diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index f01639ed115e..8b2a9137075c 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -453,6 +453,11 @@ def generate_translation_passmanager( Raises: TranspilerError: If the ``method`` kwarg is not a valid value """ + if basis_gates is None and ( + target is None or (target is not None and len(target.operation_names) == 0) + ): + return PassManager([]) + if method == "translator": unroll = [ # Use unitary synthesis for basis aware decomposition of diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 830618845352..8a73ab856541 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -283,7 +283,8 @@ def generate_preset_pass_manager( _skip_target = ( target is None and backend is None - and (basis_gates is None or coupling_map is None or instruction_durations is not None) + # and (basis_gates is None or coupling_map is None or instruction_durations is not None) + and (instruction_durations is not None) ) # Resolve loose constraints case-by-case against backend constraints. @@ -344,7 +345,7 @@ def generate_preset_pass_manager( timing_constraints = target.timing_constraints() if backend_properties is None: with warnings.catch_warnings(): - # TODO this approach (target-to-properties) is going to be removed soon (1.3) in favor + # TODO: this approach (target-to-properties) is going to be removed soon (1.3) in favor # of backend-to-target approach # https://github.com/Qiskit/qiskit/pull/12850 warnings.filterwarnings( @@ -360,6 +361,7 @@ def generate_preset_pass_manager( approximation_degree = _parse_approximation_degree(approximation_degree) seed_transpiler = _parse_seed_transpiler(seed_transpiler) + print("target", target) pm_options = { "target": target, "basis_gates": basis_gates, @@ -398,6 +400,8 @@ def generate_preset_pass_manager( pm = level_3_pass_manager(pm_config) else: raise ValueError(f"Invalid optimization level {optimization_level}") + + # print("all", pm._tasks) return pm @@ -481,6 +485,7 @@ def _parse_coupling_map(coupling_map, backend): if isinstance(coupling_map, list) and all( isinstance(i, list) and len(i) == 2 for i in coupling_map ): + print("here") return CouplingMap(coupling_map) else: raise TranspilerError( diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 58381b3db7a8..ca74f7e34de5 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -69,9 +69,12 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout = None routing = None - translation = plugin_manager.get_passmanager_stage( - "translation", translation_method, pass_manager_config, optimization_level=0 - ) + if basis_gates: + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=0 + ) + else: + translation = None if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) @@ -97,9 +100,12 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa init = plugin_manager.get_passmanager_stage( "init", init_method, pass_manager_config, optimization_level=0 ) - optimization = plugin_manager.get_passmanager_stage( - "optimization", optimization_method, pass_manager_config, optimization_level=0 - ) + if basis_gates or (target and not isinstance(target, FakeTarget)): + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=0 + ) + else: + optimization = None return StagedPassManager( pre_init=pre_init, diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 0a0b1c2a8e43..7548fc5c87ac 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -74,9 +74,12 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout = None routing = None - translation = plugin_manager.get_passmanager_stage( - "translation", translation_method, pass_manager_config, optimization_level=1 - ) + if basis_gates: + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=1 + ) + else: + translation = None if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) @@ -87,9 +90,12 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa else: pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=False) - optimization = plugin_manager.get_passmanager_stage( - "optimization", optimization_method, pass_manager_config, optimization_level=1 - ) + if basis_gates: + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=1 + ) + else: + optimization = None sched = plugin_manager.get_passmanager_stage( "scheduling", scheduling_method, pass_manager_config, optimization_level=1 diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index b22743883fbe..1bd057577a30 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -73,9 +73,13 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa else: layout = None routing = None - translation = plugin_manager.get_passmanager_stage( - "translation", translation_method, pass_manager_config, optimization_level=2 - ) + + if basis_gates: + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=2 + ) + else: + translation = None if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) @@ -86,9 +90,12 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa else: pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=False) - optimization = plugin_manager.get_passmanager_stage( - "optimization", optimization_method, pass_manager_config, optimization_level=2 - ) + if basis_gates: + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=2 + ) + else: + optimization = None sched = plugin_manager.get_passmanager_stage( "scheduling", scheduling_method, pass_manager_config, optimization_level=2 diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index c1393a0b7c6e..17e7ae240db9 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -87,13 +87,17 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout = None routing = None - translation = plugin_manager.get_passmanager_stage( - "translation", translation_method, pass_manager_config, optimization_level=3 - ) + if basis_gates: + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=3 + ) + else: + translation = None optimization = plugin_manager.get_passmanager_stage( "optimization", optimization_method, pass_manager_config, optimization_level=3 ) + if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) ): diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index da4a44a8ee02..0b3a04b51ce2 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -823,6 +823,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): return cmap if self._coupling_graph is None: self._build_coupling_graph() + # if there is no connectivity constraints in the coupling graph treat it as not # existing and return if self._coupling_graph is not None: @@ -929,7 +930,7 @@ def __setstate__(self, state: tuple): @classmethod def from_configuration( cls, - basis_gates: list[str], + basis_gates: list[str] | None, num_qubits: int | None = None, coupling_map: CouplingMap | None = None, inst_map: InstructionScheduleMap | None = None, @@ -1022,20 +1023,34 @@ def from_configuration( qubit_properties = qubit_props_list_from_props(properties=backend_properties) - target = cls( - num_qubits=num_qubits, - dt=dt, - granularity=granularity, - min_length=min_length, - pulse_alignment=pulse_alignment, - acquire_alignment=acquire_alignment, - qubit_properties=qubit_properties, - concurrent_measurements=concurrent_measurements, - ) - name_mapping = get_standard_gate_name_mapping() - if custom_name_mapping is not None: - name_mapping.update(custom_name_mapping) - + if basis_gates is None: + if num_qubits is None and coupling_map is not None: + num_qubits = len(coupling_map.graph) + target = FakeTarget( + coupling_map=coupling_map, + num_qubits=num_qubits, + dt=dt, + granularity=granularity, + min_length=min_length, + pulse_alignment=pulse_alignment, + acquire_alignment=acquire_alignment, + qubit_properties=qubit_properties, + concurrent_measurements=concurrent_measurements, + ) + else: + target = cls( + num_qubits=num_qubits, + dt=dt, + granularity=granularity, + min_length=min_length, + pulse_alignment=pulse_alignment, + acquire_alignment=acquire_alignment, + qubit_properties=qubit_properties, + concurrent_measurements=concurrent_measurements, + ) + name_mapping = get_standard_gate_name_mapping() + if custom_name_mapping is not None: + name_mapping.update(custom_name_mapping) # While BackendProperties can also contain coupling information we # rely solely on CouplingMap to determine connectivity. This is because # in legacy transpiler usage (and implicitly in the BackendV1 data model) @@ -1043,38 +1058,40 @@ def from_configuration( # the properties is only used for error rate and duration population. # If coupling map is not specified we ignore the backend_properties if coupling_map is None: - for gate in basis_gates: - if gate not in name_mapping: - raise KeyError( - f"The specified basis gate: {gate} is not present in the standard gate " - "names or a provided custom_name_mapping" - ) - target.add_instruction(name_mapping[gate], name=gate) + if basis_gates is not None: + for gate in basis_gates: + if gate not in name_mapping: + raise KeyError( + f"The specified basis gate: {gate} is not present in the standard gate " + "names or a provided custom_name_mapping" + ) + target.add_instruction(name_mapping[gate], name=gate) else: one_qubit_gates = [] two_qubit_gates = [] global_ideal_variable_width_gates = [] # pylint: disable=invalid-name if num_qubits is None: num_qubits = len(coupling_map.graph) - for gate in basis_gates: - if gate not in name_mapping: - raise KeyError( - f"The specified basis gate: {gate} is not present in the standard gate " - "names or a provided custom_name_mapping" - ) - gate_obj = name_mapping[gate] - if gate_obj.num_qubits == 1: - one_qubit_gates.append(gate) - elif gate_obj.num_qubits == 2: - two_qubit_gates.append(gate) - elif inspect.isclass(gate_obj): - global_ideal_variable_width_gates.append(gate) - else: - raise TranspilerError( - f"The specified basis gate: {gate} has {gate_obj.num_qubits} " - "qubits. This constructor method only supports fixed width operations " - "with <= 2 qubits (because connectivity is defined on a CouplingMap)." - ) + if basis_gates is not None: + for gate in basis_gates: + if gate not in name_mapping: + raise KeyError( + f"The specified basis gate: {gate} is not present in the standard gate " + "names or a provided custom_name_mapping" + ) + gate_obj = name_mapping[gate] + if gate_obj.num_qubits == 1: + one_qubit_gates.append(gate) + elif gate_obj.num_qubits == 2: + two_qubit_gates.append(gate) + elif inspect.isclass(gate_obj): + global_ideal_variable_width_gates.append(gate) + else: + raise TranspilerError( + f"The specified basis gate: {gate} has {gate_obj.num_qubits} " + "qubits. This constructor method only supports fixed width operations " + "with <= 2 qubits (because connectivity is defined on a CouplingMap)." + ) for gate in one_qubit_gates: gate_properties: dict[tuple, InstructionProperties] = {} for qubit in range(num_qubits): @@ -1259,3 +1276,14 @@ def target_to_backend_properties(target: Target): return BackendProperties.from_dict(properties_dict) else: return None + + +class FakeTarget(Target): + """Fake target that only contains a connectivity constraint.""" + + def __init__(self, coupling_map=None, **kwargs): + super().__init__(**kwargs) + self._coupling_map = coupling_map + + def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): + return self._coupling_map diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 825dfd6bdfe7..1cc54b3252b4 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2039,7 +2039,8 @@ def _visit_block(circuit, qubit_mapping=None): qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)}, ) - @data(1, 2, 3) + # @data(1, 2, 3) + @data(3) def test_transpile_identity_circuit_no_target(self, opt_level): """Test circuit equivalent to identity is optimized away for all optimization levels >0. diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index b762d84032bb..18edd84bb657 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -91,7 +91,8 @@ def circuit_2532(): class TestPresetPassManager(QiskitTestCase): """Test preset passmanagers work as expected.""" - @combine(level=[0, 1, 2, 3], name="level{level}") + # @combine(level=[0, 1, 2, 3], name="level{level}") + @combine(level=[3], name="level{level}") def test_no_coupling_map_with_sabre(self, level): """Test that coupling_map can be None with Sabre (level={level})""" q = QuantumRegister(2, name="q") @@ -143,7 +144,14 @@ def test_no_basis_gates(self, level): circuit = QuantumCircuit(q) circuit.h(q[0]) circuit.cz(q[0], q[1]) - result = transpile(circuit, basis_gates=None, optimization_level=level) + + def callback_func(**kwargs): + t_pass = kwargs["pass_"].name() + print("callback", t_pass) + + result = transpile( + circuit, basis_gates=None, optimization_level=level, callback=callback_func + ) self.assertEqual(result, circuit) def test_level0_keeps_reset(self): @@ -1261,7 +1269,11 @@ def test_size_optimization(self, level): qc.cx(7, 6) qc.cx(6, 7) - circ = transpile(qc, optimization_level=level).decompose() + def callback_func(**kwargs): + t_pass = kwargs["pass_"].name() + print("callback", t_pass) + + circ = transpile(qc, optimization_level=level, callback=callback_func).decompose() circ_data = circ.data free_qubits = {0, 1, 2, 3} diff --git a/test/utils/base.py b/test/utils/base.py index dded6bbda2b2..0d614b319fc5 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -303,6 +303,17 @@ def setUp(self): self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) self.useFixture(fixtures.LoggerFixture(nuke_handlers=False, level=None)) + # a dummy setting config to make sure it does not intervene + # with the test runner environment. See https://github.com/Qiskit/qiskit/pull/12463 + self._mock_setting = unittest.mock.patch.dict( + os.environ, {"QISKIT_SETTINGS": "dummy_setting.conf"} + ) + self._mock_setting.start() + + def tearDown(self): + super().tearDown() + self._mock_setting.stop() + def dicts_almost_equal(dict1, dict2, delta=None, places=None, default_value=0): """Test if two dictionaries with numeric values are almost equal. From 6204af45fa43e6219fdeb842f31211e1679b6143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 17 Oct 2024 17:45:23 +0200 Subject: [PATCH 02/25] Remove prints --- qiskit/transpiler/preset_passmanagers/builtin_plugins.py | 3 --- .../preset_passmanagers/generate_preset_pass_manager.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index fba1f26b71f9..7767ac5ecc03 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -185,7 +185,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 ): - print("HERE??", pass_manager_config.basis_gates) init.append(ConsolidateBlocks()) # If approximation degree is None that indicates a request to approximate up to the # error rates in the target. However, in the init stage we don't yet know the target @@ -709,8 +708,6 @@ def _unroll_condition(property_set): else _opt + _unroll_if_out_of_basis + _depth_check + _size_check ) optimization.append(DoWhileController(opt_loop, do_while=_opt_control)) - print("opt", optimization._tasks) - return optimization else: return None diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 8a73ab856541..4400b1dd8262 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -361,7 +361,6 @@ def generate_preset_pass_manager( approximation_degree = _parse_approximation_degree(approximation_degree) seed_transpiler = _parse_seed_transpiler(seed_transpiler) - print("target", target) pm_options = { "target": target, "basis_gates": basis_gates, @@ -401,7 +400,6 @@ def generate_preset_pass_manager( else: raise ValueError(f"Invalid optimization level {optimization_level}") - # print("all", pm._tasks) return pm @@ -485,7 +483,6 @@ def _parse_coupling_map(coupling_map, backend): if isinstance(coupling_map, list) and all( isinstance(i, list) and len(i) == 2 for i in coupling_map ): - print("here") return CouplingMap(coupling_map) else: raise TranspilerError( From 77eff6ed12dd874a250b1a7d7a17adf6a3372598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 24 Oct 2024 14:01:14 +0200 Subject: [PATCH 03/25] Fix control-flow failures with fake target --- qiskit/transpiler/passes/basis/basis_translator.py | 3 --- qiskit/transpiler/preset_passmanagers/common.py | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index e7b9cbe05746..57db50b6b29c 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -124,8 +124,6 @@ def __init__(self, equivalence_library, target_basis, target=None, min_qubits=0) for qarg in self._target[gate]: self._qargs_with_non_global_operation[qarg].add(gate) - print("inside pas: target:", target, "target basis", target_basis) - def run(self, dag): """Translate an input DAGCircuit to the target basis. @@ -155,7 +153,6 @@ def run(self, dag): source_basis, qargs_local_source_basis = self._extract_basis_target(dag, qarg_indices) target_basis = set(target_basis).union(basic_instrs) - print("final target basis", target_basis) # If the source basis is a subset of the target basis and we have no circuit # instructions on qargs that have non-global operations there is nothing to diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 8b2a9137075c..0b605674aeaa 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -84,9 +84,9 @@ class _InvalidControlFlowForBackend: # Explicitly stateful closure to allow pickling. def __init__(self, basis_gates=(), target=None): - if target is not None: + if target is not None and len(target.operation_names) > 0: self.unsupported = [op for op in CONTROL_FLOW_OP_NAMES if op not in target] - elif basis_gates is not None: + elif basis_gates is not None and len(basis_gates) > 0: basis_gates = set(basis_gates) self.unsupported = [op for op in CONTROL_FLOW_OP_NAMES if op not in basis_gates] else: From efca6d2a2129c8d8c0e17c0284a8d486a645ef0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 24 Oct 2024 14:39:42 +0200 Subject: [PATCH 04/25] Fix init stage, add instruction_supported method to FakeTarget --- .../preset_passmanagers/builtin_plugins.py | 48 +++-- .../transpiler/preset_passmanagers/common.py | 166 ++++++++++-------- qiskit/transpiler/target.py | 3 + 3 files changed, 119 insertions(+), 98 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 7767ac5ecc03..15362418b8fe 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -14,7 +14,6 @@ import os -from qiskit.transpiler.target import FakeTarget from qiskit.transpiler.passes.optimization.split_2q_unitaries import Split2QUnitaries from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.exceptions import TranspilerError @@ -95,7 +94,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana or ( pass_manager_config.target is not None and pass_manager_config.target.build_coupling_map() is not None - and not isinstance(pass_manager_config.target, FakeTarget) ) ): init = common.generate_unroll_3q( @@ -115,7 +113,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana or ( pass_manager_config.target is not None and pass_manager_config.target.build_coupling_map() is not None - and not isinstance(pass_manager_config.target, FakeTarget) ) ): init += common.generate_unroll_3q( @@ -147,17 +144,15 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) elif optimization_level in {2, 3}: - init = PassManager() - if not isinstance(pass_manager_config.target, FakeTarget): - init = common.generate_unroll_3q( - pass_manager_config.target, - pass_manager_config.basis_gates, - pass_manager_config.approximation_degree, - pass_manager_config.unitary_synthesis_method, - pass_manager_config.unitary_synthesis_plugin_config, - pass_manager_config.hls_config, - pass_manager_config.qubits_initially_zero, - ) + init = common.generate_unroll_3q( + pass_manager_config.target, + pass_manager_config.basis_gates, + pass_manager_config.approximation_degree, + pass_manager_config.unitary_synthesis_method, + pass_manager_config.unitary_synthesis_plugin_config, + pass_manager_config.hls_config, + pass_manager_config.qubits_initially_zero, + ) if pass_manager_config.routing_method != "none": init.append(ElidePermutations()) init.append(RemoveDiagonalGatesBeforeMeasure()) @@ -181,11 +176,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) init.append(CommutativeCancellation()) init.append(Collect2qBlocks()) - if ( - pass_manager_config.basis_gates is not None - and len(pass_manager_config.basis_gates) > 0 - ): - init.append(ConsolidateBlocks()) + init.append(ConsolidateBlocks()) # If approximation degree is None that indicates a request to approximate up to the # error rates in the target. However, in the init stage we don't yet know the target # qubits being used to figure out the fidelity so just use the default fidelity parameter @@ -613,18 +604,20 @@ def _opt_control(property_set): ] elif optimization_level == 3: # Steps for optimization level 3 - _opt = [Collect2qBlocks()] + _opt = [ + Collect2qBlocks(), + ConsolidateBlocks( + basis_gates=pass_manager_config.basis_gates, + target=pass_manager_config.target, + approximation_degree=pass_manager_config.approximation_degree, + ), + ] if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 ): _opt += [ - ConsolidateBlocks( - basis_gates=pass_manager_config.basis_gates, - target=pass_manager_config.target, - approximation_degree=pass_manager_config.approximation_degree, - ), UnitarySynthesis( pass_manager_config.basis_gates, approximation_degree=pass_manager_config.approximation_degree, @@ -697,6 +690,11 @@ def _unroll_condition(property_set): optimization.append( [ Collect2qBlocks(), + ConsolidateBlocks( + basis_gates=pass_manager_config.basis_gates, + target=pass_manager_config.target, + approximation_degree=pass_manager_config.approximation_degree, + ), ] ) optimization.append(_depth_check + _size_check) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 0b605674aeaa..529e39464784 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -236,7 +236,12 @@ def generate_unroll_3q( ) # If there are no target instructions revert to using unroll3qormore so # routing works. - if basis_gates is None and target is None: + if ( + basis_gates is None + and target is None + or target is not None + and len(target.operation_names) == 0 + ): unroll_3q.append(Unroll3qOrMore(target, basis_gates)) else: unroll_3q.append(BasisTranslator(sel, basis_gates, target=target, min_qubits=3)) @@ -453,82 +458,97 @@ def generate_translation_passmanager( Raises: TranspilerError: If the ``method`` kwarg is not a valid value """ - if basis_gates is None and ( - target is None or (target is not None and len(target.operation_names) == 0) - ): + if basis_gates is None and target is None: return PassManager([]) if method == "translator": - unroll = [ - # Use unitary synthesis for basis aware decomposition of - # UnitaryGates before custom unrolling - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_props, - plugin_config=unitary_synthesis_plugin_config, - method=unitary_synthesis_method, - target=target, - ), - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - equivalence_library=sel, - basis_gates=basis_gates, - qubits_initially_zero=qubits_initially_zero, - ), - BasisTranslator(sel, basis_gates, target), - ] + if target is not None and len(target.operation_names) == 0: + unroll = [] + else: + unroll = [ + # Use unitary synthesis for basis aware decomposition of + # UnitaryGates before custom unrolling + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + target=target, + ), + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + equivalence_library=sel, + basis_gates=basis_gates, + qubits_initially_zero=qubits_initially_zero, + ), + BasisTranslator(sel, basis_gates, target), + ] elif method == "synthesis": - unroll = [ - # # Use unitary synthesis for basis aware decomposition of - # UnitaryGates > 2q before collection - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_props, - plugin_config=unitary_synthesis_plugin_config, - method=unitary_synthesis_method, - min_qubits=3, - target=target, - ), - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - basis_gates=basis_gates, - min_qubits=3, - qubits_initially_zero=qubits_initially_zero, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - Collect2qBlocks(), - Collect1qRuns(), - ConsolidateBlocks( - basis_gates=basis_gates, target=target, approximation_degree=approximation_degree - ), - UnitarySynthesis( - basis_gates=basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_props, - plugin_config=unitary_synthesis_plugin_config, - method=unitary_synthesis_method, - target=target, - ), - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - basis_gates=basis_gates, - qubits_initially_zero=qubits_initially_zero, - ), - ] + if target is not None and len(target.operation_names) == 0: + unroll = [ + Unroll3qOrMore(target=target, basis_gates=basis_gates), + Collect2qBlocks(), + Collect1qRuns(), + ConsolidateBlocks( + basis_gates=basis_gates, + target=target, + approximation_degree=approximation_degree, + ), + ] + else: + unroll = [ + # # Use unitary synthesis for basis aware decomposition of + # UnitaryGates > 2q before collection + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + min_qubits=3, + target=target, + ), + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + basis_gates=basis_gates, + min_qubits=3, + qubits_initially_zero=qubits_initially_zero, + ), + Unroll3qOrMore(target=target, basis_gates=basis_gates), + Collect2qBlocks(), + Collect1qRuns(), + ConsolidateBlocks( + basis_gates=basis_gates, + target=target, + approximation_degree=approximation_degree, + ), + UnitarySynthesis( + basis_gates=basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + target=target, + ), + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + basis_gates=basis_gates, + qubits_initially_zero=qubits_initially_zero, + ), + ] else: raise TranspilerError(f"Invalid translation method {method}.") return PassManager(unroll) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 0b3a04b51ce2..8a8cfffbc241 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -1287,3 +1287,6 @@ def __init__(self, coupling_map=None, **kwargs): def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): return self._coupling_map + + def instruction_supported(self, *args, **kwargs): + return True From 91947e462221d068a87a8852d80975a07d4b1eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 24 Oct 2024 16:13:43 +0200 Subject: [PATCH 05/25] Tweak preset pass manager --- .../preset_passmanagers/builtin_plugins.py | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 15362418b8fe..90f502bf0da8 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -640,25 +640,17 @@ def _opt_control(property_set): else: raise TranspilerError(f"Invalid optimization_level: {optimization_level}") - if ( - pass_manager_config.basis_gates is not None - and len(pass_manager_config.basis_gates) > 0 - ): - unroll = translation.to_flow_controller() + unroll = translation.to_flow_controller() - # Build nested Flow controllers - def _unroll_condition(property_set): - return not property_set["all_gates_in_basis"] + # Build nested Flow controllers + def _unroll_condition(property_set): + return not property_set["all_gates_in_basis"] - # Check if any gate is not in the basis, and if so, run unroll passes - _unroll_if_out_of_basis = [ - GatesInBasis( - pass_manager_config.basis_gates, target=pass_manager_config.target - ), - ConditionalController(unroll, condition=_unroll_condition), - ] - else: - _unroll_if_out_of_basis = [] + # Check if any gate is not in the basis, and if so, run unroll passes + _unroll_if_out_of_basis = [ + GatesInBasis(pass_manager_config.basis_gates, target=pass_manager_config.target), + ConditionalController(unroll, condition=_unroll_condition), + ] if optimization_level == 3: optimization.append(_minimum_point_check) From 1ac1f59c2875f294cb946fef1ff8c319129eee73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 1 Nov 2024 11:13:02 +0100 Subject: [PATCH 06/25] Handle inst_map deprecation warning --- .../generate_preset_pass_manager.py | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 1568b5742b6e..1cc2323ad30f 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -338,22 +338,30 @@ def generate_preset_pass_manager( # preserve the former behavior of transpile. backend_properties = _parse_backend_properties(backend_properties, backend) # Build target from constraints. - target = Target.from_configuration( - basis_gates=basis_gates, - num_qubits=backend.num_qubits if backend is not None else None, - coupling_map=coupling_map, - # If the instruction map has custom gates, do not give as config, the information - # will be added to the target with update_from_instruction_schedule_map - inst_map=inst_map if inst_map and not inst_map.has_custom_gate() else None, - backend_properties=backend_properties, - instruction_durations=instruction_durations, - concurrent_measurements=( - backend.target.concurrent_measurements if backend is not None else None - ), - dt=dt, - timing_constraints=timing_constraints, - custom_name_mapping=name_mapping, - ) + with warnings.catch_warnings(): + # TODO: inst_map will be removed in 2.0 + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message=".*``inst_map`` is deprecated as of Qiskit 1.3.*", + module="qiskit", + ) + target = Target.from_configuration( + basis_gates=basis_gates, + num_qubits=backend.num_qubits if backend is not None else None, + coupling_map=coupling_map, + # If the instruction map has custom gates, do not give as config, the information + # will be added to the target with update_from_instruction_schedule_map + inst_map=inst_map if inst_map and not inst_map.has_custom_gate() else None, + backend_properties=backend_properties, + instruction_durations=instruction_durations, + concurrent_measurements=( + backend.target.concurrent_measurements if backend is not None else None + ), + dt=dt, + timing_constraints=timing_constraints, + custom_name_mapping=name_mapping, + ) # Update target with custom gate information. Note that this is an exception to the priority # order (target > loose constraints), added to handle custom gates for scheduling passes. From 472cf64fa3e8fb2680f95604dd3e0659b601303d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 1 Nov 2024 11:24:52 +0100 Subject: [PATCH 07/25] Handle annotated operations --- qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 870217f64754..5c912238f787 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -302,7 +302,7 @@ def __init__( self._basis_gates = basis_gates self._min_qubits = min_qubits - self._top_level_only = self._basis_gates is None and self._target is None + self._top_level_only = self._basis_gates is None and (self._target is None or len(self._target.operation_names) == 0) # include path for when target exists but target.num_qubits is None (BasicSimulator) if not self._top_level_only and (self._target is None or self._target.num_qubits is None): From 1ba53818f7c21c4f8b84a409eb6777dac3c100a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 1 Nov 2024 16:59:23 +0100 Subject: [PATCH 08/25] Do not skip stages with FakeTarget --- .../passes/synthesis/high_level_synthesis.py | 4 ++- .../transpiler/preset_passmanagers/common.py | 29 ++++++++++++++++++- .../transpiler/preset_passmanagers/level0.py | 18 ++++-------- .../transpiler/preset_passmanagers/level1.py | 18 ++++-------- .../transpiler/preset_passmanagers/level2.py | 18 ++++-------- .../transpiler/preset_passmanagers/level3.py | 9 ++---- test/python/compiler/test_transpiler.py | 13 ++++++++- 7 files changed, 64 insertions(+), 45 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 5c912238f787..d34bde12556c 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -302,7 +302,9 @@ def __init__( self._basis_gates = basis_gates self._min_qubits = min_qubits - self._top_level_only = self._basis_gates is None and (self._target is None or len(self._target.operation_names) == 0) + self._top_level_only = (self._basis_gates is None or len(self._basis_gates) == 0) and ( + self._target is None or len(self._target.operation_names) == 0 + ) # include path for when target exists but target.num_qubits is None (BasicSimulator) if not self._top_level_only and (self._target is None or self._target.num_qubits is None): diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 0000756f940b..4ae01a2549b2 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -464,7 +464,17 @@ def generate_translation_passmanager( if method == "translator": if target is not None and len(target.operation_names) == 0: - unroll = [] + unroll = [ + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + equivalence_library=sel, + basis_gates=basis_gates, + qubits_initially_zero=qubits_initially_zero, + ), + ] else: unroll = [ # Use unitary synthesis for basis aware decomposition of @@ -492,6 +502,15 @@ def generate_translation_passmanager( elif method == "synthesis": if target is not None and len(target.operation_names) == 0: unroll = [ + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + basis_gates=basis_gates, + min_qubits=3, + qubits_initially_zero=qubits_initially_zero, + ), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), Collect1qRuns(), @@ -500,6 +519,14 @@ def generate_translation_passmanager( target=target, approximation_degree=approximation_degree, ), + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + basis_gates=basis_gates, + qubits_initially_zero=qubits_initially_zero, + ), ] else: unroll = [ diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index ca74f7e34de5..58381b3db7a8 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -69,12 +69,9 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout = None routing = None - if basis_gates: - translation = plugin_manager.get_passmanager_stage( - "translation", translation_method, pass_manager_config, optimization_level=0 - ) - else: - translation = None + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=0 + ) if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) @@ -100,12 +97,9 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa init = plugin_manager.get_passmanager_stage( "init", init_method, pass_manager_config, optimization_level=0 ) - if basis_gates or (target and not isinstance(target, FakeTarget)): - optimization = plugin_manager.get_passmanager_stage( - "optimization", optimization_method, pass_manager_config, optimization_level=0 - ) - else: - optimization = None + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=0 + ) return StagedPassManager( pre_init=pre_init, diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 7548fc5c87ac..0a0b1c2a8e43 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -74,12 +74,9 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout = None routing = None - if basis_gates: - translation = plugin_manager.get_passmanager_stage( - "translation", translation_method, pass_manager_config, optimization_level=1 - ) - else: - translation = None + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=1 + ) if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) @@ -90,12 +87,9 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa else: pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=False) - if basis_gates: - optimization = plugin_manager.get_passmanager_stage( - "optimization", optimization_method, pass_manager_config, optimization_level=1 - ) - else: - optimization = None + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=1 + ) sched = plugin_manager.get_passmanager_stage( "scheduling", scheduling_method, pass_manager_config, optimization_level=1 diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 1bd057577a30..aa5f33d67bbd 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -74,12 +74,9 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout = None routing = None - if basis_gates: - translation = plugin_manager.get_passmanager_stage( - "translation", translation_method, pass_manager_config, optimization_level=2 - ) - else: - translation = None + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=2 + ) if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) @@ -90,12 +87,9 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa else: pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=False) - if basis_gates: - optimization = plugin_manager.get_passmanager_stage( - "optimization", optimization_method, pass_manager_config, optimization_level=2 - ) - else: - optimization = None + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=2 + ) sched = plugin_manager.get_passmanager_stage( "scheduling", scheduling_method, pass_manager_config, optimization_level=2 diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 17e7ae240db9..b51e4c0775be 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -87,12 +87,9 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout = None routing = None - if basis_gates: - translation = plugin_manager.get_passmanager_stage( - "translation", translation_method, pass_manager_config, optimization_level=3 - ) - else: - translation = None + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=3 + ) optimization = plugin_manager.get_passmanager_stage( "optimization", optimization_method, pass_manager_config, optimization_level=3 diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 44a11f53155c..03a7c74ab988 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2198,6 +2198,14 @@ def test_barrier_not_output_input_preservered(self, opt_level): @combine(opt_level=[0, 1, 2, 3]) def test_transpile_annotated_ops(self, opt_level): """Test transpilation of circuits with annotated operations.""" + passes = [] + kwargss = [] + + def callback_func(**kwargs): + t_pass = kwargs["pass_"].name() + kwargss.append(kwargs) + passes.append(t_pass) + qc = QuantumCircuit(3) qc.append(AnnotatedOperation(SGate(), InverseModifier()), [0]) qc.append(AnnotatedOperation(XGate(), ControlModifier(1)), [1, 2]) @@ -2206,7 +2214,10 @@ def test_transpile_annotated_ops(self, opt_level): expected.sdg(0) expected.cx(1, 2) expected.h(2) - transpiled = transpile(qc, optimization_level=opt_level, seed_transpiler=42) + transpiled = transpile( + qc, optimization_level=opt_level, seed_transpiler=42, callback=callback_func + ) + print("Callback out:", passes) self.assertNotIn("annotated", transpiled.count_ops().keys()) self.assertEqual(Operator(qc), Operator(transpiled)) self.assertEqual(Operator(qc), Operator(expected)) From c3a25732acf823d75c8b93b5878f9a545c7a21d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 1 Nov 2024 18:27:10 +0100 Subject: [PATCH 09/25] Deprecate custom basis gates --- .../generate_preset_pass_manager.py | 41 ++++++++++++++++--- ...constraint-transpile-41801e8dfc6d1ce0.yaml | 18 ++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/deprecate-custom-basis-gates-loose-constraint-transpile-41801e8dfc6d1ce0.yaml diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 1cc2323ad30f..5e8c91a88072 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -17,7 +17,13 @@ import copy import warnings -from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES +from qiskit.circuit.controlflow import ( + CONTROL_FLOW_OP_NAMES, + IfElseOp, + WhileLoopOp, + ForLoopOp, + SwitchCaseOp, +) from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping from qiskit.circuit.quantumregister import Qubit from qiskit.providers.backend import Backend @@ -441,11 +447,15 @@ def generate_preset_pass_manager( def _parse_basis_gates(basis_gates, backend, inst_map, skip_target): - name_mapping = {} standard_gates = get_standard_gate_name_mapping() - # Add control flow gates by default to basis set + # Add control flow gates by default to basis set and name mapping default_gates = {"measure", "delay", "reset"}.union(CONTROL_FLOW_OP_NAMES) - + name_mapping = { + "if_else": IfElseOp, + "while_loop": WhileLoopOp, + "for_loop": ForLoopOp, + "switch_case": SwitchCaseOp, + } try: instructions = set(basis_gates) for name in default_gates: @@ -460,7 +470,15 @@ def _parse_basis_gates(basis_gates, backend, inst_map, skip_target): return None, name_mapping, skip_target for inst in instructions: - if inst not in standard_gates or inst not in default_gates: + if inst not in standard_gates and inst not in default_gates: + warnings.warn( + category=DeprecationWarning, + message="Providing custom gates through the ``basis_gates`` argument is deprecated " + "for both ``transpile`` and ``generate_preset_pass_manager`` as of Qiskit 1.3.0. " + "It will be removed in Qiskit 2.0. The ``target`` parameter should be used instead. " + "You can build a target instance using ``Target.from_configuration()`` and provide" + "custom gate definitions with the ``custom_name_mapping`` argument.", + ) skip_target = True break @@ -473,7 +491,18 @@ def _parse_basis_gates(basis_gates, backend, inst_map, skip_target): # Check for custom instructions before removing calibrations for inst in instructions: - if inst not in standard_gates or inst not in default_gates: + if inst not in standard_gates and inst not in default_gates: + if inst not in backend.operation_names: + # do not raise warning when the custom instruction comes from the backend + # (common case with BasicSimulator) + warnings.warn( + category=DeprecationWarning, + message="Providing custom gates through the ``basis_gates`` argument is deprecated " + "for both ``transpile`` and ``generate_preset_pass_manager`` as of Qiskit 1.3.0. " + "It will be removed in Qiskit 2.0. The ``target`` parameter should be used instead. " + "You can build a target instance using ``Target.from_configuration()`` and provide" + "custom gate definitions with the ``custom_name_mapping`` argument.", + ) skip_target = True break diff --git a/releasenotes/notes/deprecate-custom-basis-gates-loose-constraint-transpile-41801e8dfc6d1ce0.yaml b/releasenotes/notes/deprecate-custom-basis-gates-loose-constraint-transpile-41801e8dfc6d1ce0.yaml new file mode 100644 index 000000000000..67cfce71eced --- /dev/null +++ b/releasenotes/notes/deprecate-custom-basis-gates-loose-constraint-transpile-41801e8dfc6d1ce0.yaml @@ -0,0 +1,18 @@ +--- +deprecations_transpiler: + - | + Providing custom gates through the ``basis_gates`` argument is deprecated + for both :func:`.transpile` and :func:`generate_preset_pass_manager`, this functionality + will be removed in Qiskit 2.0. Custom gates are still supported in the :class:`.Target` model, + and can be provided through the ``target`` argument. One can build a :class:`.Target` instance + from scratch or use the :meth:`.Target.from_configuration` method with the ``custom_name_mapping`` + argument. For example:: + + from qiskit.circuit.library import XGate + from qiskit.transpiler.target import Target + + basis_gates = ["my_x", "cx"] + custom_name_mapping = {"my_x": XGate()} + target = Target.from_configuration( + basis_gates=basis_gates, num_qubits=2, custom_name_mapping=custom_name_mapping + ) \ No newline at end of file From cbd0973fc12b50d509e8f349f36bd446148f73af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 1 Nov 2024 19:21:44 +0100 Subject: [PATCH 10/25] Cleanup --- .../passes/synthesis/high_level_synthesis.py | 2 ++ .../preset_passmanagers/builtin_plugins.py | 13 ++++++++++- .../generate_preset_pass_manager.py | 11 +++++----- .../transpiler/preset_passmanagers/level2.py | 1 - .../transpiler/preset_passmanagers/level3.py | 1 - qiskit/transpiler/target.py | 22 ++++++++++++++++--- test/python/compiler/test_transpiler.py | 16 ++------------ .../transpiler/test_preset_passmanagers.py | 17 +++----------- test/utils/base.py | 11 ---------- 9 files changed, 43 insertions(+), 51 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index d34bde12556c..fc847a40a2c8 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -302,6 +302,8 @@ def __init__( self._basis_gates = basis_gates self._min_qubits = min_qubits + # include cases where target exists with no basis gates, or the basis gates + # are an empty list self._top_level_only = (self._basis_gates is None or len(self._basis_gates) == 0) and ( self._target is None or len(self._target.operation_names) == 0 ) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 8b60b16dfcf2..555dedef8c54 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -558,6 +558,9 @@ def _opt_control(property_set): ) if optimization_level == 1: # Steps for optimization level 1 + + # If there are no basis gates (None/empty list), don't run + # Optimize1qGatesDecomposition if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 @@ -569,6 +572,7 @@ def _opt_control(property_set): ] else: _opt = [] + _opt += [ InverseCancellation( [ @@ -589,6 +593,10 @@ def _opt_control(property_set): ] elif optimization_level == 2: + # Steps for optimization level 2 + + # If there are no basis gates (None/empty list), don't run + # Optimize1qGatesDecomposition if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 @@ -613,7 +621,8 @@ def _opt_control(property_set): approximation_degree=pass_manager_config.approximation_degree, ), ] - + # If there are no basis gates (None/empty list), don't run + # Optimize1qGatesDecomposition or UnitarySynthesis if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 @@ -656,6 +665,8 @@ def _unroll_condition(property_set): if optimization_level == 3: optimization.append(_minimum_point_check) elif optimization_level == 2: + # If there are no basis gates (None/empty list), don't run + # UnitarySynthesis if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 5e8c91a88072..de005506307d 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -315,12 +315,11 @@ def generate_preset_pass_manager( and timing_constraints is None ) # If it's an edge case => do not build target - _skip_target = ( - target is None - and backend is None - # and (basis_gates is None or coupling_map is None or instruction_durations is not None) - and (instruction_durations is not None) - ) + # NOTE (1.3.0): we are skipping the target in the case where + # instruction_durations is provided without additional constraints + # instead of providing a target-based alternative because the argument + # will be removed in 2.0 as part of the Pulse deprecation efforts. + _skip_target = target is None and backend is None and instruction_durations is not None # Resolve loose constraints case-by-case against backend constraints. # The order of priority is loose constraints > backend. diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index aa5f33d67bbd..b22743883fbe 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -73,7 +73,6 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa else: layout = None routing = None - translation = plugin_manager.get_passmanager_stage( "translation", translation_method, pass_manager_config, optimization_level=2 ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index b51e4c0775be..c1393a0b7c6e 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -94,7 +94,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa optimization = plugin_manager.get_passmanager_stage( "optimization", optimization_method, pass_manager_config, optimization_level=3 ) - if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) ): diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index e074b96e74fb..4fe45ec2c7c8 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -867,7 +867,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): if self._coupling_graph is None: self._build_coupling_graph() - # if there is no connectivity constraints in the coupling graph treat it as not + # if there are no connectivity constraints in the coupling graph, treat it as not # existing and return if self._coupling_graph is not None: cmap = CouplingMap() @@ -1068,6 +1068,10 @@ def from_configuration( qubit_properties = qubit_props_list_from_props(properties=backend_properties) if basis_gates is None: + # The Target class requires basis_gates. + # If there are no basis_gates, we can't build a proper Target instance, + # but we can generate a "pseudo" target that holds connectivity constraints + # and is compatible with our transpilation pipeline if num_qubits is None and coupling_map is not None: num_qubits = len(coupling_map.graph) target = FakeTarget( @@ -1323,7 +1327,12 @@ def target_to_backend_properties(target: Target): class FakeTarget(Target): - """Fake target that only contains a connectivity constraint.""" + """ + Pseudo-target class for internal use in the transpilation pipeline. + Differently to the :class:`.Target` class, this preudo-target is initialized + with a `coupling_map` argument that allows to store connectivity constraints + without basis gates. + """ def __init__(self, coupling_map=None, **kwargs): super().__init__(**kwargs) @@ -1333,4 +1342,11 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): return self._coupling_map def instruction_supported(self, *args, **kwargs): - return True + """Checks whether an instruction is supported by the + Target based on instruction name and qargs. Note that if there are no + basis gates in the Target, this method will always return ``True``. + """ + if len(self.operation_names) == 0: + return True + else: + return super().instruction_supported(*args, **kwargs) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 03a7c74ab988..5241304a0db4 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2097,8 +2097,7 @@ def _visit_block(circuit, qubit_mapping=None): qubit_mapping={qubit: index for index, qubit in enumerate(transpiled.qubits)}, ) - # @data(1, 2, 3) - @data(3) + @data(1, 2, 3) def test_transpile_identity_circuit_no_target(self, opt_level): """Test circuit equivalent to identity is optimized away for all optimization levels >0. @@ -2198,14 +2197,6 @@ def test_barrier_not_output_input_preservered(self, opt_level): @combine(opt_level=[0, 1, 2, 3]) def test_transpile_annotated_ops(self, opt_level): """Test transpilation of circuits with annotated operations.""" - passes = [] - kwargss = [] - - def callback_func(**kwargs): - t_pass = kwargs["pass_"].name() - kwargss.append(kwargs) - passes.append(t_pass) - qc = QuantumCircuit(3) qc.append(AnnotatedOperation(SGate(), InverseModifier()), [0]) qc.append(AnnotatedOperation(XGate(), ControlModifier(1)), [1, 2]) @@ -2214,10 +2205,7 @@ def callback_func(**kwargs): expected.sdg(0) expected.cx(1, 2) expected.h(2) - transpiled = transpile( - qc, optimization_level=opt_level, seed_transpiler=42, callback=callback_func - ) - print("Callback out:", passes) + transpiled = transpile(qc, optimization_level=opt_level, seed_transpiler=42) self.assertNotIn("annotated", transpiled.count_ops().keys()) self.assertEqual(Operator(qc), Operator(transpiled)) self.assertEqual(Operator(qc), Operator(expected)) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index ec1d4836393b..4b36c6095ef0 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -91,8 +91,7 @@ def circuit_2532(): class TestPresetPassManager(QiskitTestCase): """Test preset passmanagers work as expected.""" - # @combine(level=[0, 1, 2, 3], name="level{level}") - @combine(level=[3], name="level{level}") + @combine(level=[0, 1, 2, 3], name="level{level}") def test_no_coupling_map_with_sabre(self, level): """Test that coupling_map can be None with Sabre (level={level})""" q = QuantumRegister(2, name="q") @@ -145,13 +144,7 @@ def test_no_basis_gates(self, level): circuit.h(q[0]) circuit.cz(q[0], q[1]) - def callback_func(**kwargs): - t_pass = kwargs["pass_"].name() - print("callback", t_pass) - - result = transpile( - circuit, basis_gates=None, optimization_level=level, callback=callback_func - ) + result = transpile(circuit, basis_gates=None, optimization_level=level) self.assertEqual(result, circuit) def test_level0_keeps_reset(self): @@ -1271,11 +1264,7 @@ def test_size_optimization(self, level): qc.cx(7, 6) qc.cx(6, 7) - def callback_func(**kwargs): - t_pass = kwargs["pass_"].name() - print("callback", t_pass) - - circ = transpile(qc, optimization_level=level, callback=callback_func).decompose() + circ = transpile(qc, optimization_level=level).decompose() circ_data = circ.data free_qubits = {0, 1, 2, 3} diff --git a/test/utils/base.py b/test/utils/base.py index a35d15bb1cd3..133666cfc7ad 100644 --- a/test/utils/base.py +++ b/test/utils/base.py @@ -324,17 +324,6 @@ def setUp(self): self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) self.useFixture(fixtures.LoggerFixture(nuke_handlers=False, level=None)) - # a dummy setting config to make sure it does not intervene - # with the test runner environment. See https://github.com/Qiskit/qiskit/pull/12463 - self._mock_setting = unittest.mock.patch.dict( - os.environ, {"QISKIT_SETTINGS": "dummy_setting.conf"} - ) - self._mock_setting.start() - - def tearDown(self): - super().tearDown() - self._mock_setting.stop() - def dicts_almost_equal(dict1, dict2, delta=None, places=None, default_value=0): """Test if two dictionaries with numeric values are almost equal. From 2df8a27ff7184f6529647561ff8e4d9a6a15551e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 5 Nov 2024 13:13:17 +0100 Subject: [PATCH 11/25] Remove reno --- ...-constraint-transpile-41801e8dfc6d1ce0.yaml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 releasenotes/notes/deprecate-custom-basis-gates-loose-constraint-transpile-41801e8dfc6d1ce0.yaml diff --git a/releasenotes/notes/deprecate-custom-basis-gates-loose-constraint-transpile-41801e8dfc6d1ce0.yaml b/releasenotes/notes/deprecate-custom-basis-gates-loose-constraint-transpile-41801e8dfc6d1ce0.yaml deleted file mode 100644 index 67cfce71eced..000000000000 --- a/releasenotes/notes/deprecate-custom-basis-gates-loose-constraint-transpile-41801e8dfc6d1ce0.yaml +++ /dev/null @@ -1,18 +0,0 @@ ---- -deprecations_transpiler: - - | - Providing custom gates through the ``basis_gates`` argument is deprecated - for both :func:`.transpile` and :func:`generate_preset_pass_manager`, this functionality - will be removed in Qiskit 2.0. Custom gates are still supported in the :class:`.Target` model, - and can be provided through the ``target`` argument. One can build a :class:`.Target` instance - from scratch or use the :meth:`.Target.from_configuration` method with the ``custom_name_mapping`` - argument. For example:: - - from qiskit.circuit.library import XGate - from qiskit.transpiler.target import Target - - basis_gates = ["my_x", "cx"] - custom_name_mapping = {"my_x": XGate()} - target = Target.from_configuration( - basis_gates=basis_gates, num_qubits=2, custom_name_mapping=custom_name_mapping - ) \ No newline at end of file From e79f06c34cb8af3acef9ce90da6ee160a5ba1b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 5 Nov 2024 14:43:51 +0100 Subject: [PATCH 12/25] Refactor creation of _FakeTarget, add tests --- .../generate_preset_pass_manager.py | 52 ++++--- qiskit/transpiler/target.py | 135 +++++++++--------- .../transpiler/test_preset_passmanagers.py | 1 - test/python/transpiler/test_target.py | 18 +++ 4 files changed, 114 insertions(+), 92 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index de005506307d..e3629cffa942 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -33,7 +33,7 @@ from qiskit.transpiler.instruction_durations import InstructionDurations from qiskit.transpiler.layout import Layout from qiskit.transpiler.passmanager_config import PassManagerConfig -from qiskit.transpiler.target import Target, target_to_backend_properties +from qiskit.transpiler.target import Target, target_to_backend_properties, _FakeTarget from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.utils import deprecate_arg from qiskit.utils.deprecate_pulse import deprecate_pulse_arg @@ -342,30 +342,36 @@ def generate_preset_pass_manager( # Only parse backend properties when the target isn't skipped to # preserve the former behavior of transpile. backend_properties = _parse_backend_properties(backend_properties, backend) - # Build target from constraints. - with warnings.catch_warnings(): - # TODO: inst_map will be removed in 2.0 - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=".*``inst_map`` is deprecated as of Qiskit 1.3.*", - module="qiskit", - ) - target = Target.from_configuration( - basis_gates=basis_gates, + if basis_gates is not None: + # Build target from constraints. + with warnings.catch_warnings(): + # TODO: inst_map will be removed in 2.0 + warnings.filterwarnings( + "ignore", + category=DeprecationWarning, + message=".*``inst_map`` is deprecated as of Qiskit 1.3.*", + module="qiskit", + ) + target = Target.from_configuration( + basis_gates=basis_gates, + num_qubits=backend.num_qubits if backend is not None else None, + coupling_map=coupling_map, + # If the instruction map has custom gates, do not give as config, the information + # will be added to the target with update_from_instruction_schedule_map + inst_map=inst_map if inst_map and not inst_map.has_custom_gate() else None, + backend_properties=backend_properties, + instruction_durations=instruction_durations, + concurrent_measurements=( + backend.target.concurrent_measurements if backend is not None else None + ), + dt=dt, + timing_constraints=timing_constraints, + custom_name_mapping=name_mapping, + ) + else: + target = _FakeTarget.from_configuration( num_qubits=backend.num_qubits if backend is not None else None, coupling_map=coupling_map, - # If the instruction map has custom gates, do not give as config, the information - # will be added to the target with update_from_instruction_schedule_map - inst_map=inst_map if inst_map and not inst_map.has_custom_gate() else None, - backend_properties=backend_properties, - instruction_durations=instruction_durations, - concurrent_measurements=( - backend.target.concurrent_measurements if backend is not None else None - ), - dt=dt, - timing_constraints=timing_constraints, - custom_name_mapping=name_mapping, ) # Update target with custom gate information. Note that this is an exception to the priority diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 4fe45ec2c7c8..910be1b35596 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -866,8 +866,7 @@ def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): return cmap if self._coupling_graph is None: self._build_coupling_graph() - - # if there are no connectivity constraints in the coupling graph, treat it as not + # if there is no connectivity constraints in the coupling graph treat it as not # existing and return if self._coupling_graph is not None: cmap = CouplingMap() @@ -974,7 +973,7 @@ def __setstate__(self, state: tuple): @deprecate_pulse_arg("inst_map") def from_configuration( cls, - basis_gates: list[str] | None, + basis_gates: list[str], num_qubits: int | None = None, coupling_map: CouplingMap | None = None, inst_map: InstructionScheduleMap | None = None, @@ -1067,38 +1066,20 @@ def from_configuration( qubit_properties = qubit_props_list_from_props(properties=backend_properties) - if basis_gates is None: - # The Target class requires basis_gates. - # If there are no basis_gates, we can't build a proper Target instance, - # but we can generate a "pseudo" target that holds connectivity constraints - # and is compatible with our transpilation pipeline - if num_qubits is None and coupling_map is not None: - num_qubits = len(coupling_map.graph) - target = FakeTarget( - coupling_map=coupling_map, - num_qubits=num_qubits, - dt=dt, - granularity=granularity, - min_length=min_length, - pulse_alignment=pulse_alignment, - acquire_alignment=acquire_alignment, - qubit_properties=qubit_properties, - concurrent_measurements=concurrent_measurements, - ) - else: - target = cls( - num_qubits=num_qubits, - dt=dt, - granularity=granularity, - min_length=min_length, - pulse_alignment=pulse_alignment, - acquire_alignment=acquire_alignment, - qubit_properties=qubit_properties, - concurrent_measurements=concurrent_measurements, - ) - name_mapping = get_standard_gate_name_mapping() - if custom_name_mapping is not None: - name_mapping.update(custom_name_mapping) + target = cls( + num_qubits=num_qubits, + dt=dt, + granularity=granularity, + min_length=min_length, + pulse_alignment=pulse_alignment, + acquire_alignment=acquire_alignment, + qubit_properties=qubit_properties, + concurrent_measurements=concurrent_measurements, + ) + name_mapping = get_standard_gate_name_mapping() + if custom_name_mapping is not None: + name_mapping.update(custom_name_mapping) + # While BackendProperties can also contain coupling information we # rely solely on CouplingMap to determine connectivity. This is because # in legacy transpiler usage (and implicitly in the BackendV1 data model) @@ -1106,40 +1087,38 @@ def from_configuration( # the properties is only used for error rate and duration population. # If coupling map is not specified we ignore the backend_properties if coupling_map is None: - if basis_gates is not None: - for gate in basis_gates: - if gate not in name_mapping: - raise KeyError( - f"The specified basis gate: {gate} is not present in the standard gate " - "names or a provided custom_name_mapping" - ) - target.add_instruction(name_mapping[gate], name=gate) + for gate in basis_gates: + if gate not in name_mapping: + raise KeyError( + f"The specified basis gate: {gate} is not present in the standard gate " + "names or a provided custom_name_mapping" + ) + target.add_instruction(name_mapping[gate], name=gate) else: one_qubit_gates = [] two_qubit_gates = [] global_ideal_variable_width_gates = [] # pylint: disable=invalid-name if num_qubits is None: num_qubits = len(coupling_map.graph) - if basis_gates is not None: - for gate in basis_gates: - if gate not in name_mapping: - raise KeyError( - f"The specified basis gate: {gate} is not present in the standard gate " - "names or a provided custom_name_mapping" - ) - gate_obj = name_mapping[gate] - if gate_obj.num_qubits == 1: - one_qubit_gates.append(gate) - elif gate_obj.num_qubits == 2: - two_qubit_gates.append(gate) - elif inspect.isclass(gate_obj): - global_ideal_variable_width_gates.append(gate) - else: - raise TranspilerError( - f"The specified basis gate: {gate} has {gate_obj.num_qubits} " - "qubits. This constructor method only supports fixed width operations " - "with <= 2 qubits (because connectivity is defined on a CouplingMap)." - ) + for gate in basis_gates: + if gate not in name_mapping: + raise KeyError( + f"The specified basis gate: {gate} is not present in the standard gate " + "names or a provided custom_name_mapping" + ) + gate_obj = name_mapping[gate] + if gate_obj.num_qubits == 1: + one_qubit_gates.append(gate) + elif gate_obj.num_qubits == 2: + two_qubit_gates.append(gate) + elif inspect.isclass(gate_obj): + global_ideal_variable_width_gates.append(gate) + else: + raise TranspilerError( + f"The specified basis gate: {gate} has {gate_obj.num_qubits} " + "qubits. This constructor method only supports fixed width operations " + "with <= 2 qubits (because connectivity is defined on a CouplingMap)." + ) for gate in one_qubit_gates: gate_properties: dict[tuple, InstructionProperties] = {} for qubit in range(num_qubits): @@ -1326,17 +1305,22 @@ def target_to_backend_properties(target: Target): return None -class FakeTarget(Target): +class _FakeTarget(Target): """ - Pseudo-target class for internal use in the transpilation pipeline. - Differently to the :class:`.Target` class, this preudo-target is initialized - with a `coupling_map` argument that allows to store connectivity constraints + Pseudo-target class for INTERNAL use in the transpilation pipeline. + It's essentially an empty :class:`.Target` instance with a a `coupling_map` + argument that allows to store connectivity constraints without basis gates. + This is intended to replace the use of loose constraints in the pipeline. """ def __init__(self, coupling_map=None, **kwargs): super().__init__(**kwargs) - self._coupling_map = coupling_map + if coupling_map is None or isinstance(coupling_map, CouplingMap): + self._coupling_map = coupling_map + + def __len__(self): + return len(self._gate_map) def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): return self._coupling_map @@ -1350,3 +1334,18 @@ def instruction_supported(self, *args, **kwargs): return True else: return super().instruction_supported(*args, **kwargs) + + @classmethod + def from_configuration( + cls, + num_qubits: int | None = None, + coupling_map: CouplingMap | None = None, + ) -> _FakeTarget: + + if num_qubits is None and coupling_map is not None: + num_qubits = len(coupling_map.graph) + + return cls( + num_qubits=num_qubits, + coupling_map=coupling_map, + ) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 4b36c6095ef0..7bc23b01e841 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -143,7 +143,6 @@ def test_no_basis_gates(self, level): circuit = QuantumCircuit(q) circuit.h(q[0]) circuit.cz(q[0], q[1]) - result = transpile(circuit, basis_gates=None, optimization_level=level) self.assertEqual(result, circuit) diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 980924224d15..e779df44ee0a 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -41,6 +41,7 @@ from qiskit.pulse.calibration_entries import CalibrationPublisher, ScheduleDef from qiskit.transpiler.coupling import CouplingMap from qiskit.transpiler.instruction_durations import InstructionDurations +from qiskit.transpiler.target import _FakeTarget from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler import Target @@ -2077,3 +2078,20 @@ def test_over_two_qubits_with_coupling(self): cmap = CouplingMap.from_line(15) with self.assertRaisesRegex(TranspilerError, "This constructor method only supports"): Target.from_configuration(basis_gates, 15, cmap) + + +class TestFakeTarget(QiskitTestCase): + """Test the fake target class.""" + + def test_fake_instantiation(self): + cmap = CouplingMap([[0, 1]]) + target = _FakeTarget(coupling_map=cmap) + self.assertEqual(target.num_qubits, 0) + self.assertEqual(target.build_coupling_map(), cmap) + + def test_fake_from_configuration(self): + cmap = CouplingMap([[0, 1]]) + target = _FakeTarget.from_configuration(coupling_map=cmap) + self.assertNotEqual(target, None) + self.assertEqual(target.num_qubits, 2) + self.assertEqual(target.build_coupling_map(), cmap) From 75a5535d421725093f19f4ae16ce5aeb71df8aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 5 Nov 2024 16:34:27 +0100 Subject: [PATCH 13/25] Fix handling of pre-optimization stage and gate direction passes --- .../passes/utils/check_gate_direction.py | 16 +++++++++----- .../transpiler/passes/utils/gate_direction.py | 5 +++++ .../transpiler/preset_passmanagers/common.py | 22 +++++++++++-------- .../transpiler/preset_passmanagers/level0.py | 6 +++-- .../transpiler/preset_passmanagers/level1.py | 6 +++-- .../transpiler/preset_passmanagers/level2.py | 6 +++-- .../transpiler/preset_passmanagers/level3.py | 6 +++-- qiskit/transpiler/target.py | 9 +++++--- .../transpiler/test_preset_passmanagers.py | 19 ++++++++++++++++ 9 files changed, 70 insertions(+), 25 deletions(-) diff --git a/qiskit/transpiler/passes/utils/check_gate_direction.py b/qiskit/transpiler/passes/utils/check_gate_direction.py index 5251bfc8de96..a8ba3d39df43 100644 --- a/qiskit/transpiler/passes/utils/check_gate_direction.py +++ b/qiskit/transpiler/passes/utils/check_gate_direction.py @@ -45,8 +45,14 @@ def run(self, dag): Args: dag (DAGCircuit): DAG to check. """ - self.property_set["is_direction_mapped"] = ( - check_gate_direction_target(dag, self.target) - if self.target - else check_gate_direction_coupling(dag, set(self.coupling_map.get_edges())) - ) + + if self.target is None: + self.property_set["is_direction_mapped"] = check_gate_direction_coupling( + dag, set(self.coupling_map.get_edges()) + ) + elif len(self.target.operation_names) == 0: + self.property_set["is_direction_mapped"] = check_gate_direction_coupling( + dag, set(self.target.build_coupling_map().get_edges()) + ) + else: + self.property_set["is_direction_mapped"] = check_gate_direction_target(dag, self.target) diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index fbdcc9321cbb..1c12ecdb4a5d 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -83,4 +83,9 @@ def run(self, dag): """ if self.target is None: return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges())) + # if target.operation_names is an empty list, extract and use the coupling map + if self.target is not None and len(self.target.operation_names) == 0: + return fix_gate_direction_coupling( + dag, set(self.target.build_coupling_map().get_edges()) + ) return fix_gate_direction_target(dag, self.target) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 4ae01a2549b2..887765d27a3f 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -213,16 +213,19 @@ def generate_unroll_3q( PassManager: The unroll 3q or more pass manager """ unroll_3q = PassManager() - unroll_3q.append( - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - method=unitary_synthesis_method, - min_qubits=3, - plugin_config=unitary_synthesis_plugin_config, - target=target, + if (basis_gates is not None and len(basis_gates) > 0) or ( + target is not None and len(target.operation_names) > 0 + ): + unroll_3q.append( + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + method=unitary_synthesis_method, + min_qubits=3, + plugin_config=unitary_synthesis_plugin_config, + target=target, + ) ) - ) unroll_3q.append( HighLevelSynthesis( hls_config=hls_config, @@ -409,6 +412,7 @@ def _direction_condition(property_set): condition=_direction_condition, ) ) + if remove_reset_in_zero: pre_opt.append(RemoveResetInZeroState()) return pre_opt diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 58381b3db7a8..1ad9b9107a9a 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -73,8 +73,10 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "translation", translation_method, pass_manager_config, optimization_level=0 ) - if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None and target.get_non_global_operation_names(strict_direction=True) + if ( + (coupling_map and not coupling_map.is_symmetric) + or (target is not None and target.get_non_global_operation_names(strict_direction=True)) + or (target is not None and len(target.operation_names) == 0) ): pre_opt = common.generate_pre_op_passmanager(target, coupling_map) pre_opt += translation diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 0a0b1c2a8e43..39f7ec469398 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -78,8 +78,10 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "translation", translation_method, pass_manager_config, optimization_level=1 ) - if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None and target.get_non_global_operation_names(strict_direction=True) + if ( + (coupling_map and not coupling_map.is_symmetric) + or (target is not None and target.get_non_global_operation_names(strict_direction=True)) + or (target is not None and len(target.operation_names) == 0) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index b22743883fbe..815a4cf29068 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -77,8 +77,10 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "translation", translation_method, pass_manager_config, optimization_level=2 ) - if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None and target.get_non_global_operation_names(strict_direction=True) + if ( + (coupling_map and not coupling_map.is_symmetric) + or (target is not None and target.get_non_global_operation_names(strict_direction=True)) + or (target is not None and len(target.operation_names) == 0) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index c1393a0b7c6e..040655f753f3 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -94,8 +94,10 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa optimization = plugin_manager.get_passmanager_stage( "optimization", optimization_method, pass_manager_config, optimization_level=3 ) - if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None and target.get_non_global_operation_names(strict_direction=True) + if ( + (coupling_map and not coupling_map.is_symmetric) + or (target is not None and target.get_non_global_operation_names(strict_direction=True)) + or (target is not None and len(target.operation_names) == 0) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 910be1b35596..3be4e53ba33a 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -24,6 +24,7 @@ from typing import Optional, List, Any from collections.abc import Mapping +import copy import datetime import io import logging @@ -1318,12 +1319,14 @@ def __init__(self, coupling_map=None, **kwargs): super().__init__(**kwargs) if coupling_map is None or isinstance(coupling_map, CouplingMap): self._coupling_map = coupling_map + else: + self._coupling_map = CouplingMap(coupling_map) def __len__(self): return len(self._gate_map) - def build_coupling_map(self, two_q_gate=None, filter_idle_qubits=False): - return self._coupling_map + def build_coupling_map(self, *args, **kwargs): + return copy.deepcopy(self._coupling_map) def instruction_supported(self, *args, **kwargs): """Checks whether an instruction is supported by the @@ -1339,7 +1342,7 @@ def instruction_supported(self, *args, **kwargs): def from_configuration( cls, num_qubits: int | None = None, - coupling_map: CouplingMap | None = None, + coupling_map: CouplingMap | list | None = None, ) -> _FakeTarget: if num_qubits is None and coupling_map is not None: diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 7bc23b01e841..4d6d153fcb8a 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1549,6 +1549,25 @@ def test_parse_seed_transpiler_raises_value_error(self): ): generate_preset_pass_manager(seed_transpiler=0.1) + @data(0, 1) + def test_gate_direction_with_asymmetric_cmap(self, optimization_level): + "Test that GateDirection is run when only an asymmetric coupling_map is provided." + "Only testing optimization levels 0 and 1 to avoid non-determinism in the result due to Sabre" + qc = QuantumCircuit(2, 2) + qc.cx(0, 1) + expected = QuantumCircuit(2, 2) + expected.h(0) + expected.h(1) + expected.cx(1, 0) + expected.h(0) + expected.h(1) + + pm = generate_preset_pass_manager( + optimization_level=optimization_level, coupling_map=[[1, 0]] + ) + tqc = pm.run(qc) + self.assertEqual(tqc, expected) + @ddt class TestIntegrationControlFlow(QiskitTestCase): From 33dc0c4f66f97809010184ba3ce4d291c966faf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 10 Jan 2025 11:51:17 +0100 Subject: [PATCH 14/25] Restore cargo.lock --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74ef042e64d3..133299e5a34a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,9 +558,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -581,7 +581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "rayon", ] From 0008e529fdf0a685b8e57de179c2e5f7587f9b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Fri, 10 Jan 2025 16:25:06 +0100 Subject: [PATCH 15/25] Address most lint complaints --- qiskit/transpiler/preset_passmanagers/level0.py | 10 ++++++---- qiskit/transpiler/preset_passmanagers/level1.py | 10 ++++++---- qiskit/transpiler/preset_passmanagers/level2.py | 10 ++++++---- qiskit/transpiler/preset_passmanagers/level3.py | 10 ++++++---- qiskit/transpiler/target.py | 3 +-- test/python/transpiler/test_preset_passmanagers.py | 5 +++-- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 1ad9b9107a9a..5f20c0943470 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -73,10 +73,12 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "translation", translation_method, pass_manager_config, optimization_level=0 ) - if ( - (coupling_map and not coupling_map.is_symmetric) - or (target is not None and target.get_non_global_operation_names(strict_direction=True)) - or (target is not None and len(target.operation_names) == 0) + if (coupling_map and not coupling_map.is_symmetric) or ( + target is not None + and ( + target.get_non_global_operation_names(strict_direction=True) + or len(target.operation_names) == 0 + ) ): pre_opt = common.generate_pre_op_passmanager(target, coupling_map) pre_opt += translation diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 39f7ec469398..722a3a672327 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -78,10 +78,12 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "translation", translation_method, pass_manager_config, optimization_level=1 ) - if ( - (coupling_map and not coupling_map.is_symmetric) - or (target is not None and target.get_non_global_operation_names(strict_direction=True)) - or (target is not None and len(target.operation_names) == 0) + if (coupling_map and not coupling_map.is_symmetric) or ( + target is not None + and ( + target.get_non_global_operation_names(strict_direction=True) + or len(target.operation_names) == 0 + ) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 815a4cf29068..2f8230476e17 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -77,10 +77,12 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "translation", translation_method, pass_manager_config, optimization_level=2 ) - if ( - (coupling_map and not coupling_map.is_symmetric) - or (target is not None and target.get_non_global_operation_names(strict_direction=True)) - or (target is not None and len(target.operation_names) == 0) + if (coupling_map and not coupling_map.is_symmetric) or ( + target is not None + and ( + target.get_non_global_operation_names(strict_direction=True) + or len(target.operation_names) == 0 + ) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 040655f753f3..9f5627fe1469 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -94,10 +94,12 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa optimization = plugin_manager.get_passmanager_stage( "optimization", optimization_method, pass_manager_config, optimization_level=3 ) - if ( - (coupling_map and not coupling_map.is_symmetric) - or (target is not None and target.get_non_global_operation_names(strict_direction=True)) - or (target is not None and len(target.operation_names) == 0) + if (coupling_map and not coupling_map.is_symmetric) or ( + target is not None + and ( + target.get_non_global_operation_names(strict_direction=True) + or len(target.operation_names) == 0 + ) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 6b09b6dfe965..e0472a97e193 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -1324,8 +1324,7 @@ class _FakeTarget(Target): """ Pseudo-target class for INTERNAL use in the transpilation pipeline. It's essentially an empty :class:`.Target` instance with a a `coupling_map` - argument that allows to store connectivity constraints - without basis gates. + argument that allows to store connectivity constraints without basis gates. This is intended to replace the use of loose constraints in the pipeline. """ diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index bd8e86c55514..a495259b2b4e 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1577,8 +1577,9 @@ def test_parse_seed_transpiler_raises_value_error(self): @data(0, 1) def test_gate_direction_with_asymmetric_cmap(self, optimization_level): - "Test that GateDirection is run when only an asymmetric coupling_map is provided." - "Only testing optimization levels 0 and 1 to avoid non-determinism in the result due to Sabre" + """Test that GateDirection is run when only an asymmetric coupling_map is provided. + Only testing optimization levels 0 and 1 to avoid non-determinism in the result due to Sabre + """ qc = QuantumCircuit(2, 2) qc.cx(0, 1) expected = QuantumCircuit(2, 2) From a46ec06666bc8c7e018f29a3ba2ae8561f408613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Mon, 13 Jan 2025 11:34:09 +0100 Subject: [PATCH 16/25] Fix dt issue --- .../preset_passmanagers/generate_preset_pass_manager.py | 1 + qiskit/transpiler/target.py | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index b36321e09e05..cc8a1c810dfe 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -372,6 +372,7 @@ def generate_preset_pass_manager( target = _FakeTarget.from_configuration( num_qubits=backend.num_qubits if backend is not None else None, coupling_map=coupling_map, + dt=dt, ) # Update target with custom gate information. Note that this is an exception to the priority diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index e0472a97e193..2d17caa6beee 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -1356,12 +1356,11 @@ def from_configuration( cls, num_qubits: int | None = None, coupling_map: CouplingMap | list | None = None, + *args, + **kwargs, ) -> _FakeTarget: if num_qubits is None and coupling_map is not None: num_qubits = len(coupling_map.graph) - return cls( - num_qubits=num_qubits, - coupling_map=coupling_map, - ) + return cls(num_qubits=num_qubits, coupling_map=coupling_map, *args, **kwargs) From 570c1e87e1c0b010cbe9b64dbbbf7d50d54c94d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 14 Jan 2025 13:18:54 +0100 Subject: [PATCH 17/25] Address HLS issue --- qiskit/transpiler/passes/basis/unroll_3q_or_more.py | 1 + qiskit/transpiler/passes/synthesis/high_level_synthesis.py | 6 +++++- test/python/transpiler/test_elide_permutations.py | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py index 885009a23e03..06cb261ea1ea 100644 --- a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py +++ b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py @@ -53,6 +53,7 @@ def run(self, dag): Raises: QiskitError: if a 3q+ gate is not decomposable """ + for node in dag.multi_qubit_ops(): if dag._has_calibration_for(node): continue diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index ac97200d08e8..11fd8fcf8260 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -847,7 +847,11 @@ def _definitely_skip_node( def _instruction_supported(self, name: str, qubits: tuple[int] | None) -> bool: # include path for when target exists but target.num_qubits is None (BasicSimulator) - if self._target is None or self._target.num_qubits is None: + if ( + self._target is None + or self._target.num_qubits is None + or len(self._target.operation_names) == 0 + ): return name in self._device_insts return self._target.instruction_supported(operation_name=name, qargs=qubits) diff --git a/test/python/transpiler/test_elide_permutations.py b/test/python/transpiler/test_elide_permutations.py index edef139af2ee..2ad0f589c591 100644 --- a/test/python/transpiler/test_elide_permutations.py +++ b/test/python/transpiler/test_elide_permutations.py @@ -433,6 +433,7 @@ def test_unitary_equivalence_routing_and_basis_translation(self): self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc))) with self.subTest("larger coupling map"): + spm = generate_preset_pass_manager( optimization_level=3, seed_transpiler=42, From c11219b45be060aef4252e1a3ee05a8b11a95d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 14 Jan 2025 14:33:45 +0100 Subject: [PATCH 18/25] Address GateDirection issue --- qiskit/transpiler/passes/basis/unroll_3q_or_more.py | 1 - 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 | 6 +----- 5 files changed, 4 insertions(+), 21 deletions(-) diff --git a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py index 06cb261ea1ea..885009a23e03 100644 --- a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py +++ b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py @@ -53,7 +53,6 @@ def run(self, dag): Raises: QiskitError: if a 3q+ gate is not decomposable """ - for node in dag.multi_qubit_ops(): if dag._has_calibration_for(node): continue diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 5f20c0943470..58381b3db7a8 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -74,11 +74,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa ) if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None - and ( - target.get_non_global_operation_names(strict_direction=True) - or len(target.operation_names) == 0 - ) + target is not None and target.get_non_global_operation_names(strict_direction=True) ): pre_opt = common.generate_pre_op_passmanager(target, coupling_map) pre_opt += translation diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 722a3a672327..0a0b1c2a8e43 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -79,11 +79,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa ) if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None - and ( - target.get_non_global_operation_names(strict_direction=True) - or len(target.operation_names) == 0 - ) + target is not None and target.get_non_global_operation_names(strict_direction=True) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 2f8230476e17..b22743883fbe 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -78,11 +78,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa ) if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None - and ( - target.get_non_global_operation_names(strict_direction=True) - or len(target.operation_names) == 0 - ) + target is not None and target.get_non_global_operation_names(strict_direction=True) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 9f5627fe1469..c1393a0b7c6e 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -95,11 +95,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "optimization", optimization_method, pass_manager_config, optimization_level=3 ) if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None - and ( - target.get_non_global_operation_names(strict_direction=True) - or len(target.operation_names) == 0 - ) + target is not None and target.get_non_global_operation_names(strict_direction=True) ): pre_optimization = common.generate_pre_op_passmanager( target, coupling_map, remove_reset_in_zero=False From 321ce000502cf9dbfac147780cd0c4cf76911dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Tue, 14 Jan 2025 15:24:14 +0100 Subject: [PATCH 19/25] Fix lint and improve style --- .../passes/synthesis/high_level_synthesis.py | 10 +- .../passes/utils/check_gate_direction.py | 3 +- .../transpiler/passes/utils/gate_direction.py | 5 +- .../preset_passmanagers/builtin_plugins.py | 104 +++++++++--------- .../transpiler/preset_passmanagers/common.py | 50 ++------- qiskit/transpiler/target.py | 4 +- .../transpiler/test_elide_permutations.py | 1 - 7 files changed, 74 insertions(+), 103 deletions(-) diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 11fd8fcf8260..eaf895728c52 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -248,13 +248,14 @@ def __init__( self._basis_gates = basis_gates self._min_qubits = min_qubits - # include cases where target exists with no basis gates, or the basis gates - # are an empty list + # When basis gates haven't been provided as an input, and + # there are no target basis gates (None or empty list), + # treat as "top level only" (no HLS). self._top_level_only = (self._basis_gates is None or len(self._basis_gates) == 0) and ( self._target is None or len(self._target.operation_names) == 0 ) - # include path for when target exists but target.num_qubits is None (BasicSimulator) + # Account for when target exists but target.num_qubits is None (BasicSimulator case) if not self._top_level_only and (self._target is None or self._target.num_qubits is None): basic_insts = {"measure", "reset", "barrier", "snapshot", "delay", "store"} self._device_insts = basic_insts | set(self._basis_gates) @@ -846,7 +847,8 @@ def _definitely_skip_node( ) def _instruction_supported(self, name: str, qubits: tuple[int] | None) -> bool: - # include path for when target exists but target.num_qubits is None (BasicSimulator) + # Do not use target's method when there is no target, target.num_qubits is + # None (BasicSimulator), or target.operation_names is an empty list. if ( self._target is None or self._target.num_qubits is None diff --git a/qiskit/transpiler/passes/utils/check_gate_direction.py b/qiskit/transpiler/passes/utils/check_gate_direction.py index a8ba3d39df43..d5f0db821614 100644 --- a/qiskit/transpiler/passes/utils/check_gate_direction.py +++ b/qiskit/transpiler/passes/utils/check_gate_direction.py @@ -45,7 +45,8 @@ def run(self, dag): Args: dag (DAGCircuit): DAG to check. """ - + # Only use "check_gate_direction_target" if a target exists and target.operation_names + # is not empty, else use "check_gate_direction_coupling". if self.target is None: self.property_set["is_direction_mapped"] = check_gate_direction_coupling( dag, set(self.coupling_map.get_edges()) diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 1c12ecdb4a5d..195b6599bce9 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -81,10 +81,11 @@ def run(self, dag): TranspilerError: If the circuit cannot be mapped just by flipping the cx nodes. """ + # Only use "fix_gate_direction_target" if a target exists and target.operation_names + # is not empty, else use "fix_gate_direction_coupling". if self.target is None: return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges())) - # if target.operation_names is an empty list, extract and use the coupling map - if self.target is not None and len(self.target.operation_names) == 0: + elif len(self.target.operation_names) == 0: return fix_gate_direction_coupling( dag, set(self.target.build_coupling_map().get_edges()) ) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 21a0113542fe..ffc95397e886 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -559,11 +559,12 @@ def _opt_control(property_set): pass_manager_config, optimization_level=optimization_level, ) - if optimization_level == 1: - # Steps for optimization level 1 - # If there are no basis gates (None/empty list), don't run - # Optimize1qGatesDecomposition + # Basic steps for optimization level 1: + # 1. Optimize1qGatesDecomposition (only if basis gates) + # 2. InverseCancellation + if optimization_level == 1: + # Only run Optimize1qGatesDecomposition if there are basis_gates in the config if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 @@ -595,36 +596,39 @@ def _opt_control(property_set): ), ] + # Basic steps for optimization level 2: + # 1. RemoveIdentityEquivalent + # 2. Optimize1qGatesDecomposition (only if basis gates) + # 3. CommutativeCancelation elif optimization_level == 2: - # Steps for optimization level 2 - - # If there are no basis gates (None/empty list), don't run - # Optimize1qGatesDecomposition + _opt = [ + RemoveIdentityEquivalent( + approximation_degree=pass_manager_config.approximation_degree, + target=pass_manager_config.target, + ) + ] + # Only run Optimize1qGatesDecomposition if there are basis_gates in the config if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 ): - _opt = [ - RemoveIdentityEquivalent( - approximation_degree=pass_manager_config.approximation_degree, - target=pass_manager_config.target, - ), + _opt += [ Optimize1qGatesDecomposition( basis=pass_manager_config.basis_gates, target=pass_manager_config.target ), ] - else: - _opt = [ - RemoveIdentityEquivalent( - approximation_degree=pass_manager_config.approximation_degree, - target=pass_manager_config.target, - ) - ] + _opt += [ CommutativeCancellation(target=pass_manager_config.target), ] + + # Basic steps for optimization level 3: + # 1. ConsolidateBlocks + # 2. UnitarySynthesis (only if basis gates) + # 3. RemoveIdentityEquivalent (only if basis gates) + # 4. Optimize1qGatesDecomposition (only if basis gates) + # 5. CommutativeCancelation elif optimization_level == 3: - # Steps for optimization level 3 _opt = [ ConsolidateBlocks( basis_gates=pass_manager_config.basis_gates, @@ -632,8 +636,8 @@ def _opt_control(property_set): approximation_degree=pass_manager_config.approximation_degree, ), ] - # If there are no basis gates (None/empty list), don't run - # Optimize1qGatesDecomposition or UnitarySynthesis + # Only run UnitarySynthesis, RemoveIdentityEquivalent and + # Optimize1qGatesDeecomposition if there are basis_gates in the config if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 @@ -667,11 +671,11 @@ def _opt_control(property_set): unroll = translation.to_flow_controller() - # Build nested Flow controllers + # Build nested flow controllers def _unroll_condition(property_set): return not property_set["all_gates_in_basis"] - # Check if any gate is not in the basis, and if so, run unroll passes + # Check if any gate is not in the basis, and if so, run unroll/translation passes _unroll_if_out_of_basis = [ GatesInBasis(pass_manager_config.basis_gates, target=pass_manager_config.target), ConditionalController(unroll, condition=_unroll_condition), @@ -680,40 +684,30 @@ def _unroll_condition(property_set): if optimization_level == 3: optimization.append(_minimum_point_check) elif optimization_level == 2: - # If there are no basis gates (None/empty list), don't run - # UnitarySynthesis + _extra_opt = [ + ConsolidateBlocks( + basis_gates=pass_manager_config.basis_gates, + target=pass_manager_config.target, + approximation_degree=pass_manager_config.approximation_degree, + ), + ] + # Only run UnitarySynthesis if there are basis_gates in the config if ( pass_manager_config.basis_gates is not None and len(pass_manager_config.basis_gates) > 0 ): - optimization.append( - [ - ConsolidateBlocks( - basis_gates=pass_manager_config.basis_gates, - target=pass_manager_config.target, - approximation_degree=pass_manager_config.approximation_degree, - ), - UnitarySynthesis( - pass_manager_config.basis_gates, - approximation_degree=pass_manager_config.approximation_degree, - coupling_map=pass_manager_config.coupling_map, - backend_props=pass_manager_config.backend_properties, - method=pass_manager_config.unitary_synthesis_method, - plugin_config=pass_manager_config.unitary_synthesis_plugin_config, - target=pass_manager_config.target, - ), - ] - ) - else: - optimization.append( - [ - ConsolidateBlocks( - basis_gates=pass_manager_config.basis_gates, - target=pass_manager_config.target, - approximation_degree=pass_manager_config.approximation_degree, - ), - ] - ) + _extra_opt += [ + UnitarySynthesis( + pass_manager_config.basis_gates, + approximation_degree=pass_manager_config.approximation_degree, + coupling_map=pass_manager_config.coupling_map, + backend_props=pass_manager_config.backend_properties, + method=pass_manager_config.unitary_synthesis_method, + plugin_config=pass_manager_config.unitary_synthesis_plugin_config, + target=pass_manager_config.target, + ), + ] + optimization.append(_extra_opt) optimization.append(_depth_check + _size_check) else: optimization.append(_depth_check + _size_check) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 51428fe8aa59..a2c479257698 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -508,7 +508,7 @@ def generate_translation_passmanager( ] elif method == "synthesis": if target is not None and len(target.operation_names) == 0: - unroll = [ + synth_block = [ HighLevelSynthesis( hls_config=hls_config, coupling_map=coupling_map, @@ -517,28 +517,12 @@ def generate_translation_passmanager( basis_gates=basis_gates, min_qubits=3, qubits_initially_zero=qubits_initially_zero, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - Collect2qBlocks(), - Collect1qRuns(), - ConsolidateBlocks( - basis_gates=basis_gates, - target=target, - approximation_degree=approximation_degree, - ), - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - basis_gates=basis_gates, - qubits_initially_zero=qubits_initially_zero, - ), + ) ] else: - unroll = [ - # # Use unitary synthesis for basis aware decomposition of - # UnitaryGates > 2q before collection + # Use unitary synthesis for basis aware decomposition of + # UnitaryGates > 2q before collection + synth_block = [ UnitarySynthesis( basis_gates, approximation_degree=approximation_degree, @@ -558,6 +542,11 @@ def generate_translation_passmanager( min_qubits=3, qubits_initially_zero=qubits_initially_zero, ), + ] + + unroll = ( + synth_block + + [ Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), Collect1qRuns(), @@ -566,24 +555,9 @@ def generate_translation_passmanager( target=target, approximation_degree=approximation_degree, ), - UnitarySynthesis( - basis_gates=basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_props, - plugin_config=unitary_synthesis_plugin_config, - method=unitary_synthesis_method, - target=target, - ), - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - basis_gates=basis_gates, - qubits_initially_zero=qubits_initially_zero, - ), ] + + synth_block + ) else: raise TranspilerError(f"Invalid translation method {method}.") return PassManager(unroll) diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 2d17caa6beee..a19e17ce71c7 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -1338,7 +1338,7 @@ def __init__(self, coupling_map=None, **kwargs): def __len__(self): return len(self._gate_map) - def build_coupling_map(self, *args, **kwargs): + def build_coupling_map(self, *args, **kwargs): # pylint: disable=unused-argument return copy.deepcopy(self._coupling_map) def instruction_supported(self, *args, **kwargs): @@ -1354,9 +1354,9 @@ def instruction_supported(self, *args, **kwargs): @classmethod def from_configuration( cls, + *args, num_qubits: int | None = None, coupling_map: CouplingMap | list | None = None, - *args, **kwargs, ) -> _FakeTarget: diff --git a/test/python/transpiler/test_elide_permutations.py b/test/python/transpiler/test_elide_permutations.py index 2ad0f589c591..edef139af2ee 100644 --- a/test/python/transpiler/test_elide_permutations.py +++ b/test/python/transpiler/test_elide_permutations.py @@ -433,7 +433,6 @@ def test_unitary_equivalence_routing_and_basis_translation(self): self.assertTrue(Operator.from_circuit(res).equiv(Operator(qc))) with self.subTest("larger coupling map"): - spm = generate_preset_pass_manager( optimization_level=3, seed_transpiler=42, From 92fad86a5ec393c02362377c0dc7a112a0f0dcf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 15 Jan 2025 09:15:32 +0100 Subject: [PATCH 20/25] Refactor plugin logic, handle target with no basis gates at the pass level (Python interface only) instead of pm level. --- .../passes/basis/basis_translator.py | 7 +- .../passes/optimization/consolidate_blocks.py | 4 +- .../optimization/optimize_1q_decomposition.py | 16 +- .../passes/synthesis/unitary_synthesis.py | 7 +- .../preset_passmanagers/builtin_plugins.py | 113 ++++------- .../transpiler/preset_passmanagers/common.py | 184 ++++++++---------- .../generate_preset_pass_manager.py | 2 +- 7 files changed, 141 insertions(+), 192 deletions(-) diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index c6a61b8ad49a..73ff01a9c134 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -98,15 +98,16 @@ def __init__(self, equivalence_library, target_basis, target=None, min_qubits=0) min_qubits (int): The minimum number of qubits for operations in the input dag to translate. """ - super().__init__() self._equiv_lib = equivalence_library self._target_basis = target_basis - self._target = target + # Bypass target if it doesn't contain any basis gates, as this + # not part of the official target model. + self._target = target if target is not None and len(target.operation_names) > 0 else None self._non_global_operations = None self._qargs_with_non_global_operation = {} self._min_qubits = min_qubits - if target is not None: + if self._target is not None: self._non_global_operations = self._target.get_non_global_operation_names() self._qargs_with_non_global_operation = defaultdict(set) for gate in self._non_global_operations: diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index f31401abb6a7..e76aaadf94de 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -69,7 +69,9 @@ def __init__( """ super().__init__() self.basis_gates = None - self.target = target + # Bypass target if it doesn't contain any basis gates, as this + # not part of the official target model. + self.target = target if target is not None and len(target.operation_names) > 0 else None if basis_gates is not None: self.basis_gates = set(basis_gates) self.force_consolidate = force_consolidate diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 55232ac1be20..1652d554547f 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -81,17 +81,19 @@ def __init__(self, basis=None, target=None): """ super().__init__() - if basis: + if basis and len(basis) > 0: self._basis_gates = set(basis) else: self._basis_gates = None - self._target = target + # Bypass target if it doesn't contain any basis gates, as this + # not part of the official target model. + self._target = target if target is not None and len(target.operation_names) > 0 else None self._global_decomposers = None self._local_decomposers_cache = {} - if basis: + if self._basis_gates: self._global_decomposers = _possible_decomposers(set(basis)) - elif target is None: + elif target is None or len(target.operation_names) == 0: self._global_decomposers = _possible_decomposers(None) self._basis_gates = None @@ -115,7 +117,11 @@ def _build_error_map(self): def _get_decomposer(self, qubit=None): # include path for when target exists but target.num_qubits is None (BasicSimulator) - if self._target is not None and self._target.num_qubits is not None: + if ( + self._target is not None + and self._target.num_qubits is not None + and len(self._target.operation_names) > 0 + ): if qubit is not None: qubits_tuple = (qubit,) else: diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index a2bd044c7341..c0c34429da63 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -403,9 +403,11 @@ def __init__( self._pulse_optimize = pulse_optimize self._natural_direction = natural_direction self._plugin_config = plugin_config - self._target = target + # Bypass target if it doesn't contain any basis gates, as this + # not part of the official target model. + self._target = target if target is not None and len(target.operation_names) > 0 else None if target is not None: - self._coupling_map = self._target.build_coupling_map() + self._coupling_map = target.build_coupling_map() if synth_gates: self._synth_gates = synth_gates else: @@ -476,7 +478,6 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: _coupling_edges = ( set(self._coupling_map.get_edges()) if self._coupling_map is not None else set() ) - out = run_default_main_loop( dag, list(qubit_indices.values()), diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index ffc95397e886..1e94781bf1da 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -561,23 +561,14 @@ def _opt_control(property_set): ) # Basic steps for optimization level 1: - # 1. Optimize1qGatesDecomposition (only if basis gates) + # 1. Optimize1qGatesDecomposition # 2. InverseCancellation if optimization_level == 1: - # Only run Optimize1qGatesDecomposition if there are basis_gates in the config - if ( - pass_manager_config.basis_gates is not None - and len(pass_manager_config.basis_gates) > 0 - ): - _opt = [ - Optimize1qGatesDecomposition( - basis=pass_manager_config.basis_gates, target=pass_manager_config.target - ) - ] - else: - _opt = [] - _opt += [ + _opt = [ + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ), InverseCancellation( [ CXGate(), @@ -598,35 +589,25 @@ def _opt_control(property_set): # Basic steps for optimization level 2: # 1. RemoveIdentityEquivalent - # 2. Optimize1qGatesDecomposition (only if basis gates) + # 2. Optimize1qGatesDecomposition # 3. CommutativeCancelation elif optimization_level == 2: _opt = [ RemoveIdentityEquivalent( approximation_degree=pass_manager_config.approximation_degree, target=pass_manager_config.target, - ) - ] - # Only run Optimize1qGatesDecomposition if there are basis_gates in the config - if ( - pass_manager_config.basis_gates is not None - and len(pass_manager_config.basis_gates) > 0 - ): - _opt += [ - Optimize1qGatesDecomposition( - basis=pass_manager_config.basis_gates, target=pass_manager_config.target - ), - ] - - _opt += [ + ), + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ), CommutativeCancellation(target=pass_manager_config.target), ] # Basic steps for optimization level 3: # 1. ConsolidateBlocks - # 2. UnitarySynthesis (only if basis gates) - # 3. RemoveIdentityEquivalent (only if basis gates) - # 4. Optimize1qGatesDecomposition (only if basis gates) + # 2. UnitarySynthesis + # 3. RemoveIdentityEquivalent + # 4. Optimize1qGatesDecomposition # 5. CommutativeCancelation elif optimization_level == 3: _opt = [ @@ -635,33 +616,24 @@ def _opt_control(property_set): target=pass_manager_config.target, approximation_degree=pass_manager_config.approximation_degree, ), + UnitarySynthesis( + pass_manager_config.basis_gates, + approximation_degree=pass_manager_config.approximation_degree, + coupling_map=pass_manager_config.coupling_map, + backend_props=pass_manager_config.backend_properties, + method=pass_manager_config.unitary_synthesis_method, + plugin_config=pass_manager_config.unitary_synthesis_plugin_config, + target=pass_manager_config.target, + ), + RemoveIdentityEquivalent( + approximation_degree=pass_manager_config.approximation_degree, + target=pass_manager_config.target, + ), + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ), + CommutativeCancellation(target=pass_manager_config.target), ] - # Only run UnitarySynthesis, RemoveIdentityEquivalent and - # Optimize1qGatesDeecomposition if there are basis_gates in the config - if ( - pass_manager_config.basis_gates is not None - and len(pass_manager_config.basis_gates) > 0 - ): - _opt += [ - UnitarySynthesis( - pass_manager_config.basis_gates, - approximation_degree=pass_manager_config.approximation_degree, - coupling_map=pass_manager_config.coupling_map, - backend_props=pass_manager_config.backend_properties, - method=pass_manager_config.unitary_synthesis_method, - plugin_config=pass_manager_config.unitary_synthesis_plugin_config, - target=pass_manager_config.target, - ), - RemoveIdentityEquivalent( - approximation_degree=pass_manager_config.approximation_degree, - target=pass_manager_config.target, - ), - Optimize1qGatesDecomposition( - basis=pass_manager_config.basis_gates, target=pass_manager_config.target - ), - ] - - _opt += [CommutativeCancellation(target=pass_manager_config.target)] def _opt_control(property_set): return not property_set["optimization_loop_minimum_point"] @@ -690,23 +662,16 @@ def _unroll_condition(property_set): target=pass_manager_config.target, approximation_degree=pass_manager_config.approximation_degree, ), + UnitarySynthesis( + pass_manager_config.basis_gates, + approximation_degree=pass_manager_config.approximation_degree, + coupling_map=pass_manager_config.coupling_map, + backend_props=pass_manager_config.backend_properties, + method=pass_manager_config.unitary_synthesis_method, + plugin_config=pass_manager_config.unitary_synthesis_plugin_config, + target=pass_manager_config.target, + ), ] - # Only run UnitarySynthesis if there are basis_gates in the config - if ( - pass_manager_config.basis_gates is not None - and len(pass_manager_config.basis_gates) > 0 - ): - _extra_opt += [ - UnitarySynthesis( - pass_manager_config.basis_gates, - approximation_degree=pass_manager_config.approximation_degree, - coupling_map=pass_manager_config.coupling_map, - backend_props=pass_manager_config.backend_properties, - method=pass_manager_config.unitary_synthesis_method, - plugin_config=pass_manager_config.unitary_synthesis_plugin_config, - target=pass_manager_config.target, - ), - ] optimization.append(_extra_opt) optimization.append(_depth_check + _size_check) else: diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index a2c479257698..522cfb1f2513 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -213,19 +213,16 @@ def generate_unroll_3q( PassManager: The unroll 3q or more pass manager """ unroll_3q = PassManager() - if (basis_gates is not None and len(basis_gates) > 0) or ( - target is not None and len(target.operation_names) > 0 - ): - unroll_3q.append( - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - method=unitary_synthesis_method, - min_qubits=3, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ) + unroll_3q.append( + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + method=unitary_synthesis_method, + min_qubits=3, + plugin_config=unitary_synthesis_plugin_config, + target=target, ) + ) unroll_3q.append( HighLevelSynthesis( hls_config=hls_config, @@ -240,12 +237,7 @@ def generate_unroll_3q( ) # If there are no target instructions revert to using unroll3qormore so # routing works. - if ( - basis_gates is None - and target is None - or target is not None - and len(target.operation_names) == 0 - ): + if basis_gates is None and target is None: unroll_3q.append(Unroll3qOrMore(target, basis_gates)) else: unroll_3q.append(BasisTranslator(sel, basis_gates, target=target, min_qubits=3)) @@ -415,7 +407,6 @@ def _direction_condition(property_set): condition=_direction_condition, ) ) - if remove_reset_in_zero: pre_opt.append(RemoveResetInZeroState()) return pre_opt @@ -470,94 +461,77 @@ def generate_translation_passmanager( return PassManager([]) if method == "translator": - if target is not None and len(target.operation_names) == 0: - unroll = [ - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - equivalence_library=sel, - basis_gates=basis_gates, - qubits_initially_zero=qubits_initially_zero, - ), - ] - else: - unroll = [ - # Use unitary synthesis for basis aware decomposition of - # UnitaryGates before custom unrolling - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_props, - plugin_config=unitary_synthesis_plugin_config, - method=unitary_synthesis_method, - target=target, - ), - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - equivalence_library=sel, - basis_gates=basis_gates, - qubits_initially_zero=qubits_initially_zero, - ), - BasisTranslator(sel, basis_gates, target), - ] - elif method == "synthesis": - if target is not None and len(target.operation_names) == 0: - synth_block = [ - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - basis_gates=basis_gates, - min_qubits=3, - qubits_initially_zero=qubits_initially_zero, - ) - ] - else: + unroll = [ # Use unitary synthesis for basis aware decomposition of + # UnitaryGates before custom unrolling + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + target=target, + ), + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + equivalence_library=sel, + basis_gates=basis_gates, + qubits_initially_zero=qubits_initially_zero, + ), + BasisTranslator(sel, basis_gates, target), + ] + + elif method == "synthesis": + unroll = [ + # # Use unitary synthesis for basis aware decomposition of # UnitaryGates > 2q before collection - synth_block = [ - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_props, - plugin_config=unitary_synthesis_plugin_config, - method=unitary_synthesis_method, - min_qubits=3, - target=target, - ), - HighLevelSynthesis( - hls_config=hls_config, - coupling_map=coupling_map, - target=target, - use_qubit_indices=True, - basis_gates=basis_gates, - min_qubits=3, - qubits_initially_zero=qubits_initially_zero, - ), - ] - - unroll = ( - synth_block - + [ - Unroll3qOrMore(target=target, basis_gates=basis_gates), - Collect2qBlocks(), - Collect1qRuns(), - ConsolidateBlocks( - basis_gates=basis_gates, - target=target, - approximation_degree=approximation_degree, - ), - ] - + synth_block - ) + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + min_qubits=3, + target=target, + ), + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + basis_gates=basis_gates, + min_qubits=3, + qubits_initially_zero=qubits_initially_zero, + ), + Unroll3qOrMore(target=target, basis_gates=basis_gates), + Collect2qBlocks(), + Collect1qRuns(), + ConsolidateBlocks( + basis_gates=basis_gates, target=target, approximation_degree=approximation_degree + ), + UnitarySynthesis( + basis_gates=basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + target=target, + ), + HighLevelSynthesis( + hls_config=hls_config, + coupling_map=coupling_map, + target=target, + use_qubit_indices=True, + basis_gates=basis_gates, + qubits_initially_zero=qubits_initially_zero, + ), + ] else: raise TranspilerError(f"Invalid translation method {method}.") return PassManager(unroll) diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index cc8a1c810dfe..a8f317a2b7e3 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -384,7 +384,7 @@ def generate_preset_pass_manager( if target is not None: if coupling_map is None: coupling_map = target.build_coupling_map() - if basis_gates is None: + if basis_gates is None and len(target.operation_names) > 0: basis_gates = target.operation_names if instruction_durations is None: instruction_durations = target.durations() From 1d449ea7eae233eb3369863737ca99ab561dc841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Wed, 5 Mar 2025 17:48:35 +0100 Subject: [PATCH 21/25] Remove deprecated arguments: instruction_durations, timing_constrants and custom basis_gates. Do not skip target unless dictated by backend. Add warning for backend + loose constraints and raise error if basis_gates contains 3q+ gates with a coupling map, as this generates a conflic in the model where it's not clear to which qubits the gates can apply. This is a limitation that comes from Target.from_configuration, but it's more user-friendly to raise the error from generate_preset_pass_manager. --- qiskit/compiler/transpiler.py | 128 ++--------- .../generate_preset_pass_manager.py | 198 ++++++------------ test/python/circuit/library/test_qft.py | 12 +- test/python/circuit/test_scheduled_circuit.py | 162 +++----------- test/python/compiler/test_compiler.py | 18 +- test/python/compiler/test_transpiler.py | 29 +-- .../transpiler/test_preset_passmanagers.py | 73 ++++--- test/python/transpiler/test_solovay_kitaev.py | 4 +- .../visualization/timeline/test_core.py | 69 +++--- 9 files changed, 214 insertions(+), 479 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index a57f740fec4e..d60bd3a4ec56 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -27,35 +27,15 @@ from qiskit.transpiler import Layout, CouplingMap, PropertySet from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget -from qiskit.transpiler.instruction_durations import InstructionDurationsType from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.transpiler.target import Target -from qiskit.utils import deprecate_arg logger = logging.getLogger(__name__) _CircuitT = TypeVar("_CircuitT", bound=Union[QuantumCircuit, List[QuantumCircuit]]) -@deprecate_arg( - name="instruction_durations", - since="1.3", - package_name="Qiskit", - removal_timeline="in Qiskit 2.0", - additional_msg="The `target` parameter should be used instead. You can build a `Target` instance " - "with defined instruction durations with " - "`Target.from_configuration(..., instruction_durations=...)`", -) -@deprecate_arg( - name="timing_constraints", - since="1.3", - package_name="Qiskit", - removal_timeline="in Qiskit 2.0", - additional_msg="The `target` parameter should be used instead. You can build a `Target` instance " - "with defined timing constraints with " - "`Target.from_configuration(..., timing_constraints=...)`", -) def transpile( # pylint: disable=too-many-return-statements circuits: _CircuitT, backend: Optional[Backend] = None, @@ -66,10 +46,8 @@ def transpile( # pylint: disable=too-many-return-statements 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] = 1.0, - timing_constraints: Optional[Dict[str, int]] = None, seed_transpiler: Optional[int] = None, optimization_level: Optional[int] = None, callback: Optional[Callable[[BasePass, DAGCircuit, float, PropertySet, int], Any]] = None, @@ -92,8 +70,8 @@ def transpile( # pylint: disable=too-many-return-statements The prioritization of transpilation target constraints works as follows: if a ``target`` input is provided, it will take priority over any ``backend`` input or loose constraints - (``basis_gates``, ``coupling_map``, ``instruction_durations``, - ``dt`` or ``timing_constraints``). If a ``backend`` is provided together with any loose constraint + (``basis_gates``, ``coupling_map``, or ``dt``). If a ``backend`` is provided + together with any loose constraint from the list above, the loose constraint will take priority over the corresponding backend constraint. This behavior is independent of whether the ``backend`` instance is of type :class:`.BackendV1` or :class:`.BackendV2`, as summarized in the table below. The first column @@ -106,9 +84,7 @@ def transpile( # pylint: disable=too-many-return-statements ============================ ========= ======================== ======================= **basis_gates** target basis_gates basis_gates **coupling_map** target coupling_map coupling_map - **instruction_durations** target instruction_durations instruction_durations **dt** target dt dt - **timing_constraints** target timing_constraints timing_constraints ============================ ========= ======================== ======================= Args: @@ -179,40 +155,10 @@ def transpile( # pylint: disable=too-many-return-statements to use for the ``scheduling`` stage. You can see a list of installed plugins by using :func:`~.list_stage_plugins` with ``"scheduling"`` for the ``stage_name`` argument. - instruction_durations: Durations of instructions. - Applicable only if scheduling_method is specified. - The gate lengths defined in ``backend.properties`` are used as default. - They are overwritten if this ``instruction_durations`` is specified. - The format of ``instruction_durations`` must be as follows. - The `instruction_durations` must be given as a list of tuples - [(instruction_name, qubits, duration, unit), ...]. - | [('cx', [0, 1], 12.3, 'ns'), ('u3', [0], 4.56, 'ns')] - | [('cx', [0, 1], 1000), ('u3', [0], 300)] - If unit is omitted, the default is 'dt', which is a sample time depending on backend. - If the time unit is 'dt', the duration must be an integer. dt: Backend sample time (resolution) in seconds. If ``None`` (default), ``backend.dt`` is used. approximation_degree (float): heuristic dial used for circuit approximation (1.0=no approximation, 0.0=maximal approximation) - timing_constraints: An optional control hardware restriction on instruction time resolution. - A quantum computer backend may report a set of restrictions, namely: - - - granularity: An integer value representing minimum pulse gate - resolution in units of ``dt``. A user-defined pulse gate should have - duration of a multiple of this granularity value. - - min_length: An integer value representing minimum pulse gate - length in units of ``dt``. A user-defined pulse gate should be longer - than this length. - - pulse_alignment: An integer value representing a time resolution of gate - instruction starting time. Gate instruction should start at time which - is a multiple of the alignment value. - - acquire_alignment: An integer value representing a time resolution of measure - instruction starting time. Measure instruction should start at time which - is a multiple of the alignment value. - - This information will be provided by the backend configuration. - If the backend doesn't have any restriction on the instruction time allocation, - then ``timing_constraints`` is None and no adjustment will be performed. seed_transpiler: Sets random seed for the stochastic parts of the transpiler optimization_level: How much optimization to perform on the circuits. Higher levels generate more optimized circuits, @@ -344,18 +290,6 @@ def callback_func(**kwargs): ) backend = BackendV2Converter(backend) - if ( - scheduling_method is not None - and backend is None - and target is None - and not instruction_durations - ): - warnings.warn( - "When scheduling circuits without backend," - " 'instruction_durations' should be usually provided.", - UserWarning, - ) - if not ignore_backend_supplied_default_methods: if scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"): scheduling_method = backend.get_scheduling_stage_plugin() @@ -369,43 +303,27 @@ def callback_func(**kwargs): # Edge cases require using the old model (loose constraints) instead of building a target, # but we don't populate the passmanager config with loose constraints unless it's one of # the known edge cases to control the execution path. - # Filter instruction_durations and timing_constraints deprecation - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=".*``timing_constraints`` is deprecated as of Qiskit 1.3.*", - module="qiskit", - ) - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=".*``instruction_durations`` is deprecated as of Qiskit 1.3.*", - module="qiskit", - ) - pm = generate_preset_pass_manager( - optimization_level, - target=target, - backend=backend, - basis_gates=basis_gates, - coupling_map=coupling_map, - instruction_durations=instruction_durations, - timing_constraints=timing_constraints, - initial_layout=initial_layout, - layout_method=layout_method, - routing_method=routing_method, - translation_method=translation_method, - scheduling_method=scheduling_method, - approximation_degree=approximation_degree, - seed_transpiler=seed_transpiler, - unitary_synthesis_method=unitary_synthesis_method, - unitary_synthesis_plugin_config=unitary_synthesis_plugin_config, - hls_config=hls_config, - init_method=init_method, - optimization_method=optimization_method, - dt=dt, - qubits_initially_zero=qubits_initially_zero, - ) + pm = generate_preset_pass_manager( + optimization_level, + target=target, + backend=backend, + basis_gates=basis_gates, + coupling_map=coupling_map, + initial_layout=initial_layout, + layout_method=layout_method, + routing_method=routing_method, + translation_method=translation_method, + scheduling_method=scheduling_method, + approximation_degree=approximation_degree, + seed_transpiler=seed_transpiler, + unitary_synthesis_method=unitary_synthesis_method, + unitary_synthesis_plugin_config=unitary_synthesis_plugin_config, + hls_config=hls_config, + init_method=init_method, + optimization_method=optimization_method, + dt=dt, + qubits_initially_zero=qubits_initially_zero, + ) out_circuits = pm.run(circuits, callback=callback, num_processes=num_processes) diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 9dc659f2c6d4..9f80525eda4f 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -28,7 +28,6 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.target import Target, _FakeTarget from qiskit.transpiler.timing_constraints import TimingConstraints -from qiskit.utils import deprecate_arg from .level0 import level_0_pass_manager from .level1 import level_1_pass_manager @@ -36,32 +35,15 @@ from .level3 import level_3_pass_manager -@deprecate_arg( - name="instruction_durations", - since="1.3", - package_name="Qiskit", - removal_timeline="in Qiskit 2.0", - additional_msg="The `target` parameter should be used instead. You can build a `Target` instance " - "with defined instruction durations with " - "`Target.from_configuration(..., instruction_durations=...)`", -) -@deprecate_arg( - name="timing_constraints", - since="1.3", - package_name="Qiskit", - removal_timeline="in Qiskit 2.0", - additional_msg="The `target` parameter should be used instead. You can build a `Target` instance " - "with defined timing constraints with " - "`Target.from_configuration(..., timing_constraints=...)`", -) +OVER_3Q_GATES = ["ccx", "ccz", "cswap", "rccx", "c3x", "c3sx", "rc3x"] + + def generate_preset_pass_manager( optimization_level=2, backend=None, target=None, basis_gates=None, coupling_map=None, - instruction_durations=None, - timing_constraints=None, initial_layout=None, layout_method=None, routing_method=None, @@ -89,8 +71,7 @@ def generate_preset_pass_manager( The target constraints for the pass manager construction can be specified through a :class:`.Target` instance, a :class:`.BackendV1` or :class:`.BackendV2` instance, or via loose constraints - (``basis_gates``, ``coupling_map``, ``instruction_durations``, - ``dt`` or ``timing_constraints``). + (``basis_gates``, ``coupling_map``, or ``dt``). The order of priorities for target constraints works as follows: if a ``target`` input is provided, it will take priority over any ``backend`` input or loose constraints. If a ``backend`` is provided together with any loose constraint @@ -106,9 +87,7 @@ def generate_preset_pass_manager( ============================ ========= ======================== ======================= **basis_gates** target basis_gates basis_gates **coupling_map** target coupling_map coupling_map - **instruction_durations** target instruction_durations instruction_durations **dt** target dt dt - **timing_constraints** target timing_constraints timing_constraints ============================ ========= ======================== ======================= Args: @@ -125,14 +104,12 @@ def generate_preset_pass_manager( backend (Backend): An optional backend object which can be used as the source of the default values for the ``basis_gates``, - ``coupling_map``, ``instruction_durations``, - ``timing_constraints``, and ``target``. If any of those other arguments + ``coupling_map``, and ``target``. If any of those other arguments are specified in addition to ``backend`` they will take precedence over the value contained in the backend. target (Target): The :class:`~.Target` representing a backend compilation target. The following attributes will be inferred from this - argument if they are not set: ``coupling_map``, ``basis_gates``, - ``instruction_durations`` and ``timing_constraints``. + argument if they are not set: ``coupling_map`` and ``basis_gates``. basis_gates (list): List of basis gate names to unroll to (e.g: ``['u1', 'u2', 'u3', 'cx']``). coupling_map (CouplingMap or list): Directed graph represented a coupling @@ -142,45 +119,8 @@ def generate_preset_pass_manager( #. List, must be given as an adjacency matrix, where each entry specifies all directed two-qubit interactions supported by backend, e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]`` - - instruction_durations (InstructionDurations or list): Dictionary of duration - (in dt) for each instruction. If specified, these durations overwrite the - gate lengths in ``backend.properties``. Applicable only if ``scheduling_method`` - is specified. - The format of ``instruction_durations`` must be as follows: - They must be given as an :class:`.InstructionDurations` instance or a list of tuples - - ``` - [(instruction_name, qubits, duration, unit), ...]. - | [('cx', [0, 1], 12.3, 'ns'), ('u3', [0], 4.56, 'ns')] - | [('cx', [0, 1], 1000), ('u3', [0], 300)] - ``` - - If ``unit`` is omitted, the default is ``'dt'``, which is a sample time depending on backend. - If the time unit is ``'dt'``, the duration must be an integer. dt (float): Backend sample time (resolution) in seconds. - If provided, this value will overwrite the ``dt`` value in ``instruction_durations``. If ``None`` (default) and a backend is provided, ``backend.dt`` is used. - timing_constraints (TimingConstraints): Hardware time alignment restrictions. - A quantum computer backend may report a set of restrictions, namely: - - - granularity: An integer value representing minimum pulse gate - resolution in units of ``dt``. A user-defined pulse gate should have - duration of a multiple of this granularity value. - - min_length: An integer value representing minimum pulse gate - length in units of ``dt``. A user-defined pulse gate should be longer - than this length. - - pulse_alignment: An integer value representing a time resolution of gate - instruction starting time. Gate instruction should start at time which - is a multiple of the alignment value. - - acquire_alignment: An integer value representing a time resolution of measure - instruction starting time. Measure instruction should start at time which - is a multiple of the alignment value. - - This information will be provided by the backend configuration. - If the backend doesn't have any restriction on the instruction time allocation, - then ``timing_constraints`` is None and no adjustment will be performed. - initial_layout (Layout | List[int]): Initial position of virtual qubits on physical qubits. layout_method (str): The :class:`~.Pass` to use for choosing initial qubit @@ -274,35 +214,42 @@ def generate_preset_pass_manager( backend = BackendV2Converter(backend) # If there are no loose constraints => use backend target if available - _no_loose_constraints = ( - basis_gates is None - and coupling_map is None - and dt is None - and instruction_durations is None - and timing_constraints is None - ) - # If it's an edge case => do not build target - # NOTE (1.3.0): we are skipping the target in the case where - # instruction_durations is provided without additional constraints - # instead of providing a target-based alternative because the argument - # will be removed in 2.0 as part of the Pulse deprecation efforts. - _skip_target = target is None and backend is None and instruction_durations is not None + _no_loose_constraints = basis_gates is None and coupling_map is None and dt is None + + # Warn about inconsistencies in backend + loose constraints path (dt shouldn't be a problem) + if backend is not None and (coupling_map is not None or basis_gates is not None): + warnings.warn( + "Providing `coupling_map` and/or `basis_gates` along with `backend` is not " + "recommended. This may introduce inconsistencies in the transpilation target, " + "leading to potential errors.", + category=UserWarning, + stacklevel=2, + ) # Resolve loose constraints case-by-case against backend constraints. # The order of priority is loose constraints > backend. dt = _parse_dt(dt, backend) - instruction_durations = _parse_instruction_durations(backend, instruction_durations, dt) - timing_constraints = _parse_timing_constraints(backend, timing_constraints) - # The basis gates parser will set _skip_target to True if a custom basis gate is found - # (known edge case). - basis_gates, name_mapping, _skip_target = _parse_basis_gates(basis_gates, backend, _skip_target) + instruction_durations = _parse_instruction_durations(backend, dt) + timing_constraints = _parse_timing_constraints(backend) coupling_map = _parse_coupling_map(coupling_map, backend) + basis_gates, name_mapping = _parse_basis_gates(basis_gates, backend) + + # Check if coupling map has been provided (either standalone or through backend) + # with user-defined basis_gates, and whether these have 3q or more. + if coupling_map is not None and basis_gates is not None: + for gate in OVER_3Q_GATES: + if gate in basis_gates: + raise ValueError( + f"Gates with 3 or more qubits ({gate}) in `basis_gates` or `backend` are " + "incompatible with a custom `coupling_map`. To include 3-qubit or larger " + " gates in the transpilation basis, use a custom `target` instance instead." + ) if target is None: if backend is not None and _no_loose_constraints: # If a backend is specified without loose constraints, use its target directly. target = backend.target - elif not _skip_target: + else: if basis_gates is not None: # Build target from constraints. target = Target.from_configuration( @@ -324,15 +271,15 @@ def generate_preset_pass_manager( dt=dt, ) - if target is not None: - if coupling_map is None: - coupling_map = target.build_coupling_map() - if basis_gates is None and len(target.operation_names) > 0: - basis_gates = target.operation_names - if instruction_durations is None: - instruction_durations = target.durations() - if timing_constraints is None: - timing_constraints = target.timing_constraints() + # update loose constraints to populate pm options + if coupling_map is None: + coupling_map = target.build_coupling_map() + if basis_gates is None and len(target.operation_names) > 0: + basis_gates = target.operation_names + if instruction_durations is None: + instruction_durations = target.durations() + if timing_constraints is None: + timing_constraints = target.timing_constraints() # Parse non-target dependent pm options initial_layout = _parse_initial_layout(initial_layout) @@ -378,7 +325,7 @@ def generate_preset_pass_manager( return pm -def _parse_basis_gates(basis_gates, backend, skip_target): +def _parse_basis_gates(basis_gates, backend): standard_gates = get_standard_gate_name_mapping() # Add control flow gates by default to basis set and name mapping default_gates = {"measure", "delay", "reset"}.union(CONTROL_FLOW_OP_NAMES) @@ -394,23 +341,18 @@ def _parse_basis_gates(basis_gates, backend, skip_target): if backend is None: # Check for custom instructions if instructions is None: - return None, name_mapping, skip_target + return None, name_mapping for inst in instructions: if inst not in standard_gates and inst not in default_gates: - warnings.warn( - category=DeprecationWarning, - message=f"Providing non-standard gates ({inst}) through the ``basis_gates`` " - "argument is deprecated for both ``transpile`` and ``generate_preset_pass_manager`` " - "as of Qiskit 1.3.0. " - "It will be removed in Qiskit 2.0. The ``target`` parameter should be used instead. " + raise ValueError( + f"Providing non-standard gates ({inst}) through the ``basis_gates`` " + "argument is not allowed. Use the ``target`` parameter instead. " "You can build a target instance using ``Target.from_configuration()`` and provide " - "custom gate definitions with the ``custom_name_mapping`` argument.", + "custom gate definitions with the ``custom_name_mapping`` argument." ) - skip_target = True - break - return list(instructions), name_mapping, skip_target + return list(instructions), name_mapping instructions = instructions or backend.operation_names name_mapping.update( @@ -421,20 +363,16 @@ def _parse_basis_gates(basis_gates, backend, skip_target): for inst in instructions: if inst not in standard_gates and inst not in default_gates: if inst not in backend.operation_names: - # do not raise warning when the custom instruction comes from the backend + # do not raise error when the custom instruction comes from the backend # (common case with BasicSimulator) - warnings.warn( - category=DeprecationWarning, - message="Providing custom gates through the ``basis_gates`` argument is deprecated " - "for both ``transpile`` and ``generate_preset_pass_manager`` as of Qiskit 1.3.0. " - "It will be removed in Qiskit 2.0. The ``target`` parameter should be used instead. " - "You can build a target instance using ``Target.from_configuration()`` and provide" - "custom gate definitions with the ``custom_name_mapping`` argument.", + raise ValueError( + f"Providing non-standard gates ({inst}) through the ``basis_gates`` " + "argument is not allowed. Use the ``target`` parameter instead. " + "You can build a target instance using ``Target.from_configuration()`` and provide " + "custom gate definitions with the ``custom_name_mapping`` argument." ) - skip_target = True - break - return list(instructions) if instructions else None, name_mapping, skip_target + return list(instructions) if instructions else None, name_mapping def _parse_dt(dt, backend): @@ -462,28 +400,20 @@ def _parse_coupling_map(coupling_map, backend): ) -def _parse_instruction_durations(backend, inst_durations, dt): - """Create a list of ``InstructionDuration``s. If ``inst_durations`` is provided, - the backend will be ignored, otherwise, the durations will be populated from the - backend. - """ +def _parse_instruction_durations(backend, dt): + """Create a list of ``InstructionDuration``s populated from the backend.""" final_durations = InstructionDurations() - if not inst_durations: - backend_durations = InstructionDurations() - if backend is not None: - backend_durations = backend.instruction_durations - final_durations.update(backend_durations, dt or backend_durations.dt) - else: - final_durations.update(inst_durations, dt or getattr(inst_durations, "dt", None)) + backend_durations = InstructionDurations() + if backend is not None: + backend_durations = backend.instruction_durations + final_durations.update(backend_durations, dt or backend_durations.dt) return final_durations -def _parse_timing_constraints(backend, timing_constraints): - if isinstance(timing_constraints, TimingConstraints): - return timing_constraints - if backend is None and timing_constraints is None: +def _parse_timing_constraints(backend): + if backend is None: timing_constraints = TimingConstraints() - elif backend is not None: + else: timing_constraints = backend.target.timing_constraints() return timing_constraints diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index 0c39fd9cc4f6..417df30b7582 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -127,18 +127,22 @@ def test_qft_mutability(self): @data( (4, 0, False), - (3, 0, True), - (6, 2, False), - (4, 5, True), + # (3, 0, True), + # (6, 2, False), + # (4, 5, True), ) @unpack def test_qft_num_gates(self, num_qubits, approximation_degree, insert_barriers): """Test the number of gates in the QFT and the approximated QFT.""" basis_gates = ["h", "swap", "cu1"] - qft = QFT( num_qubits, approximation_degree=approximation_degree, insert_barriers=insert_barriers ) + print(qft.decompose()) + print(qft.decompose(reps=2)) + # from qiskit.providers.fake_provider import GenericBackendV2 + # backend = GenericBackendV2(num_qubits=num_qubits, basis_gates = ["h", "swap", "cu1"]) + # ops = transpile(qft, backend=backend, optimization_level=1).count_ops() ops = transpile(qft, basis_gates=basis_gates, optimization_level=1).count_ops() with self.subTest(msg="assert H count"): diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index d4cbcbc69247..a2eeff46560a 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -19,11 +19,12 @@ from qiskit import transpile from qiskit.circuit import Parameter from qiskit.circuit.duration import convert_durations_to_dt +from qiskit.circuit.library import CXGate, HGate +from qiskit.circuit.delay import Delay from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.providers.basic_provider import BasicSimulator -from qiskit.transpiler import InstructionProperties +from qiskit.transpiler import InstructionProperties, Target from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.instruction_durations import InstructionDurations from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -153,40 +154,39 @@ def test_transpile_delay_circuit_with_backend(self): target_durations = self.backend_with_dt.target.durations() self.assertEqual(scheduled.duration, target_durations.get("cx", (0, 1)) + 450) - def test_transpile_delay_circuit_without_backend(self): - qc = QuantumCircuit(2) - qc.h(0) - qc.delay(500, 1) - qc.cx(0, 1) - with self.assertWarnsRegex( - DeprecationWarning, "argument ``instruction_durations`` is deprecated" - ): - scheduled = transpile( - qc, - scheduling_method="alap", - basis_gates=["h", "cx"], - instruction_durations=[("h", 0, 200), ("cx", [0, 1], 700)], - dt=1e-7, - ) - self.assertEqual(scheduled.duration, 1200) - def test_transpile_circuit_with_custom_instruction(self): """See: https://github.com/Qiskit/qiskit-terra/issues/5154""" bell = QuantumCircuit(2, name="bell") bell.h(0) bell.cx(0, 1) + bell_instr = bell.to_instruction() qc = QuantumCircuit(2) qc.delay(500, 1) - qc.append(bell.to_instruction(), [0, 1]) - with self.assertWarnsRegex( - DeprecationWarning, "argument ``instruction_durations`` is deprecated" - ): - scheduled = transpile( - qc, - scheduling_method="alap", - instruction_durations=[("bell", [0, 1], 1000)], - dt=1e-2, - ) + qc.append(bell_instr, [0, 1]) + + print(qc) + target = Target(num_qubits=2) + target.add_instruction(CXGate(), {(0, 1): InstructionProperties(0)}) + target.add_instruction( + HGate(), {(0,): InstructionProperties(0), (1,): InstructionProperties(0)} + ) + target.add_instruction(Delay(Parameter("t")), {(0,): None, (1,): None}) + target.add_instruction( + bell_instr, + { + (0, 1): InstructionProperties(1000 * 1e-2), + (1, 0): InstructionProperties(1000 * 1e-2), + }, + ) + target.dt = 1e-2 + print(target) + scheduled = transpile( + qc, + scheduling_method="alap", + target=target, + dt=1e-2, + ) + print(scheduled) self.assertEqual(scheduled.duration, 1500) def test_transpile_delay_circuit_with_dt_but_without_scheduling_method(self): @@ -222,38 +222,6 @@ def test_invalidate_schedule_circuit_if_new_instruction_is_appended(self): scheduled.h(0) self.assertEqual(scheduled.duration, None) - def test_default_units_for_my_own_duration_users(self): - qc = QuantumCircuit(2) - qc.h(0) - qc.delay(500, 1) - qc.cx(0, 1) - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - # accept None for qubits - scheduled = transpile( - qc, - basis_gates=["h", "cx", "delay"], - scheduling_method="alap", - instruction_durations=[("h", 0, 200), ("cx", None, 900)], - dt=1e-6, - ) - self.assertEqual(scheduled.duration, 1400) - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - # prioritize specified qubits over None - scheduled = transpile( - qc, - basis_gates=["h", "cx", "delay"], - scheduling_method="alap", - instruction_durations=[("h", 0, 200), ("cx", None, 900), ("cx", [0, 1], 800)], - dt=1e-7, - ) - self.assertEqual(scheduled.duration, 1300) - def test_unit_seconds_when_using_backend_durations(self): qc = QuantumCircuit(2) qc.h(0) @@ -266,78 +234,6 @@ def test_unit_seconds_when_using_backend_durations(self): target_durations = self.backend_with_dt.target.durations() self.assertEqual(scheduled.duration, target_durations.get("cx", (0, 1)) + 500) - # update durations - durations = InstructionDurations.from_backend(self.backend_with_dt) - durations.update([("cx", [0, 1], 1000 * self.dt, "s")]) - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - scheduled = transpile( - qc, - backend=self.backend_with_dt, - scheduling_method="alap", - instruction_durations=durations, - layout_method="trivial", - ) - self.assertEqual(scheduled.duration, 1500) - - def test_per_qubit_durations_loose_constraint(self): - """See Qiskit/5109 and Qiskit/13306""" - qc = QuantumCircuit(3) - qc.h(0) - qc.delay(500, 1) - qc.cx(0, 1) - qc.h(1) - - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - sc = transpile( - qc, - scheduling_method="alap", - basis_gates=["h", "cx"], - instruction_durations=[("h", None, 200), ("cx", [0, 1], 700)], - dt=1e-7, - ) - self.assertEqual(sc.qubit_start_time(0), 300) - self.assertEqual(sc.qubit_stop_time(0), 1200) - self.assertEqual(sc.qubit_start_time(1), 500) - self.assertEqual(sc.qubit_stop_time(1), 1400) - self.assertEqual(sc.qubit_start_time(2), 0) - self.assertEqual(sc.qubit_stop_time(2), 0) - self.assertEqual(sc.qubit_start_time(0, 1), 300) - self.assertEqual(sc.qubit_stop_time(0, 1), 1400) - self.assertEqual(sc.qubit_stop_time(0, 1, 2), 1400) - - qc.measure_all() - - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - sc = transpile( - qc, - scheduling_method="alap", - basis_gates=["h", "cx", "measure"], - instruction_durations=[ - ("h", None, 200), - ("cx", [0, 1], 700), - ("measure", None, 1000), - ], - dt=1e-8, - ) - q = sc.qubits - self.assertEqual(sc.qubit_start_time(q[0]), 300) - self.assertEqual(sc.qubit_stop_time(q[0]), 2400) - self.assertEqual(sc.qubit_start_time(q[1]), 500) - self.assertEqual(sc.qubit_stop_time(q[1]), 2400) - self.assertEqual(sc.qubit_start_time(q[2]), 1400) - self.assertEqual(sc.qubit_stop_time(q[2]), 2400) - self.assertEqual(sc.qubit_start_time(*q), 300) - self.assertEqual(sc.qubit_stop_time(*q), 2400) - def test_per_qubit_durations(self): """Test target with custom instruction_durations""" target = GenericBackendV2( diff --git a/test/python/compiler/test_compiler.py b/test/python/compiler/test_compiler.py index 40dff32c681f..7801341d2ab8 100644 --- a/test/python/compiler/test_compiler.py +++ b/test/python/compiler/test_compiler.py @@ -64,7 +64,7 @@ def test_example_multiple_compile(self): bell.measure(qr[1], cr[1]) shots = 2048 bell_qcirc = transpile(bell, backend=backend) - ghz_qcirc = transpile(ghz, backend=backend, coupling_map=coupling_map) + ghz_qcirc = transpile(ghz, coupling_map=coupling_map) bell_result = backend.run(bell_qcirc, shots=shots, seed_simulator=10).result() ghz_result = backend.run(ghz_qcirc, shots=shots, seed_simulator=10).result() @@ -96,9 +96,7 @@ def test_compile_coupling_map(self): shots = 2048 coupling_map = [[0, 1], [1, 2]] initial_layout = [0, 1, 2] - qc_b = transpile( - qc, backend=backend, coupling_map=coupling_map, initial_layout=initial_layout - ) + qc_b = transpile(qc, coupling_map=coupling_map, initial_layout=initial_layout) job = backend.run(qc_b, shots=shots, seed_simulator=88) result = job.result() qasm_to_check = dumps(qc) @@ -175,12 +173,12 @@ def test_example_swap_bits(self): qc.measure(qr1[j], ans[j + n]) # First version: no mapping result = backend.run( - transpile(qc, backend, coupling_map=None), shots=1024, seed_simulator=14 + transpile(qc, coupling_map=None), shots=1024, seed_simulator=14 ).result() self.assertEqual(result.get_counts(qc), {"010000": 1024}) # Second version: map to coupling graph result = backend.run( - transpile(qc, backend, coupling_map=coupling_map), + transpile(qc, coupling_map=coupling_map), shots=1024, seed_simulator=14, ).result() @@ -302,7 +300,7 @@ def test_mapper_overoptimization(self): shots = 1000 result1 = self.backend.run( - transpile(circ, backend=self.backend, coupling_map=coupling_map, seed_transpiler=8), + transpile(circ, coupling_map=coupling_map, seed_transpiler=8), seed_simulator=self.seed_simulator, shots=shots, ) @@ -310,7 +308,6 @@ def test_mapper_overoptimization(self): result2 = self.backend.run( transpile( circ, - backend=self.backend, coupling_map=None, seed_transpiler=8, ), @@ -401,7 +398,7 @@ def test_math_domain_error(self): # ┌───┐┌───┐ ┌─┐ # q0_0: ┤ Y ├┤ X ├─────┤M├───────────────────── # └───┘└─┬─┘ └╥┘ ┌─┐ - # q0_1: ───────■────────╫─────────────■──┤M├─── + # q0_1: ───────■────────╫─────────────■──┤M├─── This constructor method only supports fixed width operations # ┌───┐┌───┐┌───┐ ║ ┌───┐┌───┐┌─┴─┐└╥┘┌─┐ # q0_2: ┤ Z ├┤ H ├┤ Y ├─╫─┤ T ├┤ Z ├┤ X ├─╫─┤M├ # └┬─┬┘└───┘└───┘ ║ └───┘└───┘└───┘ ║ └╥┘ @@ -430,7 +427,6 @@ def test_math_domain_error(self): job = self.backend.run( transpile( circ, - backend=self.backend, coupling_map=coupling_map, ), seed_simulator=self.seed_simulator, @@ -449,7 +445,7 @@ def test_random_parameter_circuit(self): coupling_map.make_symmetric() shots = 1024 qobj = self.backend.run( - transpile(circ, backend=self.backend, coupling_map=coupling_map, seed_transpiler=42), + transpile(circ, coupling_map=coupling_map, seed_transpiler=42), shots=shots, seed_simulator=self.seed_simulator, ) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 61cf3c60a684..3f75c9ee260a 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1299,19 +1299,22 @@ def test_circuit_with_delay(self, optimization_level): qc.delay(500, 1) qc.cx(0, 1) - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - out = transpile( - qc, - scheduling_method="alap", - basis_gates=["h", "cx"], - instruction_durations=[("h", 0, 200), ("cx", [0, 1], 700)], - dt=1e-9, - optimization_level=optimization_level, - seed_transpiler=42, - ) + dt = 1e-9 + backend = GenericBackendV2( + 2, coupling_map=[[0, 1]], basis_gates=["cx", "h"], seed=42, dt=dt + ) + # update durations + backend.target.update_instruction_properties("cx", (0, 1), InstructionProperties(700 * dt)) + backend.target.update_instruction_properties("h", (0,), InstructionProperties(200 * dt)) + + out = transpile( + qc, + scheduling_method="alap", + target=backend.target, + dt=1e-9, + optimization_level=optimization_level, + seed_transpiler=42, + ) self.assertEqual(out.duration, 1200) diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 18e10c2ba3a2..5c0de7bfb184 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -39,7 +39,7 @@ ) from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.converters import circuit_to_dag -from qiskit.circuit.library import GraphStateGate +from qiskit.circuit.library import GraphStateGate, UnitaryGate from qiskit.quantum_info import random_unitary from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.transpiler.preset_passmanagers import level0, level1, level2, level3 @@ -163,16 +163,18 @@ def test_level0_keeps_reset(self): self.assertEqual(result, circuit) @combine(level=[0, 1, 2, 3], name="level{level}") - def test_unitary_is_preserved_if_in_basis(self, level): + def test_unitary_is_preserved_if_in_basis_lala(self, level): """Test that a unitary is not synthesized if in the basis.""" qc = QuantumCircuit(2) - qc.unitary(random_unitary(4, seed=42), [0, 1]) + ugate = UnitaryGate(random_unitary(4, seed=42)) + qc.append(ugate, [0, 1]) qc.measure_all() - with self.assertWarnsRegex( - DeprecationWarning, - "Providing non-standard gates \\(unitary\\) through the ``basis_gates`` argument", - ): - result = transpile(qc, basis_gates=["cx", "u", "unitary"], optimization_level=level) + target = Target.from_configuration( + num_qubits=2, + basis_gates=["cx", "u", "unitary", "measure"], + custom_name_mapping={"unitary": ugate}, + ) + result = transpile(qc, target=target, optimization_level=level) self.assertEqual(result, qc) @combine(level=[0, 1, 2, 3], name="level{level}") @@ -188,18 +190,20 @@ def test_unitary_is_preserved_if_basis_is_None(self, level): def test_unitary_is_preserved_if_in_basis_synthesis_translation(self, level): """Test that a unitary is not synthesized if in the basis with synthesis translation.""" qc = QuantumCircuit(2) - qc.unitary(random_unitary(4, seed=424242), [0, 1]) + ugate = UnitaryGate(random_unitary(4, seed=424242)) + qc.unitary(ugate, [0, 1]) qc.measure_all() - with self.assertWarnsRegex( - DeprecationWarning, - "Providing non-standard gates \\(unitary\\) through the ``basis_gates`` argument", - ): - result = transpile( - qc, - basis_gates=["cx", "u", "unitary"], - optimization_level=level, - translation_method="synthesis", - ) + target = Target.from_configuration( + num_qubits=2, + basis_gates=["cx", "u", "unitary", "measure"], + custom_name_mapping={"unitary": ugate}, + ) + result = transpile( + qc, + target=target, + optimization_level=level, + translation_method="synthesis", + ) self.assertEqual(result, qc) @combine(level=[0, 1, 2, 3], name="level{level}") @@ -1240,18 +1244,12 @@ def test_default_optimization_level_target_first_pos_arg(self): def test_with_no_backend(self, optimization_level): """Test a passmanager is constructed with no backend and optimization level.""" target = GenericBackendV2(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - pm = generate_preset_pass_manager( - optimization_level, - coupling_map=target.coupling_map, - basis_gates=target.operation_names, - instruction_durations=target.instruction_durations, - timing_constraints=target.target.timing_constraints(), - target=target.target, - ) + pm = generate_preset_pass_manager( + optimization_level, + coupling_map=target.coupling_map, + basis_gates=target.operation_names, + target=target.target, + ) self.assertIsInstance(pm, PassManager) @data(0, 1, 2, 3) @@ -1679,27 +1677,26 @@ def test_unsupported_targets_raise(self, optimization_level): @data(0, 1, 2, 3) def test_custom_basis_gates_raise(self, optimization_level): """Test that trying to provide a list of custom basis gates to generate_preset_pass_manager - raises a deprecation warning.""" + raises a ValueError.""" - with self.subTest(msg="no warning"): + with self.subTest(msg="no error"): # check that the warning isn't raised if the basis gates aren't custom basis_gates = ["x", "cx"] _ = generate_preset_pass_manager( optimization_level=optimization_level, basis_gates=basis_gates ) - with self.subTest(msg="warning only basis gates"): + with self.subTest(msg="error only basis gates"): # check that the warning is raised if they are custom basis_gates = ["my_gate"] - with self.assertWarnsRegex( - DeprecationWarning, - "Providing non-standard gates \\(my_gate\\) through the ``basis_gates`` argument", + with self.assertRaises( + ValueError, ): _ = generate_preset_pass_manager( optimization_level=optimization_level, basis_gates=basis_gates ) - with self.subTest(msg="no warning custom basis gates in backend"): + with self.subTest(msg="no error custom basis gates in backend"): # check that the warning is not raised if a loose custom gate is found in the backend backend = GenericBackendV2(num_qubits=2) gate = Gate(name="my_gate", num_qubits=1, params=[]) diff --git a/test/python/transpiler/test_solovay_kitaev.py b/test/python/transpiler/test_solovay_kitaev.py index 471c4791c114..dde92161695b 100644 --- a/test/python/transpiler/test_solovay_kitaev.py +++ b/test/python/transpiler/test_solovay_kitaev.py @@ -174,7 +174,9 @@ def test_str_basis_gates(self): def test_approximation_on_qft(self): """Test the Solovay-Kitaev decomposition on the QFT circuit.""" qft = QFT(3) - transpiled = transpile(qft, basis_gates=["u", "cx"], optimization_level=1) + transpiled = transpile( + qft, basis_gates=["u1", "u2", "u3", "u", "cx", "id"], optimization_level=1 + ) skd = SolovayKitaev(1) diff --git a/test/python/visualization/timeline/test_core.py b/test/python/visualization/timeline/test_core.py index a6077b2bc672..29a43a5116ff 100644 --- a/test/python/visualization/timeline/test_core.py +++ b/test/python/visualization/timeline/test_core.py @@ -166,18 +166,22 @@ def test_multi_measurement_with_clbit_not_shown(self): circ.measure(0, 0) circ.measure(1, 1) - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - circ = transpile( - circ, - scheduling_method="alap", - basis_gates=[], - dt=1e-7, - instruction_durations=[("measure", 0, 2000), ("measure", 1, 2000)], - optimization_level=0, - ) + target = Target(num_qubits=2, dt=1e-7) + target.add_instruction( + Measure(), + { + (0,): InstructionProperties(duration=2000 * 1e-7), + (1,): InstructionProperties(duration=2000 * 1e-7), + }, + ) + + circ = transpile( + circ, + scheduling_method="alap", + dt=1e-7, + target=target, + optimization_level=0, + ) canvas = core.DrawerCanvas(stylesheet=self.style) canvas.formatter.update({"control.show_clbits": False}) @@ -191,14 +195,7 @@ def test_multi_measurement_with_clbit_not_shown(self): "barriers": [], "gate_links": [generators.gen_gate_link], } - target = Target(num_qubits=2, dt=1e-7) - target.add_instruction( - Measure(), - { - (0,): InstructionProperties(duration=2000 * 1e-7), - (1,): InstructionProperties(duration=2000 * 1e-7), - }, - ) + canvas.load_program(circ, target) canvas.update() self.assertEqual(len(canvas._output_dataset), 0) @@ -209,18 +206,18 @@ def test_multi_measurement_with_clbit_shown(self): circ.measure(0, 0) circ.measure(1, 1) - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="The `target` parameter should be used instead", - ): - circ = transpile( - circ, - scheduling_method="alap", - dt=1e-7, - basis_gates=[], - instruction_durations=[("measure", 0, 2000), ("measure", 1, 2000)], - optimization_level=0, - ) + target = Target(num_qubits=2, dt=1e-7) + target.add_instruction( + Measure(), + { + (0,): InstructionProperties(duration=2000 * 1e-7), + (1,): InstructionProperties(duration=2000 * 1e-7), + }, + ) + + circ = transpile( + circ, scheduling_method="alap", dt=1e-7, optimization_level=0, target=target + ) canvas = core.DrawerCanvas(stylesheet=self.style) canvas.formatter.update({"control.show_clbits": True}) @@ -234,14 +231,6 @@ def test_multi_measurement_with_clbit_shown(self): "barriers": [], "gate_links": [generators.gen_gate_link], } - target = Target(num_qubits=2, dt=1e-7) - target.add_instruction( - Measure(), - { - (0,): InstructionProperties(duration=2000 * 1e-7), - (1,): InstructionProperties(duration=2000 * 1e-7), - }, - ) canvas.load_program(circ, target) canvas.update() From 852811483fd7280cb723cea776f2692468b8f905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:14:22 +0100 Subject: [PATCH 22/25] Apply suggestions from Eli's code review Co-authored-by: Eli Arbel <46826214+eliarbel@users.noreply.github.com> --- qiskit/transpiler/passes/basis/basis_translator.py | 2 +- qiskit/transpiler/passes/optimization/consolidate_blocks.py | 2 +- .../passes/optimization/optimize_1q_decomposition.py | 2 +- qiskit/transpiler/passes/synthesis/unitary_synthesis.py | 2 +- qiskit/transpiler/passes/utils/check_gate_direction.py | 1 + qiskit/transpiler/passes/utils/gate_direction.py | 1 + qiskit/transpiler/preset_passmanagers/builtin_plugins.py | 4 ++-- qiskit/transpiler/target.py | 2 +- 8 files changed, 9 insertions(+), 7 deletions(-) diff --git a/qiskit/transpiler/passes/basis/basis_translator.py b/qiskit/transpiler/passes/basis/basis_translator.py index 73ff01a9c134..94dcf73720be 100644 --- a/qiskit/transpiler/passes/basis/basis_translator.py +++ b/qiskit/transpiler/passes/basis/basis_translator.py @@ -101,7 +101,7 @@ def __init__(self, equivalence_library, target_basis, target=None, min_qubits=0) super().__init__() self._equiv_lib = equivalence_library self._target_basis = target_basis - # Bypass target if it doesn't contain any basis gates, as this + # Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this # not part of the official target model. self._target = target if target is not None and len(target.operation_names) > 0 else None self._non_global_operations = None diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 2092a887c771..1693b2390f66 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -69,7 +69,7 @@ def __init__( """ super().__init__() self.basis_gates = None - # Bypass target if it doesn't contain any basis gates, as this + # Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this # not part of the official target model. self.target = target if target is not None and len(target.operation_names) > 0 else None if basis_gates is not None: diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index ee2742eca6bb..e95406ade84b 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -85,7 +85,7 @@ def __init__(self, basis=None, target=None): self._basis_gates = set(basis) else: self._basis_gates = None - # Bypass target if it doesn't contain any basis gates, as this + # Bypass target if it doesn't contain any basis gates (i.e. it's a _FakeTarget), as this # not part of the official target model. self._target = target if target is not None and len(target.operation_names) > 0 else None self._global_decomposers = None diff --git a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py index f0b89f498b9e..689b1b37d552 100644 --- a/qiskit/transpiler/passes/synthesis/unitary_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/unitary_synthesis.py @@ -138,7 +138,7 @@ def __init__( self._pulse_optimize = pulse_optimize self._natural_direction = natural_direction self._plugin_config = plugin_config - # Bypass target if it doesn't contain any basis gates, as this + # Bypass target if it doesn't contain any basis gates (i.e it's _FakeTarget), as this # not part of the official target model. self._target = target if target is not None and len(target.operation_names) > 0 else None if target is not None: diff --git a/qiskit/transpiler/passes/utils/check_gate_direction.py b/qiskit/transpiler/passes/utils/check_gate_direction.py index d5f0db821614..3622bd7c956b 100644 --- a/qiskit/transpiler/passes/utils/check_gate_direction.py +++ b/qiskit/transpiler/passes/utils/check_gate_direction.py @@ -52,6 +52,7 @@ def run(self, dag): dag, set(self.coupling_map.get_edges()) ) elif len(self.target.operation_names) == 0: + # A _FakeTarget path, no basis gates, just use the coupling map self.property_set["is_direction_mapped"] = check_gate_direction_coupling( dag, set(self.target.build_coupling_map().get_edges()) ) diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py index 195b6599bce9..d01d8f1c4f69 100644 --- a/qiskit/transpiler/passes/utils/gate_direction.py +++ b/qiskit/transpiler/passes/utils/gate_direction.py @@ -86,6 +86,7 @@ def run(self, dag): if self.target is None: return fix_gate_direction_coupling(dag, set(self.coupling_map.get_edges())) elif len(self.target.operation_names) == 0: + # A _FakeTarget path, no basis gates, just use the coupling map return fix_gate_direction_coupling( dag, set(self.target.build_coupling_map().get_edges()) ) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 7008bd44d1be..f13d89b63cc0 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -553,7 +553,7 @@ def _opt_control(property_set): # Basic steps for optimization level 2: # 1. RemoveIdentityEquivalent # 2. Optimize1qGatesDecomposition - # 3. CommutativeCancelation + # 3. CommutativeCancellation elif optimization_level == 2: _opt = [ RemoveIdentityEquivalent( @@ -572,7 +572,7 @@ def _opt_control(property_set): # 2. UnitarySynthesis # 3. RemoveIdentityEquivalent # 4. Optimize1qGatesDecomposition - # 5. CommutativeCancelation + # 5. CommutativeCancellation elif optimization_level == 3: _opt = [ ConsolidateBlocks( diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 3df56e4dfc06..c69c8335c0ad 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -985,7 +985,7 @@ def target_to_backend_properties(target: Target): class _FakeTarget(Target): """ Pseudo-target class for INTERNAL use in the transpilation pipeline. - It's essentially an empty :class:`.Target` instance with a a `coupling_map` + It's essentially an empty :class:`.Target` instance with a `coupling_map` argument that allows to store connectivity constraints without basis gates. This is intended to replace the use of loose constraints in the pipeline. """ From 6a53a5835178d35944ee148d5ff44dbf274cf506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 6 Mar 2025 15:01:32 +0100 Subject: [PATCH 23/25] Clean up --- qiskit/compiler/transpiler.py | 1 - qiskit/transpiler/target.py | 1 + test/benchmarks/transpiler_levels.py | 1 - test/python/circuit/library/test_qft.py | 2 -- test/python/circuit/test_scheduled_circuit.py | 3 --- test/python/compiler/test_compiler.py | 2 +- 6 files changed, 2 insertions(+), 8 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 1885653c441f..7dbf98a8aadd 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -16,7 +16,6 @@ import logging from time import time from typing import List, Union, Dict, Callable, Any, Optional, TypeVar -import warnings from qiskit import user_config from qiskit.circuit.quantumcircuit import QuantumCircuit diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 03cb8ef487c3..02ee1b5295c1 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -24,6 +24,7 @@ from typing import Optional, List, Any from collections.abc import Mapping import io +import copy import logging import inspect diff --git a/test/benchmarks/transpiler_levels.py b/test/benchmarks/transpiler_levels.py index ab044143a819..78412e810fd2 100644 --- a/test/benchmarks/transpiler_levels.py +++ b/test/benchmarks/transpiler_levels.py @@ -231,7 +231,6 @@ def time_schedule_qv_14_x_14(self, transpiler_level): seed_transpiler=0, optimization_level=transpiler_level, scheduling_method="alap", - instruction_durations=self.durations, ) # limit optimization levels to reduce time diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index 417df30b7582..c298f8d9a77a 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -138,8 +138,6 @@ def test_qft_num_gates(self, num_qubits, approximation_degree, insert_barriers): qft = QFT( num_qubits, approximation_degree=approximation_degree, insert_barriers=insert_barriers ) - print(qft.decompose()) - print(qft.decompose(reps=2)) # from qiskit.providers.fake_provider import GenericBackendV2 # backend = GenericBackendV2(num_qubits=num_qubits, basis_gates = ["h", "swap", "cu1"]) # ops = transpile(qft, backend=backend, optimization_level=1).count_ops() diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index a2eeff46560a..0aa5ac16009b 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -164,7 +164,6 @@ def test_transpile_circuit_with_custom_instruction(self): qc.delay(500, 1) qc.append(bell_instr, [0, 1]) - print(qc) target = Target(num_qubits=2) target.add_instruction(CXGate(), {(0, 1): InstructionProperties(0)}) target.add_instruction( @@ -179,14 +178,12 @@ def test_transpile_circuit_with_custom_instruction(self): }, ) target.dt = 1e-2 - print(target) scheduled = transpile( qc, scheduling_method="alap", target=target, dt=1e-2, ) - print(scheduled) self.assertEqual(scheduled.duration, 1500) def test_transpile_delay_circuit_with_dt_but_without_scheduling_method(self): diff --git a/test/python/compiler/test_compiler.py b/test/python/compiler/test_compiler.py index 9fe244281e0a..e60c98640f6a 100644 --- a/test/python/compiler/test_compiler.py +++ b/test/python/compiler/test_compiler.py @@ -398,7 +398,7 @@ def test_math_domain_error(self): # ┌───┐┌───┐ ┌─┐ # q0_0: ┤ Y ├┤ X ├─────┤M├───────────────────── # └───┘└─┬─┘ └╥┘ ┌─┐ - # q0_1: ───────■────────╫─────────────■──┤M├─── This constructor method only supports fixed width operations + # q0_1: ───────■────────╫─────────────■──┤M├─── # ┌───┐┌───┐┌───┐ ║ ┌───┐┌───┐┌─┴─┐└╥┘┌─┐ # q0_2: ┤ Z ├┤ H ├┤ Y ├─╫─┤ T ├┤ Z ├┤ X ├─╫─┤M├ # └┬─┬┘└───┘└───┘ ║ └───┘└───┘└───┘ ║ └╥┘ From 591664cd3d6ca53877d280a306e392448e695442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 6 Mar 2025 15:46:50 +0100 Subject: [PATCH 24/25] Apply suggestions from Eli's code review. Add reno for API changes. Fix oversights --- ...cated-transpile-args-d6e212a659eec7cb.yaml | 50 +++++++++++++++++++ test/python/circuit/library/test_qft.py | 9 ++-- test/python/transpiler/test_target.py | 8 +++ 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/remove-deprecated-transpile-args-d6e212a659eec7cb.yaml diff --git a/releasenotes/notes/remove-deprecated-transpile-args-d6e212a659eec7cb.yaml b/releasenotes/notes/remove-deprecated-transpile-args-d6e212a659eec7cb.yaml new file mode 100644 index 000000000000..7370f81ffad0 --- /dev/null +++ b/releasenotes/notes/remove-deprecated-transpile-args-d6e212a659eec7cb.yaml @@ -0,0 +1,50 @@ +--- +upgrade_transpiler: + - | + The following :func:`.transpile` and :func:`.generate_preset_pass_manager` input arguments, + deprecated since Qiskit 1.3 , have been removed from the API: + + * `instruction_durations` + + * `timing_constraints` + + In addition to this, the specification of custom basis gates through the ``basis`` gate argument of + :func:`.transpile` and :func:`.generate_preset_pass_manager`, also deprecated in Qiskit 1.3, is + no longer allowed, and a ``ValueError`` will be raised in these cases. + + The information formerly provided through these can still be specified via the ``backend`` or + ``target`` arguments. You can build a `Target` instance with defined instruction durations doing:: + + Target.from_configuration(..., instruction_durations=...) + + + For specific timing constraints:: + + Target.from_configuration(..., timing_constraints=...) + + And for custom basis gates, you can manually add them to the target or use ``.from_configuration`` + with a custom name mapping, for example:: + + from qiskit.circuit.library import XGate + from qiskit.transpiler.target import Target + + basis_gates = ["my_x", "cx"] + custom_name_mapping = {"my_x": XGate()} + target = Target.from_configuration( + basis_gates=basis_gates, num_qubits=2, custom_name_mapping=custom_name_mapping + ) + + - | + The :func:`.transpile` and :func:`.generate_preset_pass_manager` interfaces now + raise a ``UserWarning`` when providing a ``coupling_map`` and/or ``basis_gates`` + along with a ``backend``. In these cases there are multiple sources of truth, + the user intentions are not always clear, and there can be conflicts that + :func:`.generate_preset_pass_manager` may not know how to resolve. In these cases, + we highly encourage the creation of a custom target that combines the chosen + constraints. + + One of these situations is the specification of a gate with 3 or more qubits in + `backend` or `basis_gates` together with a custom coupling map. The coupling map + does not provide the necessary connectivity details to be able to determine the + action of the gate. In these cases, :func:`.transpile` and + :func:`.generate_preset_pass_manager` now raise a ``ValueError``. \ No newline at end of file diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index c298f8d9a77a..e49b18d9b612 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -127,9 +127,9 @@ def test_qft_mutability(self): @data( (4, 0, False), - # (3, 0, True), - # (6, 2, False), - # (4, 5, True), + (3, 0, True), + (6, 2, False), + (4, 5, True), ) @unpack def test_qft_num_gates(self, num_qubits, approximation_degree, insert_barriers): @@ -138,9 +138,6 @@ def test_qft_num_gates(self, num_qubits, approximation_degree, insert_barriers): qft = QFT( num_qubits, approximation_degree=approximation_degree, insert_barriers=insert_barriers ) - # from qiskit.providers.fake_provider import GenericBackendV2 - # backend = GenericBackendV2(num_qubits=num_qubits, basis_gates = ["h", "swap", "cu1"]) - # ops = transpile(qft, backend=backend, optimization_level=1).count_ops() ops = transpile(qft, basis_gates=basis_gates, optimization_level=1).count_ops() with self.subTest(msg="assert H count"): diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index c99a3da32416..13221a39e880 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1765,3 +1765,11 @@ def test_fake_from_configuration(self): self.assertNotEqual(target, None) self.assertEqual(target.num_qubits, 2) self.assertEqual(target.build_coupling_map(), cmap) + + def test_fake_only_when_necessary(self): + # Make sure _FakeTarget cannot be instantiated if there + # is enough info for a real Target + with self.assertRaises(TypeError): + _ = _FakeTarget.from_configuration( + basis_gates=["cx"], coupling_map=CouplingMap([[0, 1]]) + ) From 2b12e65f6501f9aa9ddb43d5beeb069061520599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 6 Mar 2025 15:49:12 +0100 Subject: [PATCH 25/25] Fix merge conflict --- qiskit/compiler/transpiler.py | 368 ++++++++++++++++------------------ 1 file changed, 178 insertions(+), 190 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 7dbf98a8aadd..72acf93c26f5 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -61,196 +61,184 @@ def transpile( # pylint: disable=too-many-return-statements ) -> _CircuitT: """Transpile one or more circuits, according to some desired transpilation targets. - Transpilation is potentially done in parallel using multiprocessing when ``circuits`` - is a list with > 1 :class:`~.QuantumCircuit` object, depending on the local environment - and configuration. - - The prioritization of transpilation target constraints works as follows: if a ``target`` - input is provided, it will take priority over any ``backend`` input or loose constraints - (``basis_gates``, ``coupling_map``, or ``dt``). If a ``backend`` is provided - together with any loose constraint - from the list above, the loose constraint will take priority over the corresponding backend - constraint. This behavior is summarized in the table below. The first column - in the table summarizes the potential user-provided constraints, and each cell shows whether - the priority is assigned to that specific constraint input or another input - (`target`/`backend(V2)`). - - <<<<<<< HEAD - ============================ ========= ======================== ======================= - User Provided target backend(V1) backend(V2) - ============================ ========= ======================== ======================= - **basis_gates** target basis_gates basis_gates - **coupling_map** target coupling_map coupling_map - **dt** target dt dt - ============================ ========= ======================== ======================= - ======= - ============================ ========= ======================== - User Provided target backend(V2) - ============================ ========= ======================== - **basis_gates** target basis_gates - **coupling_map** target coupling_map - **instruction_durations** target instruction_durations - **dt** target dt - **timing_constraints** target timing_constraints - ============================ ========= ======================== - >>>>>>> 2ec8f8f66650d84159f3bc058b9f38a1285175cb - - Args: - circuits: Circuit(s) to transpile - backend: If set, the transpiler will compile the input circuit to this target - device. If any other option is explicitly set (e.g., ``coupling_map``), it - will override the backend's. - basis_gates: List of basis gate names to unroll to - (e.g: ``['u1', 'u2', 'u3', 'cx']``). If ``None``, do not unroll. - coupling_map: Directed coupling map (perhaps custom) to target in mapping. If - the coupling map is symmetric, both directions need to be specified. - - Multiple formats are supported: - - #. ``CouplingMap`` instance - #. List, must be given as an adjacency matrix, where each entry - specifies all directed two-qubit interactions supported by backend, - e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]`` - initial_layout: Initial position of virtual qubits on physical qubits. - If this layout makes the circuit compatible with the coupling_map - constraints, it will be used. The final layout is not guaranteed to be the same, - as the transpiler may permute qubits through swaps or other means. - Multiple formats are supported: - - #. ``Layout`` instance - #. Dict - * virtual to physical:: - - {qr[0]: 0, - qr[1]: 3, - qr[2]: 5} - - * physical to virtual:: - - {0: qr[0], - 3: qr[1], - 5: qr[2]} - - #. List - - * virtual to physical:: - - [0, 3, 5] # virtual qubits are ordered (in addition to named) - - * physical to virtual:: - - [qr[0], None, None, qr[1], None, qr[2]] - - layout_method: Name of layout selection pass ('trivial', 'dense', 'sabre'). - This can also be the external plugin name to use for the ``layout`` stage. - You can see a list of installed plugins by using :func:`~.list_stage_plugins` with - ``"layout"`` for the ``stage_name`` argument. - routing_method: Name of routing pass - ('basic', 'lookahead', 'stochastic', 'sabre', 'none'). Note - This can also be the external plugin name to use for the ``routing`` stage. - You can see a list of installed plugins by using :func:`~.list_stage_plugins` with - ``"routing"`` for the ``stage_name`` argument. - translation_method: Name of translation pass (``"default"``, ``"translator"`` or - ``"synthesis"``). This can also be the external plugin name to use for the - ``translation`` stage. You can see a list of installed plugins by using - :func:`~.list_stage_plugins` with ``"translation"`` for the ``stage_name`` argument. - scheduling_method: Name of scheduling pass. - * ``'as_soon_as_possible'``: Schedule instructions greedily, as early as possible - on a qubit resource. (alias: ``'asap'``) - * ``'as_late_as_possible'``: Schedule instructions late, i.e. keeping qubits - in the ground state when possible. (alias: ``'alap'``) - If ``None``, no scheduling will be done. This can also be the external plugin name - to use for the ``scheduling`` stage. You can see a list of installed plugins by - using :func:`~.list_stage_plugins` with ``"scheduling"`` for the ``stage_name`` - argument. - dt: Backend sample time (resolution) in seconds. - If ``None`` (default), ``backend.dt`` is used. - approximation_degree (float): heuristic dial used for circuit approximation - (1.0=no approximation, 0.0=maximal approximation) - seed_transpiler: Sets random seed for the stochastic parts of the transpiler - optimization_level: How much optimization to perform on the circuits. - Higher levels generate more optimized circuits, - at the expense of longer transpilation time. - - * 0: no optimization - * 1: light optimization - * 2: heavy optimization - * 3: even heavier optimization - - If ``None``, level 2 will be chosen as default. - callback: A callback function that will be called after each - pass execution. The function will be called with 5 keyword - arguments, - | ``pass_``: the pass being run. - | ``dag``: the dag output of the pass. - | ``time``: the time to execute the pass. - | ``property_set``: the property set. - | ``count``: the index for the pass execution. - The exact arguments passed expose the internals of the pass manager, - and are subject to change as the pass manager internals change. If - you intend to reuse a callback function over multiple releases, be - sure to check that the arguments being passed are the same. - To use the callback feature, define a function that will - take in kwargs dict and access the variables. For example:: - - def callback_func(**kwargs): - pass_ = kwargs['pass_'] - dag = kwargs['dag'] - time = kwargs['time'] - property_set = kwargs['property_set'] - count = kwargs['count'] - ... - transpile(circ, callback=callback_func) - - 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. You can see a list of installed - plugins with :func:`.unitary_synthesis_plugin_names`. - unitary_synthesis_plugin_config: An optional configuration dictionary - that will be passed directly to the unitary synthesis plugin. By - default this setting will have no effect as the default unitary - synthesis method does not take custom configuration. This should - only be necessary when a unitary synthesis plugin is specified with - the ``unitary_synthesis_method`` argument. As this is custom for each - unitary synthesis plugin refer to the plugin documentation for how - to use this option. - target: A backend transpiler target. Normally this is specified as part of - the ``backend`` argument, but if you have manually constructed a - :class:`~qiskit.transpiler.Target` object you can specify it manually here. - This will override the target from ``backend``. - hls_config: An optional configuration class - :class:`~qiskit.transpiler.passes.synthesis.HLSConfig` that will be passed directly - to :class:`~qiskit.transpiler.passes.synthesis.HighLevelSynthesis` transformation pass. - This configuration class allows to specify for various high-level objects the lists of - synthesis algorithms and their parameters. - init_method: The plugin name to use for the ``init`` stage. By default an external - plugin is not used. You can see a list of installed plugins by - using :func:`~.list_stage_plugins` with ``"init"`` for the stage - name argument. - optimization_method: The plugin name to use for the - ``optimization`` stage. By default an external - plugin is not used. You can see a list of installed plugins by - using :func:`~.list_stage_plugins` with ``"optimization"`` for the - ``stage_name`` argument. - ignore_backend_supplied_default_methods: If set to ``True`` any default methods specified by - a backend will be ignored. Some backends specify alternative default methods - to support custom compilation target-specific passes/plugins which support - backend-specific compilation techniques. If you'd prefer that these defaults were - not used this option is used to disable those backend-specific defaults. - num_processes: The maximum number of parallel processes to launch for this call to - transpile if parallel execution is enabled. This argument overrides - ``num_processes`` in the user configuration file, and the ``QISKIT_NUM_PROCS`` - environment variable. If set to ``None`` the system default or local user configuration - will be used. - qubits_initially_zero: Indicates whether the input circuit is zero-initialized. - - Returns: - The transpiled circuit(s). - - Raises: - TranspilerError: in case of bad inputs to transpiler (like conflicting parameters) - or errors in passes + Transpilation is potentially done in parallel using multiprocessing when ``circuits`` + is a list with > 1 :class:`~.QuantumCircuit` object, depending on the local environment + and configuration. + + The prioritization of transpilation target constraints works as follows: if a ``target`` + input is provided, it will take priority over any ``backend`` input or loose constraints + (``basis_gates``, ``coupling_map``, or ``dt``). If a ``backend`` is provided + together with any loose constraint + from the list above, the loose constraint will take priority over the corresponding backend + constraint. This behavior is summarized in the table below. The first column + in the table summarizes the potential user-provided constraints, and each cell shows whether + the priority is assigned to that specific constraint input or another input + (`target`/`backend(V2)`). + + ============================ ========= ======================== + User Provided target backend(V2) + ============================ ========= ======================== + **basis_gates** target basis_gates + **coupling_map** target coupling_map + **dt** target dt + ============================ ========= ======================== + + Args: + circuits: Circuit(s) to transpile + backend: If set, the transpiler will compile the input circuit to this target + device. If any other option is explicitly set (e.g., ``coupling_map``), it + will override the backend's. + basis_gates: List of basis gate names to unroll to + (e.g: ``['u1', 'u2', 'u3', 'cx']``). If ``None``, do not unroll. + coupling_map: Directed coupling map (perhaps custom) to target in mapping. If + the coupling map is symmetric, both directions need to be specified. + + Multiple formats are supported: + + #. ``CouplingMap`` instance + #. List, must be given as an adjacency matrix, where each entry + specifies all directed two-qubit interactions supported by backend, + e.g: ``[[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]]`` + initial_layout: Initial position of virtual qubits on physical qubits. + If this layout makes the circuit compatible with the coupling_map + constraints, it will be used. The final layout is not guaranteed to be the same, + as the transpiler may permute qubits through swaps or other means. + Multiple formats are supported: + + #. ``Layout`` instance + #. Dict + * virtual to physical:: + + {qr[0]: 0, + qr[1]: 3, + qr[2]: 5} + + * physical to virtual:: + + {0: qr[0], + 3: qr[1], + 5: qr[2]} + + #. List + + * virtual to physical:: + + [0, 3, 5] # virtual qubits are ordered (in addition to named) + + * physical to virtual:: + + [qr[0], None, None, qr[1], None, qr[2]] + + layout_method: Name of layout selection pass ('trivial', 'dense', 'sabre'). + This can also be the external plugin name to use for the ``layout`` stage. + You can see a list of installed plugins by using :func:`~.list_stage_plugins` with + ``"layout"`` for the ``stage_name`` argument. + routing_method: Name of routing pass + ('basic', 'lookahead', 'stochastic', 'sabre', 'none'). Note + This can also be the external plugin name to use for the ``routing`` stage. + You can see a list of installed plugins by using :func:`~.list_stage_plugins` with + ``"routing"`` for the ``stage_name`` argument. + translation_method: Name of translation pass (``"default"``, ``"translator"`` or + ``"synthesis"``). This can also be the external plugin name to use for the + ``translation`` stage. You can see a list of installed plugins by using + :func:`~.list_stage_plugins` with ``"translation"`` for the ``stage_name`` argument. + scheduling_method: Name of scheduling pass. + * ``'as_soon_as_possible'``: Schedule instructions greedily, as early as possible + on a qubit resource. (alias: ``'asap'``) + * ``'as_late_as_possible'``: Schedule instructions late, i.e. keeping qubits + in the ground state when possible. (alias: ``'alap'``) + If ``None``, no scheduling will be done. This can also be the external plugin name + to use for the ``scheduling`` stage. You can see a list of installed plugins by + using :func:`~.list_stage_plugins` with ``"scheduling"`` for the ``stage_name`` + argument. + dt: Backend sample time (resolution) in seconds. + If ``None`` (default), ``backend.dt`` is used. + approximation_degree (float): heuristic dial used for circuit approximation + (1.0=no approximation, 0.0=maximal approximation) + seed_transpiler: Sets random seed for the stochastic parts of the transpiler + optimization_level: How much optimization to perform on the circuits. + Higher levels generate more optimized circuits, + at the expense of longer transpilation time. + + * 0: no optimization + * 1: light optimization + * 2: heavy optimization + * 3: even heavier optimization + + If ``None``, level 2 will be chosen as default. + callback: A callback function that will be called after each + pass execution. The function will be called with 5 keyword + arguments, + | ``pass_``: the pass being run. + | ``dag``: the dag output of the pass. + | ``time``: the time to execute the pass. + | ``property_set``: the property set. + | ``count``: the index for the pass execution. + The exact arguments passed expose the internals of the pass manager, + and are subject to change as the pass manager internals change. If + you intend to reuse a callback function over multiple releases, be + sure to check that the arguments being passed are the same. + To use the callback feature, define a function that will + take in kwargs dict and access the variables. For example:: + + def callback_func(**kwargs): + pass_ = kwargs['pass_'] + dag = kwargs['dag'] + time = kwargs['time'] + property_set = kwargs['property_set'] + count = kwargs['count'] + ... + transpile(circ, callback=callback_func) + + 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. You can see a list of installed + plugins with :func:`.unitary_synthesis_plugin_names`. + unitary_synthesis_plugin_config: An optional configuration dictionary + that will be passed directly to the unitary synthesis plugin. By + default this setting will have no effect as the default unitary + synthesis method does not take custom configuration. This should + only be necessary when a unitary synthesis plugin is specified with + the ``unitary_synthesis_method`` argument. As this is custom for each + unitary synthesis plugin refer to the plugin documentation for how + to use this option. + target: A backend transpiler target. Normally this is specified as part of + the ``backend`` argument, but if you have manually constructed a + :class:`~qiskit.transpiler.Target` object you can specify it manually here. + This will override the target from ``backend``. + hls_config: An optional configuration class + :class:`~qiskit.transpiler.passes.synthesis.HLSConfig` that will be passed directly + to :class:`~qiskit.transpiler.passes.synthesis.HighLevelSynthesis` transformation pass. + This configuration class allows to specify for various high-level objects the lists of + synthesis algorithms and their parameters. + init_method: The plugin name to use for the ``init`` stage. By default an external + plugin is not used. You can see a list of installed plugins by + using :func:`~.list_stage_plugins` with ``"init"`` for the stage + name argument. + optimization_method: The plugin name to use for the + ``optimization`` stage. By default an external + plugin is not used. You can see a list of installed plugins by + using :func:`~.list_stage_plugins` with ``"optimization"`` for the + ``stage_name`` argument. + ignore_backend_supplied_default_methods: If set to ``True`` any default methods specified by + a backend will be ignored. Some backends specify alternative default methods + to support custom compilation target-specific passes/plugins which support + backend-specific compilation techniques. If you'd prefer that these defaults were + not used this option is used to disable those backend-specific defaults. + num_processes: The maximum number of parallel processes to launch for this call to + transpile if parallel execution is enabled. This argument overrides + ``num_processes`` in the user configuration file, and the ``QISKIT_NUM_PROCS`` + environment variable. If set to ``None`` the system default or local user configuration + will be used. + qubits_initially_zero: Indicates whether the input circuit is zero-initialized. + + Returns: + The transpiled circuit(s). + + Raises: + TranspilerError: in case of bad inputs to transpiler (like conflicting parameters) + or errors in passes """ arg_circuits_list = isinstance(circuits, list) circuits = circuits if arg_circuits_list else [circuits]