Repository: Qiskit/qiskit — pull request #14349 (python)
Title: Bug fix in HighLevelSynthesis fast-return mechanism

<!--\r\n⚠️ If you do not respect this template, your pull request will be closed.\r\n⚠️ Your pull request title should be short detailed and understandable for all.\r\n⚠️ Also, please add a release note file using reno if the change needs to be\r\n  documented in the release notes.\r\n⚠️ If your pull request fixes an open issue, please link to the issue.\r\n\r\n- [x] I have added the tests to cover my changes.\r\n- [x] I have updated the documentation accordingly.\r\n- [x] I have read the CONTRIBUTING document.\r\n-->\r\n\r\n### Summary\r\n\r\nThe `HighLevelSynthesis` transpiler pass checks whether all of the circuit instructions are already natively supported, in which case the pass terminates early. Previously this check called `dag.count_ops(py, recurse=false`), which however is not correct on circuits with control-flow operations when operations within the blocks that are not natively supported. The PR simply changes `recurse=false` to `recurse=true`.\r\n\r\n### Details and comments\r\n\r\nThis should fix #14338 and #14254.

Resolved issues:
title: `TranspilerError` occurs when transpiling a circuit with `for_loop`
body: ### Environment\n\n- **Qiskit version**: 2.0.0\n- **Python version**: 3.13.2\n- **Operating system**: Windows 11, Version 24H2, Build 26100\n\n\n### What is happening?\n\nWhen transpiling a circuit that uses an `for_loop` conditional statement, a `TranspilerError` occurs.\n```\nqiskit.transpiler.exceptions.TranspilerError: 'Unable to translate the operations in the circuit: ["for_loop", "clifford"] to the backend\\'s (or manually specified) target basis: {"mcy", "sxdg", "rzx", "mcx", "unitary", "switch_case", "delay", "initialize", "h", "cswap", "mcx_gray", "x", "reset", "u2", "crx", "diagonal", "set_stabilizer", "mcp", "cu2", "save_density_matrix", "mcu3", "mcrz", "pauli", "mcsx", "cu3", "set_density_matrix", "set_statevector", "z", "save_expval_var", "save_stabilizer", "crz", "cp", "ry", "save_state", "measure", "save_matrix_product_state", "qerror_loc", "save_amplitudes", "save_superop", "save_expval", "cy", "save_statevector", "tdg", "mcz", "ccx", "cz", "cu", "u3", "continue_loop", "for_loop", "t", "mcu1", "quantum_channel", "save_statevector_dict", "roerror", "ecr", "cu1", "if_else", "s", "cry", "save_unitary", "cx", "mcphase", "ccz", "superop", "store", "mcrx", "u1", "set_unitary", "save_clifford", "rxx", "set_matrix_product_state", "rz", "mcu", "mcry", "u", "break_loop", "rx", "sx", "save_amplitudes_sq", "sdg", "csx", "ryy", "p", "id", "mcu2", "save_probabilities", "multiplexer", "save_probabilities_dict", "while_loop", "kraus", "r", "set_superop", "y", "rzz", "swap", "mcswap", "mcr", "barrier", "snapshot"}. This likely means the target basis is not universal or there are additional equivalence rules needed in the EquivalenceLibrary being used. For more details on this error see: https://docs.quantum.ibm.com/api/qiskit/qiskit.transpiler.passes. BasisTranslator#translation-errors'\n```\n\n### How can we reproduce the issue?\n\nMinimal Working Example: \n```\nfrom qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile\nfrom qiskit_aer import Aer\nfrom qiskit.transpiler.passes import *\nfrom qiskit.transpiler import PassManager\n\nqreg = QuantumRegister(5)\ncreg = ClassicalRegister(5)\nqc = QuantumCircuit(qreg, creg)\n\nwith qc.for_loop(range(5)) as i:\n\tqc.cx(4, 3)\n\tqc.cz(0, 4)\n\nqc.measure(qreg[0], creg[0])\n\nsimulator = Aer.get_backend("aer_simulator")\n\np = PassManager(CollectCliffords())\nqc = p.run(qc)\n\ncompiled_circuit = transpile(qc, backend = simulator, optimization_level = 3, routing_method = "sabre", layout_method = "noise_adaptive", approximation_degree = 1 )\njob = simulator.run(compiled_circuit, shots=10000)\nresult = job.result().get_counts()\nprint(result)\n```\n\n### What should happen?\n\nThe circuit should be successfully transpiled and executed. \n\n### Any suggestions?\n\n_No response_

Hints:

Fix patch:
diff --git a/crates/transpiler/src/passes/high_level_synthesis.rs b/crates/transpiler/src/passes/high_level_synthesis.rs\nindex 162a23ce890d..a434efdec012 100644\n--- a/crates/transpiler/src/passes/high_level_synthesis.rs\n+++ b/crates/transpiler/src/passes/high_level_synthesis.rs\n@@ -333,7 +333,7 @@ fn all_instructions_supported(\n     data: &Bound<HighLevelSynthesisData>,\n     dag: &DAGCircuit,\n ) -> PyResult<bool> {\n-    let ops = dag.count_ops(py, false)?;\n+    let ops = dag.count_ops(py, true)?;\n     let mut op_keys = ops.keys();\n \n     let borrowed_data = data.borrow();\ndiff --git a/releasenotes/notes/fix-hls-fast-return-6daff39a5eba40ce.yaml b/releasenotes/notes/fix-hls-fast-return-6daff39a5eba40ce.yaml\nnew file mode 100644\nindex 000000000000..70abdd361183\n--- /dev/null\n+++ b/releasenotes/notes/fix-hls-fast-return-6daff39a5eba40ce.yaml\n@@ -0,0 +1,7 @@\n+---\n+fixes:\n+  - |\n+    Fixed a problem in :class:`.HighLevelSynthesis` transpiler pass that caused\n+    it to erroneously terminate early on certain circuits with control flow\n+    operations.\n+    Fixed `#14338 <https://github.com/Qiskit/qiskit-terra/issues/14338>`__\n

Test patch:
diff --git a/test/python/transpiler/test_high_level_synthesis.py b/test/python/transpiler/test_high_level_synthesis.py\nindex 25ee44270553..8999fb8408d7 100644\n--- a/test/python/transpiler/test_high_level_synthesis.py\n+++ b/test/python/transpiler/test_high_level_synthesis.py\n@@ -742,6 +742,21 @@ def test_track_global_phase(self):\n             expected_block.cx(0, 1)\n             self.assertEqual(transpiled_block, expected_block)\n \n+    def test_control_flow(self):\n+        """Test that the pass recurses into control-flow ops."""\n+        clifford_circuit = QuantumCircuit(3)\n+        clifford_circuit.cx(1, 0)\n+        clifford_circuit.cz(0, 2)\n+        cliff = Clifford(clifford_circuit)\n+\n+        qc = QuantumCircuit(5, 5)\n+        with qc.for_loop(range(3)):\n+            qc.append(cliff, [0, 1, 4])\n+\n+        transpiled = HighLevelSynthesis(basis_gates=["cx", "u", "for_loop"])(qc)\n+        transpiled_block = transpiled[0].operation.blocks[0]\n+        self.assertNotIn("clifford", transpiled_block.count_ops())\n+\n \n class TestHighLevelSynthesisQuality(QiskitTestCase):\n     """Test the "quality" of circuits produced by HighLevelSynthesis."""\n