diff --git a/qiskit/transpiler/passes/layout/vf2_post_layout.py b/qiskit/transpiler/passes/layout/vf2_post_layout.py index 1e874affa7c9..19570d6c4908 100644 --- a/qiskit/transpiler/passes/layout/vf2_post_layout.py +++ b/qiskit/transpiler/passes/layout/vf2_post_layout.py @@ -106,6 +106,7 @@ def __init__( call_limit=None, time_limit=None, strict_direction=True, + max_trials=0, ): """Initialize a ``VF2PostLayout`` pass instance @@ -131,6 +132,8 @@ def __init__( However, if ``strict_direction=True`` the pass expects the input :class:`~.DAGCircuit` object to :meth:`~.VF2PostLayout.run` to be in the target set of instructions. + max_trials (int): The maximum number of trials to run VF2 to find + a layout. A value of ``0`` (the default) means 'unlimited'. Raises: TypeError: At runtime, if neither ``coupling_map`` or ``target`` are provided. @@ -141,6 +144,7 @@ def __init__( self.properties = properties self.call_limit = call_limit self.time_limit = time_limit + self.max_trials = max_trials self.seed = seed self.strict_direction = strict_direction self.avg_error_map = None @@ -310,6 +314,11 @@ def run(self, dag): ) chosen_layout = layout chosen_layout_score = layout_score + + if self.max_trials and trials >= self.max_trials: + logger.debug("Trial %s is >= configured max trials %s", trials, self.max_trials) + break + elapsed_time = time.time() - start_time if self.time_limit is not None and elapsed_time >= self.time_limit: logger.debug( diff --git a/releasenotes/notes/vf2-post-layout-max-trials-98b1c736e2e33861.yaml b/releasenotes/notes/vf2-post-layout-max-trials-98b1c736e2e33861.yaml new file mode 100644 index 000000000000..c4c047871de4 --- /dev/null +++ b/releasenotes/notes/vf2-post-layout-max-trials-98b1c736e2e33861.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Added a new parameter ``max_trials`` to pass :class:`~.VF2PostLayout` + which, when specified, limits the number of layouts discovered and + compared when searching for the best layout. + This differs from existing parameters ``call_limit`` and ``time_limit`` + (which are used to limit the number of state visits performed by the VF2 + algorithm and the total time spent by pass :class:`~.VF2PostLayout`, + respectively) in that it is used to place an upper bound on the time + spent scoring potential layouts, which may be useful for larger + devices. diff --git a/test/python/transpiler/test_vf2_post_layout.py b/test/python/transpiler/test_vf2_post_layout.py index 9b48ecbb445e..d06e46a229bd 100644 --- a/test/python/transpiler/test_vf2_post_layout.py +++ b/test/python/transpiler/test_vf2_post_layout.py @@ -239,6 +239,39 @@ def test_2q_circuit_5q_backend_controlflow(self): self.assertLayout(dag, cmap, pass_.property_set) self.assertNotEqual(pass_.property_set["post_layout"], initial_layout) + def test_2q_circuit_5q_backend_max_trials(self): + """A simple example, without considering the direction + 0 - 1 + qr1 - qr0 + """ + max_trials = 11 + backend = FakeYorktown() + + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) # qr1 -> qr0 + tqc = transpile(circuit, backend, layout_method="dense") + initial_layout = tqc._layout + dag = circuit_to_dag(tqc) + cmap = CouplingMap(backend.configuration().coupling_map) + props = backend.properties() + pass_ = VF2PostLayout( + coupling_map=cmap, properties=props, seed=self.seed, max_trials=max_trials + ) + + with self.assertLogs( + "qiskit.transpiler.passes.layout.vf2_post_layout", level="DEBUG" + ) as cm: + pass_.run(dag) + self.assertIn( + f"DEBUG:qiskit.transpiler.passes.layout.vf2_post_layout:Trial {max_trials} " + f"is >= configured max trials {max_trials}", + cm.output, + ) + + self.assertLayout(dag, cmap, pass_.property_set) + self.assertNotEqual(pass_.property_set["post_layout"], initial_layout) + def test_best_mapping_ghz_state_full_device_multiple_qregs_v2(self): """Test best mappings with multiple registers""" backend = FakeLimaV2()