diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 9c74d10a7b63..adf60ca91e56 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -218,7 +218,7 @@ def transpile( # pylint: disable=too-many-return-statements * 2: heavy optimization * 3: even heavier optimization - If ``None``, level 1 will be chosen as default. + 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, @@ -312,7 +312,7 @@ def callback_func(**kwargs): if optimization_level is None: # Take optimization level from the configuration or 1 as default. config = user_config.get_config() - optimization_level = config.get("transpile_optimization_level", 1) + optimization_level = config.get("transpile_optimization_level", 2) if backend is not None and getattr(backend, "version", 0) <= 1: # This is a temporary conversion step to allow for a smoother transition diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 6023f2a4b6aa..bdbac42c8055 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -19,6 +19,7 @@ from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping from qiskit.circuit.quantumregister import Qubit +from qiskit.providers.backend import Backend from qiskit.providers.backend_compat import BackendV2Converter from qiskit.transpiler.coupling import CouplingMap from qiskit.transpiler.exceptions import TranspilerError @@ -35,7 +36,7 @@ def generate_preset_pass_manager( - optimization_level, + optimization_level=2, backend=None, target=None, basis_gates=None, @@ -96,9 +97,10 @@ def generate_preset_pass_manager( Args: optimization_level (int): The optimization level to generate a - :class:`~.PassManager` for. This can be 0, 1, 2, or 3. Higher - levels generate more optimized circuits, at the expense of - longer transpilation time: + :class:`~.StagedPassManager` for. By default optimization level 2 + is used if this is not specified. This can be 0, 1, 2, or 3. Higher + levels generate potentially more optimized circuits, at the expense + of longer transpilation time: * 0: no optimization * 1: light optimization @@ -238,6 +240,16 @@ def generate_preset_pass_manager( ValueError: if an invalid value for ``optimization_level`` is passed in. """ + # Handle positional arguments for target and backend. This enables the usage + # pattern `generate_preset_pass_manager(backend.target)` to generate a default + # pass manager for a given target. + if isinstance(optimization_level, Target): + target = optimization_level + optimization_level = 2 + elif isinstance(optimization_level, Backend): + backend = optimization_level + optimization_level = 2 + if backend is not None and getattr(backend, "version", 0) <= 1: # This is a temporary conversion step to allow for a smoother transition # to a fully target-based transpiler pipeline while maintaining the behavior diff --git a/releasenotes/notes/default-level-2-generate-preset-passmanager-ec758ddc896ae2d6.yaml b/releasenotes/notes/default-level-2-generate-preset-passmanager-ec758ddc896ae2d6.yaml new file mode 100644 index 000000000000..ff5d57797860 --- /dev/null +++ b/releasenotes/notes/default-level-2-generate-preset-passmanager-ec758ddc896ae2d6.yaml @@ -0,0 +1,44 @@ +--- +features_transpiler: + - | + The ``optimization_level`` argument for the :func:`.generate_preset_pass_manager` function is + now optional. If it's not specified it will default to using optimization level 2. As the argument + is now optional, the first positional argument has been expanded to enable passing a :class:`.Target` + or a :class:`.BackendV2` as the first argument for more convenient construction. For example:: + + from qiskit.transpiler.preset_passmanager import generate_preset_pass_manager + from qiskit.providers.fake_provider import GenericBackendV2 + + backend = GenericBackendV2(100) + + generate_preset_pass_manager(backend.Target) + + will construct a default pass manager for the 100 qubit :class`.GenericBackendV2` instance. +upgrade_transpiler: + - | + The default ``optimization_level`` used by the :func:`.transpile` function when one is not + specified has been changed to level 2. This makes it consistent with the default used + by :func:`.generate_preset_pass_manager` which is used internally by :func:`.transpile`. Optimization + level 2 provides a much better balance between the run time of the function and the optimizations it + performs, it's a better tradeoff to use by default. + + The API of :func:`.transpile` remains unchanged because, fundamentally, level 2 and level 1 + have the same semantics. If you were previously relying on the implicit default of level 1, + you can simply set the argument ``optimization_level=1`` when you call :func:`.transpile`. + Similarly you can change the default back in your local environment by using a user config + file and setting the ``transpile_optimization_level`` field to 1. + + The only potential issue is that your transpilation workflow may be relying on an implicit trivial layout (where qubit 0 + in the circuit passed to :func:`.transpile` is mapped to qubit 0 on the target backend/coupling, + 1->1, 2->2, etc.) without specifying ``optimization_level=1``, ``layout_method="trivial"``, or + explicitly setting ``initial_layout`` when calling :func:`.transpile`. This behavior was a side + effect of the preset pass manager construction in optimization level 1 and is not mirrored in + level 2. If you need this behavior you can use any of the three options listed previously to make + this behavior explicit. + + Similarly, if you were targeting a discrete basis gate set you may encounter an issue using the + new default with optimization level 2 (or running explicitly optimization level 3), as the additional optimization passes that run in + level 2 and 3 don't work in all cases with a discrete basis. You can explicitly set + ``optimization_level=1`` manually in this case. In general the transpiler does not currently + fully support discrete basis sets and if you're relying on this you should likely construct a + pass manager manually to build a compilation pipeline that will work with your target. diff --git a/test/python/circuit/library/test_qft.py b/test/python/circuit/library/test_qft.py index 1f5c9715dd7a..85837f0ac80c 100644 --- a/test/python/circuit/library/test_qft.py +++ b/test/python/circuit/library/test_qft.py @@ -139,7 +139,7 @@ def test_qft_num_gates(self, num_qubits, approximation_degree, insert_barriers): qft = QFT( num_qubits, approximation_degree=approximation_degree, insert_barriers=insert_barriers ) - ops = transpile(qft, basis_gates=basis_gates).count_ops() + ops = transpile(qft, basis_gates=basis_gates, optimization_level=1).count_ops() with self.subTest(msg="assert H count"): self.assertEqual(ops["h"], num_qubits) diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 77b63a3098bc..472fc732ca9a 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -517,11 +517,21 @@ def test_transpile_bell_discrete_basis(self): # Try with the initial layout in both directions to ensure we're dealing with the basis # having only a single direction. + + # Use optimization level=1 because the synthesis that runs as part of optimization at + # higher optimization levels will create intermediate gates that the transpiler currently + # lacks logic to translate to a discrete basis. self.assertIsInstance( - transpile(qc, target=target, initial_layout=[0, 1], seed_transpiler=42), QuantumCircuit + transpile( + qc, target=target, initial_layout=[0, 1], seed_transpiler=42, optimization_level=1 + ), + QuantumCircuit, ) self.assertIsInstance( - transpile(qc, target=target, initial_layout=[1, 0], seed_transpiler=42), QuantumCircuit + transpile( + qc, target=target, initial_layout=[1, 0], seed_transpiler=42, optimization_level=1 + ), + QuantumCircuit, ) def test_transpile_one(self): @@ -1318,6 +1328,7 @@ def test_transpile_calibrated_custom_gate_on_diff_qubit(self): backend=GenericBackendV2(num_qubits=4), layout_method="trivial", seed_transpiler=42, + optimization_level=1, ) def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): @@ -1334,7 +1345,7 @@ def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): circ.add_calibration("h", [1], q0_x180) transpiled_circuit = transpile( - circ, backend=GenericBackendV2(num_qubits=4), seed_transpiler=42 + circ, backend=GenericBackendV2(num_qubits=4), seed_transpiler=42, optimization_level=1 ) self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) @@ -1781,7 +1792,7 @@ def test_approximation_degree_invalid(self): ) def test_approximation_degree(self): - """Test more approximation gives lower-cost circuit.""" + """Test more approximation can give lower-cost circuit.""" circuit = QuantumCircuit(2) circuit.swap(0, 1) circuit.h(0) @@ -1791,6 +1802,7 @@ def test_approximation_degree(self): translation_method="synthesis", approximation_degree=0.1, seed_transpiler=42, + optimization_level=1, ) circ_90 = transpile( circuit, @@ -1798,6 +1810,7 @@ def test_approximation_degree(self): translation_method="synthesis", approximation_degree=0.9, seed_transpiler=42, + optimization_level=1, ) self.assertLess(circ_10.depth(), circ_90.depth()) diff --git a/test/python/primitives/test_backend_estimator.py b/test/python/primitives/test_backend_estimator.py index c3deb0735ca1..626ba7625bc9 100644 --- a/test/python/primitives/test_backend_estimator.py +++ b/test/python/primitives/test_backend_estimator.py @@ -430,7 +430,7 @@ def test_layout(self, backend): backend.set_options(seed_simulator=15) with self.assertWarns(DeprecationWarning): estimator = BackendEstimator(backend) - estimator.set_transpile_options(seed_transpiler=15) + estimator.set_transpile_options(seed_transpiler=15, optimization_level=1) value = estimator.run(qc, op, shots=10000).result().values[0] if optionals.HAS_AER: ref_value = -0.9954 if isinstance(backend, GenericBackendV2) else -0.916 @@ -446,7 +446,9 @@ def test_layout(self, backend): op = SparsePauliOp("IZI") with self.assertWarns(DeprecationWarning): estimator = BackendEstimator(backend) - estimator.set_transpile_options(initial_layout=[0, 1, 2], seed_transpiler=15) + estimator.set_transpile_options( + initial_layout=[0, 1, 2], seed_transpiler=15, optimization_level=1 + ) estimator.set_options(seed_simulator=15) value = estimator.run(qc, op, shots=10000).result().values[0] if optionals.HAS_AER: diff --git a/test/python/primitives/test_primitive.py b/test/python/primitives/test_primitive.py index f0401e771448..c2b6b8f14202 100644 --- a/test/python/primitives/test_primitive.py +++ b/test/python/primitives/test_primitive.py @@ -142,7 +142,7 @@ def test_with_scheduling(n): qc = QuantumCircuit(1) qc.x(0) qc.add_calibration("x", qubits=(0,), schedule=custom_gate) - return transpile(qc, Fake20QV1(), scheduling_method="alap") + return transpile(qc, Fake20QV1(), scheduling_method="alap", optimization_level=1) keys = [_circuit_key(test_with_scheduling(i)) for i in range(1, 5)] self.assertEqual(len(keys), len(set(keys))) diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index 70330085b1ab..40924e240826 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -147,7 +147,7 @@ def test_transpile_respects_arg_constraints(self): qc = QuantumCircuit(2) qc.h(0) qc.cx(1, 0) - tqc = transpile(qc, self.backend) + tqc = transpile(qc, self.backend, optimization_level=1) self.assertTrue(Operator.from_circuit(tqc).equiv(qc)) # Below is done to check we're decomposing cx(1, 0) with extra # rotations to correct for direction. However because of fp @@ -163,7 +163,7 @@ def test_transpile_respects_arg_constraints(self): qc = QuantumCircuit(2) qc.h(0) qc.ecr(0, 1) - tqc = transpile(qc, self.backend) + tqc = transpile(qc, self.backend, optimization_level=1) self.assertTrue(Operator.from_circuit(tqc).equiv(qc)) self.assertEqual(tqc.count_ops(), {"ecr": 1, "u": 4}) self.assertMatchesTargetConstraints(tqc, self.backend.target) @@ -173,7 +173,7 @@ def test_transpile_relies_on_gate_direction(self): qc = QuantumCircuit(2) qc.h(0) qc.ecr(0, 1) - tqc = transpile(qc, self.backend) + tqc = transpile(qc, self.backend, optimization_level=1) expected = QuantumCircuit(2) expected.u(0, 0, -math.pi, 0) expected.u(math.pi / 2, 0, 0, 1) @@ -191,7 +191,7 @@ def test_transpile_mumbai_target(self): qc.h(0) qc.cx(1, 0) qc.measure_all() - tqc = transpile(qc, backend) + tqc = transpile(qc, backend, optimization_level=1) qr = QuantumRegister(27, "q") cr = ClassicalRegister(2, "meas") expected = QuantumCircuit(qr, cr, global_phase=math.pi / 4) diff --git a/test/python/pulse/test_builder.py b/test/python/pulse/test_builder.py index cf029d5d98c2..1dc295a02be1 100644 --- a/test/python/pulse/test_builder.py +++ b/test/python/pulse/test_builder.py @@ -764,7 +764,9 @@ def get_sched(qubit_idx: [int], backend): qc = circuit.QuantumCircuit(2) for idx in qubit_idx: qc.append(circuit.library.U2Gate(0, pi / 2), [idx]) - return compiler.schedule(compiler.transpile(qc, backend=backend), backend) + return compiler.schedule( + compiler.transpile(qc, backend=backend, optimization_level=1), backend + ) with pulse.build(self.backend) as schedule: with pulse.align_sequential(): @@ -784,7 +786,7 @@ def get_sched(qubit_idx: [int], backend): # prepare and schedule circuits that will be used. single_u2_qc = circuit.QuantumCircuit(2) single_u2_qc.append(circuit.library.U2Gate(0, pi / 2), [1]) - single_u2_qc = compiler.transpile(single_u2_qc, self.backend) + single_u2_qc = compiler.transpile(single_u2_qc, self.backend, optimization_level=1) single_u2_sched = compiler.schedule(single_u2_qc, self.backend) # sequential context @@ -809,7 +811,7 @@ def get_sched(qubit_idx: [int], backend): triple_u2_qc.append(circuit.library.U2Gate(0, pi / 2), [0]) triple_u2_qc.append(circuit.library.U2Gate(0, pi / 2), [1]) triple_u2_qc.append(circuit.library.U2Gate(0, pi / 2), [0]) - triple_u2_qc = compiler.transpile(triple_u2_qc, self.backend) + triple_u2_qc = compiler.transpile(triple_u2_qc, self.backend, optimization_level=1) align_left_reference = compiler.schedule(triple_u2_qc, self.backend, method="alap") # measurement diff --git a/test/python/transpiler/test_basis_translator.py b/test/python/transpiler/test_basis_translator.py index e1c8063a4575..9dae6c3f283a 100644 --- a/test/python/transpiler/test_basis_translator.py +++ b/test/python/transpiler/test_basis_translator.py @@ -1106,6 +1106,7 @@ def test_skip_target_basis_equivalences_1(self): circ, basis_gates=["id", "rz", "sx", "x", "cx"], seed_transpiler=42, + optimization_level=1, ) self.assertEqual(circ_transpiled.count_ops(), {"cx": 91, "rz": 66, "sx": 22}) diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py index fd6ae6a01cda..d1ea21cde544 100644 --- a/test/python/transpiler/test_high_level_synthesis.py +++ b/test/python/transpiler/test_high_level_synthesis.py @@ -2118,11 +2118,9 @@ def test_qft_plugins_qft(self, qft_plugin_name): qc.cx(1, 3) qc.append(QFTGate(3).inverse(), [0, 1, 2]) hls_config = HLSConfig(qft=[qft_plugin_name]) - basis_gates = ["cx", "u"] - qct = transpile(qc, hls_config=hls_config, basis_gates=basis_gates) + hls_pass = HighLevelSynthesis(hls_config=hls_config) + qct = hls_pass(qc) self.assertEqual(Operator(qc), Operator(qct)) - ops = set(qct.count_ops().keys()) - self.assertEqual(ops, {"u", "cx"}) @data("line", "full") def test_qft_line_plugin_annotated_qft(self, qft_plugin_name): @@ -2130,11 +2128,9 @@ def test_qft_line_plugin_annotated_qft(self, qft_plugin_name): qc = QuantumCircuit(4) qc.append(QFTGate(3).inverse(annotated=True).control(annotated=True), [0, 1, 2, 3]) hls_config = HLSConfig(qft=[qft_plugin_name]) - basis_gates = ["cx", "u"] - qct = transpile(qc, hls_config=hls_config, basis_gates=basis_gates) + hls_pass = HighLevelSynthesis(hls_config=hls_config) + qct = hls_pass(qc) self.assertEqual(Operator(qc), Operator(qct)) - ops = set(qct.count_ops().keys()) - self.assertEqual(ops, {"u", "cx"}) if __name__ == "__main__": diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 32d39304b44e..949d754573c5 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1219,6 +1219,24 @@ def test_with_backend(self, optimization_level): pm = generate_preset_pass_manager(optimization_level, target) self.assertIsInstance(pm, PassManager) + def test_default_optimization_level(self): + """Test a pass manager is constructed with no optimization level.""" + backend = GenericBackendV2(num_qubits=14, coupling_map=MELBOURNE_CMAP) + pm = generate_preset_pass_manager(backend=backend) + self.assertIsInstance(pm, PassManager) + + def test_default_optimization_level_backend_first_pos_arg(self): + """Test a pass manager is constructed with only a positional backend.""" + backend = GenericBackendV2(num_qubits=14, coupling_map=MELBOURNE_CMAP) + pm = generate_preset_pass_manager(backend) + self.assertIsInstance(pm, PassManager) + + def test_default_optimization_level_target_first_pos_arg(self): + """Test a pass manager is constructed with only a positional target.""" + backend = GenericBackendV2(num_qubits=14, coupling_map=MELBOURNE_CMAP) + pm = generate_preset_pass_manager(backend.target) + self.assertIsInstance(pm, PassManager) + @data(0, 1, 2, 3) def test_with_no_backend(self, optimization_level): """Test a passmanager is constructed with no backend and optimization level.""" diff --git a/test/python/transpiler/test_sabre_layout.py b/test/python/transpiler/test_sabre_layout.py index 0a7b977162a3..4c09bf12efa8 100644 --- a/test/python/transpiler/test_sabre_layout.py +++ b/test/python/transpiler/test_sabre_layout.py @@ -195,7 +195,9 @@ def test_layout_with_classical_bits(self): rz(0) q4835[1]; """ ) - res = transpile(qc, Fake27QPulseV1(), layout_method="sabre", seed_transpiler=1234) + res = transpile( + qc, Fake27QPulseV1(), layout_method="sabre", seed_transpiler=1234, optimization_level=1 + ) self.assertIsInstance(res, QuantumCircuit) layout = res._layout.initial_layout self.assertEqual( @@ -251,6 +253,7 @@ def test_layout_many_search_trials(self): layout_method="sabre", routing_method="stochastic", seed_transpiler=12345, + optimization_level=1, ) self.assertIsInstance(res, QuantumCircuit) layout = res._layout.initial_layout