Repository: Qiskit/qiskit — pull request #10850 (python)
Title: Fix bug in qs_decomposition

<!--\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\nfixes #10787\r\n\r\n\r\n### Details and comments\r\nThe unitary described in this issue was causing a single 2q unitary to be generated in the quantum shannon decomposition, when opt_a2==True, which couldn't take advantage of this diagonal commutation optimization and would error. The fix instead leaves this as an undecomposed 2-qubit unitary gate.\r\n\r\n

Resolved issues:
title: ```Gate.control``` method throwing error
body: ### Environment\r\n\r\n- **Qiskit Terra version**: 0.24.0\r\n- **Python version**: Python 3.9.6\r\n\r\n### What is happening?\r\n\r\nGiven a quantum circuit object, we execute the ```to_gate()``` method and attempt to call the ```Gate.control()``` method with appropriate parameters and sometimes observe the following error: ```*** UnboundLocalError: local variable 'mat2'``` referenced before assignment\r\n\r\n### How can we reproduce the issue?\r\n\r\n``` \r\nfrom qiskit.extensions import UnitaryGate\r\nfrom qiskit import QuantumCircuit, QuantumRegister\r\n\r\n# initialize a unitary matrix\r\n\r\nA = np.array([[ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j,\r\n         0.+0.j],\r\n       [ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  1.+0.j,  0.+0.j,\r\n         0.+0.j],\r\n       [ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j, -1.+0.j,\r\n         0.+0.j],\r\n       [ 0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,\r\n        -1.+0.j],\r\n       [ 1.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,\r\n         0.+0.j],\r\n       [ 0.+0.j,  1.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,\r\n         0.+0.j],\r\n       [ 0.+0.j,  0.+0.j, -1.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,\r\n         0.+0.j],\r\n       [ 0.+0.j,  0.+0.j,  0.+0.j, -1.+0.j,  0.+0.j,  0.+0.j,  0.+0.j,\r\n         0.+0.j]])\r\n\r\ngate = UnitaryGate(A)\r\nqc = QuantumCircuit(3)\r\nqc.append(gate,[0,1,2])\r\n\r\n# This happens in a a different function than the operations above\r\n\r\nCU = qc.to_gate().control(6,28)\r\n```\r\n\r\n### What should happen?\r\n\r\nI would assume that the error is not thrown\r\n\r\n### Any suggestions?\r\n\r\nThe error appears to come from the file ```qsd.py```, and in particular because the list ```ind2q``` (line 234) has only one element. The for loop that starts on line 242 is thus never executed and the the variable ```mat2``` is never initialized. 

Hints:

Fix patch:
diff --git a/qiskit/quantum_info/synthesis/qsd.py b/qiskit/quantum_info/synthesis/qsd.py\nindex b2f00a56045d..80892a606d7e 100644\n--- a/qiskit/quantum_info/synthesis/qsd.py\n+++ b/qiskit/quantum_info/synthesis/qsd.py\n@@ -243,7 +243,11 @@ def _apply_a2(circ):\n     for i, instruction in enumerate(ccirc.data):\n         if instruction.operation.name == "qsd2q":\n             ind2q.append(i)\n-    if not ind2q:\n+    if len(ind2q) == 0:\n+        return ccirc\n+    elif len(ind2q) == 1:\n+        # No neighbors to merge diagonal into; revert name\n+        ccirc.data[ind2q[0]].operation.name = "Unitary"\n         return ccirc\n     # rolling over diagonals\n     ind2 = None  # lint\ndiff --git a/releasenotes/notes/10787-078d7caa70fc7de8.yaml b/releasenotes/notes/10787-078d7caa70fc7de8.yaml\nnew file mode 100644\nindex 000000000000..ae22987bb75f\n--- /dev/null\n+++ b/releasenotes/notes/10787-078d7caa70fc7de8.yaml\n@@ -0,0 +1,7 @@\n+---\n+fixes:\n+  - |\n+    Fix the Quantum Shannon Decomposition implemented in :func:`.qs_decomposition`. When a unitary\n+    could not take advantage of the diagonal commutation optimization, it used to error.\n+    Now, it leaves it as undecomposed 2-qubit unitary gate.\n+    Fixes `#10787 <https://github.com/Qiskit/qiskit/issues/10787>`__\n

Test patch:
diff --git a/test/python/quantum_info/test_synthesis.py b/test/python/quantum_info/test_synthesis.py\nindex 19ec8b8139e9..043abf8c87f8 100644\n--- a/test/python/quantum_info/test_synthesis.py\n+++ b/test/python/quantum_info/test_synthesis.py\n@@ -1548,6 +1548,105 @@ def test_opt_a1a2(self, nqubits):\n         elif nqubits == 2:\n             self.assertLessEqual(ccirc.count_ops().get("cx", 0), 3)\n \n+    def test_a2_opt_single_2q(self):\n+        """\n+        Test a2_opt when a unitary causes a single final 2-qubit unitary for which this optimization\n+        won't help. This came up in issue 10787.\n+        """\n+        # this somewhat unique signed permutation matrix seems to cause the issue\n+        mat = np.array(\n+            [\n+                [\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    1.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                ],\n+                [\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    1.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                ],\n+                [\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    -1.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                ],\n+                [\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    -1.0 + 0.0j,\n+                ],\n+                [\n+                    1.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                ],\n+                [\n+                    0.0 + 0.0j,\n+                    1.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                ],\n+                [\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    -1.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                ],\n+                [\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    -1.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                    0.0 + 0.0j,\n+                ],\n+            ]\n+        )\n+\n+        gate = UnitaryGate(mat)\n+        qc = QuantumCircuit(3)\n+        qc.append(gate, range(3))\n+        try:\n+            qc.to_gate().control(1)\n+        except UnboundLocalError as uerr:\n+            self.fail(str(uerr))\n+\n \n class TestTwoQubitDecomposeUpToDiagonal(QiskitTestCase):\n     """test TwoQubitDecomposeUpToDiagonal class"""\n