Repository: Qiskit/qiskit — pull request #12988 (python)
Title: Fix `StatePreparation`'s ``normalize`` argument

<!--\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- [ ] I have added the tests to cover my changes.\r\n- [ ] I have updated the documentation accordingly.\r\n- [ ] I have read the CONTRIBUTING document.\r\n-->\r\n\r\n### Summary\r\n\r\nFixes #12984. The definition didn't use the normalized parameters, if the input is synthesized by isometry.\r\n\r\n\r\n\r\n

Resolved issues:
title: Passmanager fails to transpile a basic StatePreparation gate
body: ### Environment\n\n- **Qiskit version**:  1.2.0\r\n- **Python version**: 3.12\r\n- **Operating system**: Ubuntu 24.04\r\n\n\n### What is happening?\n\nI am trying to execute the following circuit:\r\n\r\n```python\r\nfrom qiskit import QuantumCircuit, __version__ as qversion\r\nfrom qiskit.circuit.library import StatePreparation\r\nfrom qiskit_aer import AerSimulator, __version__ as qaversion\r\nfrom qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\r\n\r\nprint(qversion, '/', qaversion) # 1.2.0 / 0.14.2\r\n\r\npm = generate_preset_pass_manager(backend=AerSimulator(), optimization_level=3)\r\n\r\nqc =  QuantumCircuit(1)\r\nqc.compose(StatePreparation([1, 1], normalize=True), range(1), inplace=True)\r\n\r\npm.run(qc)\r\n```\r\n\r\nAnd I am getting the following error:\r\n\r\n```\r\n---------------------------------------------------------------------------\r\nQiskitError                               Traceback (most recent call last)\r\nCell In[21], line 13\r\n     10 qc =  QuantumCircuit(1)\r\n     11 qc.compose(StatePreparation([1, 1], normalize=True), range(1), inplace=True)\r\n---> 13 pm.run(qc)\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/transpiler/passmanager.py:441, in StagedPassManager.run(self, circuits, output_name, callback, num_processes)\r\n    433 def run(\r\n    434     self,\r\n    435     circuits: _CircuitsT,\r\n   (...)\r\n    438     num_processes: int = None,\r\n    439 ) -> _CircuitsT:\r\n    440     self._update_passmanager()\r\n--> 441     return super().run(circuits, output_name, callback, num_processes=num_processes)\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/transpiler/passmanager.py:464, in _replace_error.<locals>.wrapper(*meth_args, **meth_kwargs)\r\n    461 @wraps(meth)\r\n    462 def wrapper(*meth_args, **meth_kwargs):\r\n    463     try:\r\n--> 464         return meth(*meth_args, **meth_kwargs)\r\n    465     except PassManagerError as ex:\r\n    466         raise TranspilerError(ex.message) from ex\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/transpiler/passmanager.py:226, in PassManager.run(self, circuits, output_name, callback, num_processes)\r\n    223 if callback is not None:\r\n    224     callback = _legacy_style_callback(callback)\r\n--> 226 return super().run(\r\n    227     in_programs=circuits,\r\n    228     callback=callback,\r\n    229     output_name=output_name,\r\n    230     num_processes=num_processes,\r\n    231 )\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/passmanager/passmanager.py:232, in BasePassManager.run(self, in_programs, callback, num_processes, **kwargs)\r\n    228 # If we're not going to run in parallel, we want to avoid spending time `dill` serializing\r\n    229 # ourselves, since that can be quite expensive.\r\n    230 if len(in_programs) == 1 or not should_run_in_parallel(num_processes):\r\n    231     out = [\r\n--> 232         _run_workflow(program=program, pass_manager=self, callback=callback, **kwargs)\r\n    233         for program in in_programs\r\n    234     ]\r\n    235     if len(in_programs) == 1 and not is_list:\r\n    236         return out[0]\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/passmanager/passmanager.py:292, in _run_workflow(program, pass_manager, **kwargs)\r\n    286 initial_status = WorkflowStatus()\r\n    288 passmanager_ir = pass_manager._passmanager_frontend(\r\n    289     input_program=program,\r\n    290     **kwargs,\r\n    291 )\r\n--> 292 passmanager_ir, final_state = flow_controller.execute(\r\n    293     passmanager_ir=passmanager_ir,\r\n    294     state=PassManagerState(\r\n    295         workflow_status=initial_status,\r\n    296         property_set=PropertySet(),\r\n    297     ),\r\n    298     callback=kwargs.get("callback", None),\r\n    299 )\r\n    300 # The `property_set` has historically been returned as a mutable attribute on `PassManager`\r\n    301 # This makes us non-reentrant (though `PassManager` would be dependent on its internal tasks to\r\n    302 # be re-entrant if that was required), but is consistent with previous interfaces.  We're still\r\n    303 # safe to be called in a serial loop, again assuming internal tasks are re-runnable.  The\r\n    304 # conversion to the backend language is also allowed to use the property set, so it must be set\r\n    305 # before calling it.\r\n    306 pass_manager.property_set = final_state.property_set\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/passmanager/base_tasks.py:218, in BaseController.execute(self, passmanager_ir, state, callback)\r\n    216     return passmanager_ir, state\r\n    217 while True:\r\n--> 218     passmanager_ir, state = next_task.execute(\r\n    219         passmanager_ir=passmanager_ir,\r\n    220         state=state,\r\n    221         callback=callback,\r\n    222     )\r\n    223     try:\r\n    224         # Sending the object through the generator implies the custom controllers\r\n    225         # can always rely on the latest data to choose the next task to run.\r\n    226         next_task = task_generator.send(state)\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/transpiler/basepasses.py:195, in TransformationPass.execute(self, passmanager_ir, state, callback)\r\n    189 def execute(\r\n    190     self,\r\n    191     passmanager_ir: PassManagerIR,\r\n    192     state: PassManagerState,\r\n    193     callback: Callable = None,\r\n    194 ) -> tuple[PassManagerIR, PassManagerState]:\r\n--> 195     new_dag, state = super().execute(\r\n    196         passmanager_ir=passmanager_ir,\r\n    197         state=state,\r\n    198         callback=callback,\r\n    199     )\r\n    201     if state.workflow_status.previous_run == RunState.SUCCESS:\r\n    202         if isinstance(new_dag, DAGCircuit):\r\n    203             # Copy calibration data from the original program\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/passmanager/base_tasks.py:98, in GenericPass.execute(self, passmanager_ir, state, callback)\r\n     96 try:\r\n     97     if self not in state.workflow_status.completed_passes:\r\n---> 98         ret = self.run(passmanager_ir)\r\n     99         run_state = RunState.SUCCESS\r\n    100     else:\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/transpiler/passes/synthesis/high_level_synthesis.py:443, in HighLevelSynthesis.run(self, dag)\r\n    440 if self._definitely_skip_node(node, qubits):\r\n    441     continue\r\n--> 443 decomposition, modified = self._recursively_handle_op(node.op, qubits)\r\n    445 if not modified:\r\n    446     continue\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/transpiler/passes/synthesis/high_level_synthesis.py:551, in HighLevelSynthesis._recursively_handle_op(self, op, qubits)\r\n    547         return op, False\r\n    549 try:\r\n    550     # extract definition\r\n--> 551     definition = op.definition\r\n    552 except TypeError as err:\r\n    553     raise TranspilerError(\r\n    554         f"HighLevelSynthesis was unable to extract definition for {op.name}: {err}"\r\n    555     ) from err\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/circuit/instruction.py:312, in Instruction.definition(self)\r\n    310 """Return definition in terms of other basic gates."""\r\n    311 if self._definition is None:\r\n--> 312     self._define()\r\n    313 return self._definition\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/circuit/library/data_preparation/state_preparation.py:122, in StatePreparation._define(self)\r\n    120     self.definition = self._define_from_int()\r\n    121 else:\r\n--> 122     self.definition = self._define_synthesis_isom()\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/circuit/library/data_preparation/state_preparation.py:176, in StatePreparation._define_synthesis_isom(self)\r\n    173 q = QuantumRegister(self.num_qubits, "q")\r\n    174 initialize_circuit = QuantumCircuit(q, name="init_def")\r\n--> 176 isom = Isometry(self._params_arg, 0, 0)\r\n    177 initialize_circuit.append(isom, q[:])\r\n    179 # invert the circuit to create the desired vector from zero (assuming\r\n    180 # the qubits are in the zero state)\r\n\r\nFile ~/anaconda3/envs/building-agi/lib/python3.12/site-packages/qiskit/circuit/library/generalized_gates/isometry.py:112, in Isometry.__init__(self, isometry, num_ancillas_zero, num_ancillas_dirty, epsilon)\r\n    108     raise QiskitError(\r\n    109         "The input matrix has more columns than rows and hence it can't be an isometry."\r\n    110     )\r\n    111 if not is_isometry(isometry, self._epsilon):\r\n--> 112     raise QiskitError(\r\n    113         "The input matrix has non orthonormal columns and hence it is not an isometry."\r\n    114     )\r\n    116 num_qubits = int(n) + num_ancillas_zero + num_ancillas_dirty\r\n    118 super().__init__("isometry", num_qubits, 0, [isometry])\r\n\r\nQiskitError: 'The input matrix has non orthonormal columns and hence it is not an isometry.'\r\n```\r\n\r\n\r\nAs far as I remember, similar code used to work on previous versions of Qiskit.\n\n### How can we reproduce the issue?\n\nHere is a minimal code snippet to reproduce the error:\r\n\r\n```python\r\nfrom qiskit import QuantumCircuit, __version__ as qversion\r\nfrom qiskit.circuit.library import StatePreparation\r\nfrom qiskit_aer import AerSimulator, __version__ as qaversion\r\nfrom qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\r\n\r\nprint(qversion, '/', qaversion) # 1.2.0 / 0.14.2\r\n\r\npm = generate_preset_pass_manager(backend=AerSimulator(), optimization_level=3)\r\n\r\nqc =  QuantumCircuit(1)\r\nqc.compose(StatePreparation([1, 1], normalize=True), range(1), inplace=True)\r\n\r\npm.run(qc)\r\n```\n\n### What should happen?\n\nThe transpilation of the `StatePreparation` gate :) \n\n### Any suggestions?\n\n_No response_

Hints:

Fix patch:
diff --git a/qiskit/circuit/library/data_preparation/state_preparation.py b/qiskit/circuit/library/data_preparation/state_preparation.py\nindex 26c37334cfe4..df69f9eab8c5 100644\n--- a/qiskit/circuit/library/data_preparation/state_preparation.py\n+++ b/qiskit/circuit/library/data_preparation/state_preparation.py\n@@ -173,7 +173,7 @@ def _define_synthesis_isom(self):\n         q = QuantumRegister(self.num_qubits, "q")\n         initialize_circuit = QuantumCircuit(q, name="init_def")\n \n-        isom = Isometry(self._params_arg, 0, 0)\n+        isom = Isometry(self.params, 0, 0)\n         initialize_circuit.append(isom, q[:])\n \n         # invert the circuit to create the desired vector from zero (assuming\ndiff --git a/releasenotes/notes/fix-stateprep-normalize-a8057c339ba619bd.yaml b/releasenotes/notes/fix-stateprep-normalize-a8057c339ba619bd.yaml\nnew file mode 100644\nindex 000000000000..0175f8c1c026\n--- /dev/null\n+++ b/releasenotes/notes/fix-stateprep-normalize-a8057c339ba619bd.yaml\n@@ -0,0 +1,6 @@\n+---\n+fixes:\n+  - |\n+    Fixed a bug in :class:`.StatePreparation` where the ``normalize`` \n+    argument was ignored for input arrays.\n+    Fixed `#12984 <https://github.com/Qiskit/qiskit/issues/12984>`__.\n

Test patch:
diff --git a/test/python/circuit/library/test_state_preparation.py b/test/python/circuit/library/test_state_preparation.py\nindex adad848a2777..b5446ed37e9c 100644\n--- a/test/python/circuit/library/test_state_preparation.py\n+++ b/test/python/circuit/library/test_state_preparation.py\n@@ -113,6 +113,16 @@ def test_repeats(self):\n         qc.append(StatePreparation("01").repeat(2), [0, 1])\n         self.assertEqual(qc.decompose().count_ops()["state_preparation"], 2)\n \n+    def test_normalize(self):\n+        """Test the normalization.\n+\n+        Regression test of #12984.\n+        """\n+        qc = QuantumCircuit(1)\n+        qc.compose(StatePreparation([1, 1], normalize=True), range(1), inplace=True)\n+\n+        self.assertTrue(Statevector(qc).equiv(np.array([1, 1]) / np.sqrt(2)))\n+\n \n if __name__ == "__main__":\n     unittest.main()\n