Fix SabreSwap with classically conditioned gates (Qiskit/qiskit#8041)

### Summary\r\n\r\nThe calculation of the expected number of predecessors for a gate to\r\nbe considered "resolved" in `SabreSwap` was not accounting for wires\r\nstemming from classical conditions.  The tracking of the _actual_ number\r\nof predecessor requirements satisfied did correctly account for this, so\r\nin certain circumstances the actual count could jump from "too low" to\r\n"too high" without passing through "just right", and the gate would\r\nnever get added to the circuit.\r\n\r\n<!--\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\r\n\r\n### Details and comments\r\n\r\nFix #8040.

`SabreSwap` can throw away classically conditioned gates and all successors

### Environment\r\n\r\n- **Qiskit Terra version**: main @ 7726d4704\r\n- **Python version**: any\r\n- **Operating system**: any\r\n\r\n\r\n### What is happening?\r\n\r\nThe `SabreSwap` transpiler pass can sometimes remove classically conditioned gates from the circuit, and all their successors, whether or not the successors have classical conditions.\r\n\r\nThis is (I believe) the cause of the current failures in the randomised test suite (from https://github.com/Qiskit/qiskit-terra/issues/2645#issuecomment-1111747082 to https://github.com/Qiskit/qiskit-terra/issues/2645#issuecomment-1121930469).\r\n\r\n### How can we reproduce the issue?\r\n\r\n```python\r\nIn [1]: from qiskit import QuantumCircuit\r\n   ...: from qiskit.transpiler import PassManager, CouplingMap\r\n   ...: from qiskit.transpiler.passes import SabreSwap, TrivialLayout\r\n   ...:\r\n   ...: qc = QuantumCircuit(2, 1)\r\n   ...: qc.z(0)\r\n   ...: qc.z(0).c_if(qc.cregs[0], 0)\r\n   ...: print(qc.draw())\r\n   ...: print()\r\n   ...: cm = CouplingMap([(0, 1), (1, 0)])\r\n   ...: print(PassManager([TrivialLayout(cm), SabreSwap(cm)]).run(qc).draw())\r\n     ┌───┐ ┌───┐\r\nq_0: ┤ Z ├─┤ Z ├─\r\n     └───┘ └─╥─┘\r\nq_1: ────────╫───\r\n          ┌──╨──┐\r\nc: 1/═════╡ 0x0 ╞\r\n          └─────┘\r\n\r\n         ┌───┐\r\nq_0 -> 0 ┤ Z ├\r\n         └───┘\r\nq_1 -> 1 ─────\r\n              \r\n    c: 1/═════           \r\n```\r\n\r\n### What should happen?\r\n\r\nThe transpiled circuit should not have lost the second classically controlled gate.\r\n\r\n### Any suggestions?\r\n\r\nConditions currently aren't being accounted for in the "expected number of predecessors" calculation that determines whether a gate is eligible to be added to the circuit.  The current `_is_resolved` check, since #7952, is:\r\n```python\r\nself.applied_predecessors[node] == len(node.qargs) + len(node.cargs)\r\n```\r\nbut conditions are not part of the `cargs`.  Since the condition is _equality_ to the known value, if the relevant node is not in the original front layer, its `applied_predecessors` (which correctly account for conditions) can be increased beyond the count of its qargs and cargs, so the condition never triggers.\r\n\r\n(#7952 didn't cause this issue, it just didn't completely address the issue of all classical arguments.)\r\n\r\nThe fix is to account for all possible predecessors in `SabreSwap._is_resolved`.

diff --git a/qiskit/transpiler/passes/routing/sabre_swap.py b/qiskit/transpiler/passes/routing/sabre_swap.py\nindex c6555b069f10..1a1530508d4f 100644\n--- a/qiskit/transpiler/passes/routing/sabre_swap.py\n+++ b/qiskit/transpiler/passes/routing/sabre_swap.py\n@@ -139,7 +139,7 @@ def __init__(\n         self.heuristic = heuristic\n         self.seed = seed\n         self.fake_run = fake_run\n-        self.applied_predecessors = None\n+        self.required_predecessors = None\n         self.qubits_decay = None\n         self._bit_indices = None\n         self.dist_matrix = None\n@@ -188,12 +188,9 @@ def run(self, dag):\n         self.qubits_decay = dict.fromkeys(dag.qubits, 1)\n \n         # Start algorithm from the front layer and iterate until all gates done.\n+        self.required_predecessors = self._build_required_predecessors(dag)\n         num_search_steps = 0\n         front_layer = dag.front_layer()\n-        self.applied_predecessors = defaultdict(int)\n-        for _, input_node in dag.input_map.items():\n-            for successor in self._successors(input_node, dag):\n-                self.applied_predecessors[successor] += 1\n \n         while front_layer:\n             execute_gate_list = []\n@@ -228,7 +225,7 @@ def run(self, dag):\n                 for node in execute_gate_list:\n                     self._apply_gate(mapped_dag, node, current_layout, canonical_register)\n                     for successor in self._successors(node, dag):\n-                        self.applied_predecessors[successor] += 1\n+                        self.required_predecessors[successor] -= 1\n                         if self._is_resolved(successor):\n                             front_layer.append(successor)\n \n@@ -314,6 +311,15 @@ def _reset_qubits_decay(self):\n         """\n         self.qubits_decay = {k: 1 for k in self.qubits_decay.keys()}\n \n+    def _build_required_predecessors(self, dag):\n+        out = defaultdict(int)\n+        # We don't need to count in- or out-wires: outs can never be predecessors, and all input\n+        # wires are automatically satisfied at the start.\n+        for node in dag.op_nodes():\n+            for successor in self._successors(node, dag):\n+                out[successor] += 1\n+        return out\n+\n     def _successors(self, node, dag):\n         """Return an iterable of the successors along each wire from the given node.\n \n@@ -326,22 +332,22 @@ def _successors(self, node, dag):\n \n     def _is_resolved(self, node):\n         """Return True if all of a node's predecessors in dag are applied."""\n-        return self.applied_predecessors[node] == len(node.qargs) + len(node.cargs)\n+        return self.required_predecessors[node] == 0\n \n     def _obtain_extended_set(self, dag, front_layer):\n         """Populate extended_set by looking ahead a fixed number of gates.\n         For each existing element add a successor until reaching limit.\n         """\n         extended_set = []\n-        incremented = []\n+        decremented = []\n         tmp_front_layer = front_layer\n         done = False\n         while tmp_front_layer and not done:\n             new_tmp_front_layer = []\n             for node in tmp_front_layer:\n                 for successor in self._successors(node, dag):\n-                    incremented.append(successor)\n-                    self.applied_predecessors[successor] += 1\n+                    decremented.append(successor)\n+                    self.required_predecessors[successor] -= 1\n                     if self._is_resolved(successor):\n                         new_tmp_front_layer.append(successor)\n                         if len(successor.qargs) == 2:\n@@ -350,8 +356,8 @@ def _obtain_extended_set(self, dag, front_layer):\n                     done = True\n                     break\n             tmp_front_layer = new_tmp_front_layer\n-        for node in incremented:\n-            self.applied_predecessors[node] -= 1\n+        for node in decremented:\n+            self.required_predecessors[node] += 1\n         return extended_set\n \n     def _obtain_swaps(self, front_layer, current_layout):\ndiff --git a/releasenotes/notes/sabreswap-fix-condition-593f36e855f9064c.yaml b/releasenotes/notes/sabreswap-fix-condition-593f36e855f9064c.yaml\nnew file mode 100644\nindex 000000000000..376014ce45b8\n--- /dev/null\n+++ b/releasenotes/notes/sabreswap-fix-condition-593f36e855f9064c.yaml\n@@ -0,0 +1,8 @@\n+---\n+fixes:\n+  - |\n+    The :class:`.SabreSwap` transpiler pass, used in :func:`.transpile` when\n+    ``routing_method="sabre"`` is set, will no longer sporadically drop\n+    classically conditioned gates and their successors from circuits during the\n+    routing phase of transpilation.  See\n+    `#8040 <https://github.com/Qiskit/qiskit-terra/issues/8040>`__.\n

diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py\nindex f7a9e96cfd62..89e43b91491d 100644\n--- a/test/python/transpiler/test_sabre_swap.py\n+++ b/test/python/transpiler/test_sabre_swap.py\n@@ -17,9 +17,9 @@\n import ddt\n \n from qiskit.circuit.library import CCXGate, HGate, Measure, SwapGate\n-from qiskit.transpiler.passes import SabreSwap\n+from qiskit.transpiler.passes import SabreSwap, TrivialLayout\n from qiskit.transpiler import CouplingMap, PassManager\n-from qiskit import QuantumRegister, QuantumCircuit\n+from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit\n from qiskit.test import QiskitTestCase\n from qiskit.utils import optionals\n \n@@ -237,6 +237,31 @@ def leak_number_of_swaps(cls, *args, **kwargs):\n         out_results = sim.run(routed, shots=4096).result().get_counts()\n         self.assertEqual(set(in_results), set(out_results))\n \n+    def test_classical_condition(self):\n+        """Test that :class:`.SabreSwap` correctly accounts for classical conditions in its\n+        reckoning on whether a node is resolved or not.  If it is not handled correctly, the second\n+        gate might not appear in the output.\n+\n+        Regression test of gh-8040."""\n+        with self.subTest("1 bit in register"):\n+            qc = QuantumCircuit(2, 1)\n+            qc.z(0)\n+            qc.z(0).c_if(qc.cregs[0], 0)\n+            cm = CouplingMap([(0, 1), (1, 0)])\n+            expected = PassManager([TrivialLayout(cm)]).run(qc)\n+            actual = PassManager([TrivialLayout(cm), SabreSwap(cm)]).run(qc)\n+            self.assertEqual(expected, actual)\n+        with self.subTest("multiple registers"):\n+            cregs = [ClassicalRegister(3), ClassicalRegister(4)]\n+            qc = QuantumCircuit(QuantumRegister(2, name="q"), *cregs)\n+            qc.z(0)\n+            qc.z(0).c_if(cregs[0], 0)\n+            qc.z(0).c_if(cregs[1], 0)\n+            cm = CouplingMap([(0, 1), (1, 0)])\n+            expected = PassManager([TrivialLayout(cm)]).run(qc)\n+            actual = PassManager([TrivialLayout(cm), SabreSwap(cm)]).run(qc)\n+            self.assertEqual(expected, actual)\n+\n \n if __name__ == "__main__":\n     unittest.main()\n


Fix `SolovayKitaev` for roundoff errors in Qiskit gates (Qiskit/qiskit#9441)

<!--\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 #9437.\r\n\r\n\r\n

Solovay-Kitaev, ValueError: Input matrix is not orthogonal.

### Environment\r\n\r\n- **Commit hash**:  dc8e232a\r\n- **Python version**: 3.8.7\r\n- **Operating system**: Ubuntu 20.04.5 LTS\r\n- **Qiskit Terra version**: 0.24.0\r\n\r\n### What is happening?\r\n\r\nI'm trying to run the newly added SolovayKitaev (SK) algorithm. For some simple qiskit circuits the SK works, but most of the time it produces the following error:\r\n\r\n```\r\n---------------------------------------------------------------------------\r\nValueError                                Traceback (most recent call last)\r\nCell In[13], line 15\r\n     12 # Time computation\r\n     13 start_time = time.time()\r\n---> 15 discretized = skd(transpiled_circ_qasm)\r\n     17 print("\\t Time of conversion:", (time.time() - start_time) / 60, " min")\r\n\r\nFile ~/.pyenv/versions/circuit_comp_qiskit_dev/lib/python3.8/site-packages/qiskit/transpiler/basepasses.py:121, in BasePass.__call__(self, circuit, property_set)\r\n    118 if isinstance(property_set_, PropertySet):\r\n    119     self.property_set = property_set_\r\n--> 121 result = self.run(circuit_to_dag(circuit))\r\n    123 result_circuit = circuit\r\n    125 if isinstance(property_set, dict):  # this includes (dict, PropertySet)\r\n\r\nFile ~/.pyenv/versions/circuit_comp_qiskit_dev/lib/python3.8/site-packages/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py:158, in SolovayKitaev.run(self, dag)\r\n    155 matrix = node.op.to_matrix()\r\n    157 # call solovay kitaev\r\n--> 158 approximation = self._sk.run(matrix, self.recursion_degree, return_dag=True)\r\n    160 # convert to a dag and replace the gate by the approximation\r\n    161 dag.substitute_node_with_dag(node, approximation)\r\n\r\nFile ~/.pyenv/versions/circuit_comp_qiskit_dev/lib/python3.8/site-packages/qiskit/synthesis/discrete_basis/solovay_kitaev.py:107, in SolovayKitaevDecomposition.run(self, gate_matrix, recursion_degree, return_dag)\r\n    104 global_phase = np.arctan2(np.imag(z), np.real(z))\r\n    106 # get the decompositon as GateSequence type\r\n--> 107 decomposition = self._recurse(gate_matrix_su2, recursion_degree)\r\n    109 # simplify\r\n    110 _remove_identities(decomposition)\r\n\r\nFile ~/.pyenv/versions/circuit_comp_qiskit_dev/lib/python3.8/site-packages/qiskit/synthesis/discrete_basis/solovay_kitaev.py:146, in SolovayKitaevDecomposition._recurse(self, sequence, n)\r\n    142 u_n1 = self._recurse(sequence, n - 1)\r\n    144 v_n, w_n = commutator_decompose(sequence.dot(u_n1.adjoint()).product)\r\n--> 146 v_n1 = self._recurse(v_n, n - 1)\r\n    147 w_n1 = self._recurse(w_n, n - 1)\r\n    148 return v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)\r\n\r\nFile ~/.pyenv/versions/circuit_comp_qiskit_dev/lib/python3.8/site-packages/qiskit/synthesis/discrete_basis/solovay_kitaev.py:144, in SolovayKitaevDecomposition._recurse(self, sequence, n)\r\n    140     return self.find_basic_approximation(sequence)\r\n    142 u_n1 = self._recurse(sequence, n - 1)\r\n--> 144 v_n, w_n = commutator_decompose(sequence.dot(u_n1.adjoint()).product)\r\n    146 v_n1 = self._recurse(v_n, n - 1)\r\n    147 w_n1 = self._recurse(w_n, n - 1)\r\n\r\nFile ~/.pyenv/versions/circuit_comp_qiskit_dev/lib/python3.8/site-packages/qiskit/synthesis/discrete_basis/commutator_decompose.py:224, in commutator_decompose(u_so3, check_input)\r\n    220     identity = np.identity(3)\r\n    221     if not (\r\n    222         np.allclose(u_so3.dot(u_so3.T), identity) and np.allclose(u_so3.T.dot(u_so3), identity)\r\n    223     ):\r\n--> 224         raise ValueError("Input matrix is not orthogonal.")\r\n    226 angle = _solve_decomposition_angle(u_so3)\r\n    228 # Compute rotation about x-axis with angle 'angle'\r\n\r\nValueError: Input matrix is not orthogonal.\r\n```\r\n\r\n### How can we reproduce the issue?\r\n\r\nSince I can't share the qiskit circuit, I will upload its QASM file which contains about 100 (out of few millions) gates  of the original circuit. This QASM file is enough to replicate the error.\r\n\r\nFirst, I will give the code:\r\n```\r\n# Load the QASM file\r\ntranspiled_circ_qasm = QuantumCircuit.from_qasm_file(filename)\r\n\r\n# Do SK pass\r\ndepth = 4\r\ngate_approx_library = generate_basic_approximations(\r\n    basis_gates=["h", "t", "tdg", "s", "z"], depth=depth\r\n)\r\n\r\nskd = SolovayKitaev(recursion_degree=2, basic_approximations=gate_approx_library)\r\ndiscretized = skd(transpiled_circ_qasm)\r\n\r\n``` \r\n\r\nHere is the partial QASM file generated from  a qiskit circuit:\r\n```\r\nOPENQASM 2.0;\r\ninclude "qelib1.inc";\r\nqreg eigenvalue_ancilla_register[1];\r\nqreg eigenvalue_register[4];\r\nqreg coin_register[3];\r\nqreg coin_ancilla_register[1];\r\nqreg power_register[8];\r\nqreg state_register[8];\r\ncreg classical_register[8];\r\nu3(pi/2,-pi,-pi) eigenvalue_ancilla_register[0];\r\nu3(pi/2,pi/2,-pi) eigenvalue_register[0];\r\nu3(pi/2,pi/2,-pi) eigenvalue_register[1];\r\nu3(pi/2,pi/2,-pi) eigenvalue_register[2];\r\nu3(pi/2,pi/2,-pi) eigenvalue_register[3];\r\nu3(pi/2,pi/2,-pi) coin_register[0];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu3(-pi/4,0,-pi/2) coin_register[0];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu3(pi/4,-pi/16,0) coin_register[0];\r\nu3(0,0,pi/16) eigenvalue_register[0];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu3(0,0,pi/16) coin_register[0];\r\nu3(0,pi/4,pi/4) eigenvalue_register[0];\r\nu3(pi/2,pi/2,-pi) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(-pi/4,0,-pi/2) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(pi/4,-pi/16,0) coin_register[1];\r\nu3(0,0,pi/16) eigenvalue_register[0];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(pi/2,0,-15*pi/16) coin_register[1];\r\ncx coin_register[0],coin_register[1];\r\nu1(-pi/4) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu1(pi/4) coin_register[1];\r\ncx coin_register[0],coin_register[1];\r\nu1(pi/4) coin_register[0];\r\nu1(-pi/4) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(pi/2,0,-3*pi/4) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu1(-pi/4) coin_register[0];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu3(0,0,-pi/16) eigenvalue_register[0];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(0,0,pi/16) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(pi/2,0,15*pi/16) coin_register[1];\r\ncx coin_register[0],coin_register[1];\r\nu1(-pi/4) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu1(pi/4) coin_register[1];\r\ncx coin_register[0],coin_register[1];\r\nu1(pi/4) coin_register[0];\r\nu1(-pi/4) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(pi/2,0,-3*pi/4) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu1(-pi/4) coin_register[0];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu3(0,pi/4,pi/4) eigenvalue_register[0];\r\nu3(pi/2,pi/2,-pi) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(-pi/4,0,-pi/2) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/4,-pi/16,0) coin_register[2];\r\nu3(0,0,pi/16) eigenvalue_register[0];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/2,0,-15*pi/16) coin_register[2];\r\ncx coin_register[1],coin_register[2];\r\nu1(-pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu1(pi/4) coin_register[2];\r\ncx coin_register[1],coin_register[2];\r\nu1(pi/4) coin_register[1];\r\nu1(-pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/2,0,-3*pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu1(-pi/4) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(0,0,-pi/16) eigenvalue_register[0];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(0,0,pi/16) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/2,0,15*pi/16) coin_register[2];\r\ncx coin_register[0],coin_register[2];\r\nu1(-pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu1(pi/4) coin_register[2];\r\ncx coin_register[0],coin_register[2];\r\nu1(pi/4) coin_register[0];\r\nu1(-pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/2,0,-3*pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu1(-pi/4) coin_register[0];\r\ncx eigenvalue_register[0],coin_register[0];\r\nu3(0,0,pi/16) eigenvalue_register[0];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(0,0,-pi/16) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/2,0,-15*pi/16) coin_register[2];\r\ncx coin_register[1],coin_register[2];\r\nu1(-pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu1(pi/4) coin_register[2];\r\ncx coin_register[1],coin_register[2];\r\nu1(pi/4) coin_register[1];\r\nu1(-pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/2,0,-3*pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu1(-pi/4) coin_register[1];\r\ncx eigenvalue_register[0],coin_register[1];\r\nu3(0,0,-pi/16) eigenvalue_register[0];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(0,0,pi/16) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/2,0,15*pi/16) coin_register[2];\r\ncx coin_register[0],coin_register[2];\r\nu1(-pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu1(pi/4) coin_register[2];\r\ncx coin_register[0],coin_register[2];\r\nu1(pi/4) coin_register[0];\r\nu1(-pi/4) coin_register[2];\r\ncx eigenvalue_register[0],coin_register[2];\r\nu3(pi/2,0,-3*pi/4) coin_register[2];\r\n```\r\n\r\n### What should happen?\r\n\r\nI would like to emphasize that even if I feed the circuit directly into SK (without loading it as QASM), the SK algorithm still produces the error. The original circuit was created exclusively with qiskit's default toolset and it runs on qiskit simulators without problems.\r\n\r\nThe expectation is that the following lines of code should produce a new quantum circuit which is comprised of $S, T, T^*, Z, CNOT$ and $H$ gates. Instead there is an error `ValueError: Input matrix is not orthogonal.`\r\n\r\n```\r\nskd = SolovayKitaev(recursion_degree=2, basic_approximations=gate_approx_library)\r\ndiscretized = skd(transpiled_circ_qasm)\r\n```\r\n\r\n### Any suggestions?\r\n\r\nI can only kindly ask to fix this issue ASAP.

diff --git a/qiskit/synthesis/discrete_basis/commutator_decompose.py b/qiskit/synthesis/discrete_basis/commutator_decompose.py\nindex c50a19dfa35e..10622e76e2c9 100644\n--- a/qiskit/synthesis/discrete_basis/commutator_decompose.py\n+++ b/qiskit/synthesis/discrete_basis/commutator_decompose.py\n@@ -16,6 +16,7 @@\n \n import math\n import numpy as np\n+from qiskit.quantum_info.operators.predicates import is_identity_matrix\n from .gate_sequence import _check_is_so3, GateSequence\n \n \n@@ -217,10 +218,7 @@ def commutator_decompose(\n         # assert that the input matrix is really SO(3)\n         _check_is_so3(u_so3)\n \n-        identity = np.identity(3)\n-        if not (\n-            np.allclose(u_so3.dot(u_so3.T), identity) and np.allclose(u_so3.T.dot(u_so3), identity)\n-        ):\n+        if not is_identity_matrix(u_so3.dot(u_so3.T)):\n             raise ValueError("Input matrix is not orthogonal.")\n \n     angle = _solve_decomposition_angle(u_so3)\ndiff --git a/qiskit/synthesis/discrete_basis/solovay_kitaev.py b/qiskit/synthesis/discrete_basis/solovay_kitaev.py\nindex cd470265f348..62ad50582d40 100644\n--- a/qiskit/synthesis/discrete_basis/solovay_kitaev.py\n+++ b/qiskit/synthesis/discrete_basis/solovay_kitaev.py\n@@ -86,14 +86,20 @@ def load_basic_approximations(self, data: list | str | dict) -> list[GateSequenc\n         return sequences\n \n     def run(\n-        self, gate_matrix: np.ndarray, recursion_degree: int, return_dag: bool = False\n+        self,\n+        gate_matrix: np.ndarray,\n+        recursion_degree: int,\n+        return_dag: bool = False,\n+        check_input: bool = True,\n     ) -> "QuantumCircuit" | "DAGCircuit":\n         r"""Run the algorithm.\n \n         Args:\n-            gate_matrix: The 2x2 matrix representing the gate. Does not need to be SU(2).\n+            gate_matrix: The 2x2 matrix representing the gate. This matrix has to be SU(2)\n+                up to global phase.\n             recursion_degree: The recursion degree, called :math:`n` in the paper.\n             return_dag: If ``True`` return a :class:`.DAGCircuit`, else a :class:`.QuantumCircuit`.\n+            check_input: If ``True`` check that the input matrix is valid for the decomposition.\n \n         Returns:\n             A one-qubit circuit approximating the ``gate_matrix`` in the specified discrete basis.\n@@ -104,7 +110,7 @@ def run(\n         global_phase = np.arctan2(np.imag(z), np.real(z))\n \n         # get the decompositon as GateSequence type\n-        decomposition = self._recurse(gate_matrix_su2, recursion_degree)\n+        decomposition = self._recurse(gate_matrix_su2, recursion_degree, check_input=check_input)\n \n         # simplify\n         _remove_identities(decomposition)\n@@ -120,18 +126,20 @@ def run(\n \n         return out\n \n-    def _recurse(self, sequence: GateSequence, n: int) -> GateSequence:\n+    def _recurse(self, sequence: GateSequence, n: int, check_input: bool = True) -> GateSequence:\n         """Performs ``n`` iterations of the Solovay-Kitaev algorithm on ``sequence``.\n \n         Args:\n-            sequence: GateSequence to which the Solovay-Kitaev algorithm is applied.\n-            n: number of iterations that the algorithm needs to run.\n+            sequence: ``GateSequence`` to which the Solovay-Kitaev algorithm is applied.\n+            n: The number of iterations that the algorithm needs to run.\n+            check_input: If ``True`` check that the input matrix represented by ``GateSequence``\n+                is valid for the decomposition.\n \n         Returns:\n             GateSequence that approximates ``sequence``.\n \n         Raises:\n-            ValueError: if ``u`` does not represent an SO(3)-matrix.\n+            ValueError: If the matrix in ``GateSequence`` does not represent an SO(3)-matrix.\n         """\n         if sequence.product.shape != (3, 3):\n             raise ValueError("Shape of U must be (3, 3) but is", sequence.shape)\n@@ -139,12 +147,14 @@ def _recurse(self, sequence: GateSequence, n: int) -> GateSequence:\n         if n == 0:\n             return self.find_basic_approximation(sequence)\n \n-        u_n1 = self._recurse(sequence, n - 1)\n+        u_n1 = self._recurse(sequence, n - 1, check_input=check_input)\n \n-        v_n, w_n = commutator_decompose(sequence.dot(u_n1.adjoint()).product)\n+        v_n, w_n = commutator_decompose(\n+            sequence.dot(u_n1.adjoint()).product, check_input=check_input\n+        )\n \n-        v_n1 = self._recurse(v_n, n - 1)\n-        w_n1 = self._recurse(w_n, n - 1)\n+        v_n1 = self._recurse(v_n, n - 1, check_input=check_input)\n+        w_n1 = self._recurse(w_n, n - 1, check_input=check_input)\n         return v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)\n \n     def find_basic_approximation(self, sequence: GateSequence) -> Gate:\ndiff --git a/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py b/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py\nindex feb40ad53f04..43ae079b9fc2 100644\n--- a/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py\n+++ b/qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py\n@@ -18,6 +18,7 @@\n import numpy as np\n \n from qiskit.converters import circuit_to_dag\n+from qiskit.circuit.gate import Gate\n from qiskit.dagcircuit import DAGCircuit\n from qiskit.synthesis.discrete_basis.solovay_kitaev import SolovayKitaevDecomposition\n from qiskit.synthesis.discrete_basis.generate_basis_approximations import (\n@@ -146,16 +147,21 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:\n             if not node.op.num_qubits == 1:\n                 continue  # ignore all non-single qubit gates\n \n+            # we do not check the input matrix as we know it comes from a Qiskit gate, as this\n+            # we know it will generate a valid SU(2) matrix\n+            check_input = not isinstance(node.op, Gate)\n+\n             if not hasattr(node.op, "to_matrix"):\n                 raise TranspilerError(\n-                    "SolovayKitaev does not support gate without "\n-                    f"to_matrix method: {node.op.name}"\n+                    f"SolovayKitaev does not support gate without to_matrix method: {node.op.name}"\n                 )\n \n             matrix = node.op.to_matrix()\n \n             # call solovay kitaev\n-            approximation = self._sk.run(matrix, self.recursion_degree, return_dag=True)\n+            approximation = self._sk.run(\n+                matrix, self.recursion_degree, return_dag=True, check_input=check_input\n+            )\n \n             # convert to a dag and replace the gate by the approximation\n             dag.substitute_node_with_dag(node, approximation)\n

diff --git a/test/python/transpiler/test_solovay_kitaev.py b/test/python/transpiler/test_solovay_kitaev.py\nindex 95dfd06763d0..d3d85b8cfb76 100644\n--- a/test/python/transpiler/test_solovay_kitaev.py\n+++ b/test/python/transpiler/test_solovay_kitaev.py\n@@ -202,6 +202,35 @@ def test_approximation_on_qft(self):\n             discretized = skd(transpiled)\n             self.assertLess(_trace_distance(transpiled, discretized), 7)\n \n+    def test_u_gates_work(self):\n+        """Test SK works on Qiskit's UGate.\n+\n+        Regression test of Qiskit/qiskit-terra#9437.\n+        """\n+        circuit = QuantumCircuit(1)\n+        circuit.u(np.pi / 2, -np.pi, -np.pi, 0)\n+        circuit.u(np.pi / 2, np.pi / 2, -np.pi, 0)\n+        circuit.u(-np.pi / 4, 0, -np.pi / 2, 0)\n+        circuit.u(np.pi / 4, -np.pi / 16, 0, 0)\n+        circuit.u(0, 0, np.pi / 16, 0)\n+        circuit.u(0, np.pi / 4, np.pi / 4, 0)\n+        circuit.u(np.pi / 2, 0, -15 * np.pi / 16, 0)\n+        circuit.p(-np.pi / 4, 0)\n+        circuit.p(np.pi / 4, 0)\n+        circuit.u(np.pi / 2, 0, -3 * np.pi / 4, 0)\n+        circuit.u(0, 0, -np.pi / 16, 0)\n+        circuit.u(np.pi / 2, 0, 15 * np.pi / 16, 0)\n+\n+        depth = 4\n+        basis_gates = ["h", "t", "tdg", "s", "z"]\n+        gate_approx_library = generate_basic_approximations(basis_gates=basis_gates, depth=depth)\n+\n+        skd = SolovayKitaev(recursion_degree=2, basic_approximations=gate_approx_library)\n+        discretized = skd(circuit)\n+\n+        included_gates = set(discretized.count_ops().keys())\n+        self.assertEqual(set(basis_gates), included_gates)\n+\n \n @ddt\n class TestGateSequence(QiskitTestCase):\n


Fix wrong relative phase of MCRZ (Qiskit/qiskit#9836)

<!--\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- [ ] 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\nThis PR is on top of PR #9688 and fixes #9202.\r\n\r\n\r\n

QuantumCircuit.mcrz not correct

### Environment\r\n\r\n- **Qiskit Terra version**:  0.22.2\r\n- **Python version**: 3.9.7\r\n- **Operating system**: Darwin\r\n\r\n\r\n### What is happening?\r\n\r\nmulticontrol Rz rotation not correctly being built - see example below. \r\n\r\n(Also multicontrol Rz, Ry, Rx gates can all be implemented using fewer nonlocal (two) qubit gates... See solution below)\r\n\r\n### How can we reproduce the issue?\r\n\r\n```\r\nfrom qiskit.quantum_info import Operator\r\nfrom qiskit import QuantumCircuit, transpile\r\nimport numpy as np\r\nfrom qiskit.circuit.library import MCMT\r\n\r\n\r\n###### two qubit case ####\r\nangle = np.pi/3\r\n### correct\r\ncrz_circuit = QuantumCircuit(2)\r\ncrz_circuit.crz(angle, 0,1)\r\ncrz_circuit_unitary = Operator(crz_circuit).data\r\n\r\n### incorrect\r\nmulti_crz = QuantumCircuit(2)\r\nmulti_crz.mcrz(angle, [0],1)\r\nmulti_crz_unitary = Operator(multi_crz).data\r\n\r\n## FALSE\r\nprint('single control correct:', np.allclose(crz_circuit_unitary,\r\n                             multi_crz_unitary))\r\n\r\n###### multi control case ####\r\nctrl_list = [0,2,5]\r\ntarget= 4\r\n\r\n# ## Correct\r\ngate = QuantumCircuit(1)\r\ngate.rz(angle, 0)\r\n  \r\nmc_gate = MCMT(gate,\r\n              num_ctrl_qubits=len(ctrl_list),\r\n              num_target_qubits=1).to_gate()\r\n\r\n\r\nmc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\nmc_circuit.append(mc_gate, [*ctrl_list, target])\r\n\r\n\r\n# ## Incorrect\r\nmcrz_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\nmcrz_multi.mcrz(angle,ctrl_list,target)\r\n\r\n\r\nprint('multicontrol correct:  ', np.allclose(Operator(mc_circuit).data,\r\n                                   Operator(mcrz_multi).data))\r\n```\r\n\r\n### What should happen?\r\n\r\nmcrz method in `QuantumCircuit ` should implement the correct quantum  circuit. Solution provided below.\r\n\r\n### Any suggestions?\r\n\r\nThis code fixes the problem and acutally reduces the depth of all multicontrol rotation Rz,Ry,Rz gates:\r\n\r\n```\r\nfrom qiskit import QuantumCircuit\r\n\r\ndef primitive_block(angle, n_ctrl, axis):\r\n    """\r\n    get primative gates to perform multicontrol rotation\r\n    """\r\n    primitive=QuantumCircuit(2)\r\n    if axis=='x':\r\n        primitive.rx(angle/(2**n_ctrl), 1); primitive.cz(0,1)\r\n    elif axis=='y':\r\n        primitive.ry(angle/(2**n_ctrl), 1); primitive.cx(0,1)\r\n    elif axis=='z':\r\n        primitive.rz(angle/(2**n_ctrl), 1); primitive.cx(0,1)\r\n    else:\r\n        raise ValueError('Unrecognised axis, must be one of x,y or z.')\r\n    \r\n    return primitive\r\n\r\ndef get_pattern(n_ctrl, pattern=[0]):\r\n    """\r\n    find control sequence to implement an n_control gate \r\n    """\r\n    if n_ctrl == pattern[-1]+1:\r\n        return pattern\r\n    else:\r\n        new_pattern = pattern*2\r\n        new_pattern[-1]+=1\r\n        return get_pattern(n_ctrl, new_pattern)\r\n    \r\ndef custom_mcrtl_rot(angle, ctrl_list, target, axis='y'):\r\n    """\r\n    return circuit that performs an n-control: Rz, Ry, Rx rotation\r\n    \r\n    """\r\n    assert axis in ['x','y','z'], f'can only rotated around x,y,z axis, not {axis}'\r\n    \r\n    ctrl_list=sorted(ctrl_list)\r\n    n_ctrl=len(ctrl_list)\r\n    circ=QuantumCircuit(max([target, max(ctrl_list)])+1)    \r\n    pattern = get_pattern(n_ctrl)\r\n    \r\n    if n_ctrl >1: \r\n        for s,i in enumerate(pattern):\r\n            j = ctrl_list[-i-1]\r\n            p = primitive_block((-1)**s*angle, n_ctrl, axis)\r\n            circ = circ.compose(p, [j,target])\r\n\r\n        circ = circ.compose(circ)\r\n    else:\r\n        p_plus = primitive_block(+angle, n_ctrl, axis)\r\n        p_minus = primitive_block(-angle, n_ctrl, axis)\r\n        circ = p_plus.compose(p_minus)\r\n    \r\n    return circ\r\n\r\n```\r\n\r\nWe can check this is working via:\r\n\r\n\r\n```\r\nfrom qiskit.quantum_info import Operator\r\nfrom qiskit import QuantumCircuit, transpile\r\nimport numpy as np\r\nfrom qiskit.circuit.library import MCMT\r\n\r\n### CAN CHANGE HERE\r\nctrl_list = [0,2,5]\r\ntarget= 4\r\nangle = np.pi/3\r\n####\r\n\r\n\r\naxis = 'z'\r\nnew_circuit = custom_mcrtl_rot(angle,ctrl_list,target, axis=axis)\r\n\r\ngate = QuantumCircuit(1)\r\ngate.rz(angle, 0)\r\nmc_gate = MCMT(gate,\r\n              num_ctrl_qubits=len(ctrl_list),\r\n              num_target_qubits=1).to_gate()\r\n\r\n\r\nmc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\nmc_circuit.append(mc_gate, [*ctrl_list, target])\r\n\r\nprint('does new method match MCMT:', np.allclose(Operator(new_circuit).data,\r\n            Operator(mc_circuit).data))\r\n\r\n\r\n###  compare to mcrz\r\nmcrz_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\nmcrz_multi.mcrz(angle,ctrl_list,target)\r\n\r\nprint('does mcrz match MCMT:      ', np.allclose(Operator(mcrz_multi).data,\r\n            Operator(new_circuit).data))\r\n\r\nprint('does mcrz match new:       ', np.allclose(Operator(mcrz_multi).data,\r\n            Operator(mc_circuit).data))\r\n\r\n### compare number of nonlocal gates in each approach depth\r\nnew_trans = transpile(new_circuit, basis_gates=['cx', 'rz','ry','rx'])\r\nmctc_trans = transpile(mc_circuit, basis_gates=['cx', 'rz','ry','rx'])\r\nmcrz_multi_trans = transpile(mcrz_multi, basis_gates=['cx', 'rz','ry','rx'])\r\n\r\nprint('\\ndepth comparison')\r\nprint('num nonlocal gates new :', new_trans.num_nonlocal_gates())\r\nprint('num nonlocal gates mctc:', mctc_trans.num_nonlocal_gates())\r\nprint('num nonlocal gates mcrz:', mcrz_multi_trans.num_nonlocal_gates())\r\n\r\n### allow old implementations to be optmized (NEW method not)\r\nopt_mctc_trans = transpile(mc_circuit, optimization_level=3)\r\nopt_mcrz_multi_trans = transpile(mcrz_multi, optimization_level=3)\r\n\r\nprint('\\ndepth comparison (allowing old methods to be optimized)')\r\nprint('num nonlocal gates new :', new_trans.num_nonlocal_gates(), '(no optimization)')\r\nprint('num nonlocal gates mctc:', opt_mctc_trans.num_nonlocal_gates())\r\nprint('num nonlocal gates mcrz:', opt_mcrz_multi_trans.num_nonlocal_gates())\r\n\r\n\r\n### prints:\r\n\r\n# does new method match MCMT: True\r\n# does mcrz match MCMT:       False\r\n# does mcrz match new:        False\r\n\r\n# depth comparison\r\n# num nonlocal gates new : 8\r\n# num nonlocal gates mctc: 28\r\n# num nonlocal gates mcrz: 20\r\n\r\n# depth comparison (allowing old methods to be optimized)\r\n# num nonlocal gates new : 8 (no optimization)\r\n# num nonlocal gates mctc: 25\r\n# num nonlocal gates mcrz: 13\r\n\r\n\r\n```\r\n\r\nThe new approach gives an improvement to the correctly implemented mutlicontrol rotation gates currently coded in qiskit:\r\n\r\n```\r\n### CAN CHANGE HERE\r\nctrl_list = [0,2,5]\r\ntarget= 4\r\nangle = np.pi/3\r\n####\r\n\r\n\r\nfor ax in ['x','y']:\r\n    \r\n    new_circuit = custom_mcrtl_rot(angle,ctrl_list,target, axis=ax)\r\n\r\n    if ax =='x':\r\n        gate = QuantumCircuit(1)\r\n        gate.rx(angle, 0) #< --- rx\r\n    elif ax == 'y':\r\n        gate = QuantumCircuit(1)\r\n        gate.ry(angle, 0) #< --- ry\r\n        \r\n    mc_gate = MCMT(gate,\r\n                  num_ctrl_qubits=len(ctrl_list),\r\n                  num_target_qubits=1).to_gate()\r\n\r\n    mc_circuit = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\n    mc_circuit.append(mc_gate, [*ctrl_list, target])\r\n\r\n    print('does new method match MCMT:', np.allclose(Operator(new_circuit).data,\r\n                                         Operator(mc_circuit).data))\r\n    print()\r\n\r\n\r\n    ###  compare to mcr\r\n    mcr_multi = QuantumCircuit(max([target, max(ctrl_list)])+1)\r\n    if ax =='x':\r\n        mcr_multi.mcrx(angle,ctrl_list,target)\r\n    elif ax == 'y':\r\n        mcr_multi.mcry(angle,ctrl_list,target)\r\n    \r\n\r\n    print(f'does mcr{ax} match MCMT:      ', np.allclose(Operator(mcr_multi).data,\r\n                                             Operator(new_circuit).data))\r\n\r\n    print(f'does mcr{ax} match new:       ', np.allclose(Operator(mcr_multi).data,\r\n                                            Operator(mc_circuit).data))\r\n\r\n    ### compare number of nonlocal gates in each approach depth\r\n    new_trans = transpile(new_circuit, basis_gates=['cx', 'rz','ry','rx'])\r\n    mctc_trans = transpile(mc_circuit, basis_gates=['cx', 'rz','ry','rx'])\r\n    mcr_multi_trans = transpile(mcr_multi, basis_gates=['cx', 'rz','ry','rx'])\r\n\r\n    print(f'\\ndepth comparison for multi control R{ax} gate')\r\n    print('num nonlocal gates new :', new_trans.num_nonlocal_gates())\r\n    print('num nonlocal gates mctc:', mctc_trans.num_nonlocal_gates())\r\n    print(f'num nonlocal gates mcr{ax}:', mcr_multi_trans.num_nonlocal_gates())\r\n\r\n    ### allow old implementations to be optmized (NEW method not)\r\n    opt_mctc_trans = transpile(mc_circuit, optimization_level=3)\r\n    opt_mcrX_multi_trans = transpile(mcr_multi, optimization_level=3)\r\n    \r\n    print(f'\\ndepth comparison for multi control R{ax} gate (allowing old methods to be optimized)')\r\n    print('num nonlocal gates new :', new_trans.num_nonlocal_gates(), '(no optimization)')\r\n    print('num nonlocal gates mctc:', opt_mctc_trans.num_nonlocal_gates())\r\n    print(f'num nonlocal gates mcr{ax}:', opt_mcrX_multi_trans.num_nonlocal_gates())\r\n\r\n    \r\n    print('*'*12)\r\n    \r\n# does new method match MCMT: True\r\n\r\n# does mcrx match MCMT:       True\r\n# does mcrx match new:        True\r\n\r\n# depth comparison for multi control Rx gate\r\n# num nonlocal gates new : 8\r\n# num nonlocal gates mctc: 20\r\n# num nonlocal gates mcrx: 20\r\n\r\n# depth comparison for multi control Rx gate (allowing old methods to be optimized)\r\n# num nonlocal gates new : 8 (no optimization)\r\n# num nonlocal gates mctc: 20\r\n# num nonlocal gates mcrx: 13\r\n# ************\r\n# does new method match MCMT: True\r\n\r\n# does mcry match MCMT:       True\r\n# does mcry match new:        True\r\n\r\n# depth comparison for multi control Ry gate\r\n# num nonlocal gates new : 8\r\n# num nonlocal gates mctc: 20\r\n# num nonlocal gates mcry: 20\r\n\r\n# depth comparison for multi control Ry gate (allowing old methods to be optimized)\r\n# num nonlocal gates new : 8 (no optimization)\r\n# num nonlocal gates mctc: 20\r\n# num nonlocal gates mcry: 13\r\n# ************\r\n\r\n```\r\n\r\n\r\n

diff --git a/qiskit/circuit/add_control.py b/qiskit/circuit/add_control.py\nindex 39a91eba36ef..9a9837673d1a 100644\n--- a/qiskit/circuit/add_control.py\n+++ b/qiskit/circuit/add_control.py\n@@ -148,6 +148,7 @@ def control(\n                     q_target[bit_indices[qargs[0]]],\n                     use_basis_gates=True,\n                 )\n+                continue\n             elif gate.name == "p":\n                 from qiskit.circuit.library import MCPhaseGate\n \n@@ -184,13 +185,9 @@ def control(\n                             use_basis_gates=True,\n                         )\n                     elif theta == 0 and phi == 0:\n-                        controlled_circ.mcrz(\n-                            lamb, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True\n-                        )\n+                        controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]])\n                     else:\n-                        controlled_circ.mcrz(\n-                            lamb, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True\n-                        )\n+                        controlled_circ.mcp(lamb, q_control, q_target[bit_indices[qargs[0]]])\n                         controlled_circ.mcry(\n                             theta,\n                             q_control,\n@@ -198,9 +195,7 @@ def control(\n                             q_ancillae,\n                             use_basis_gates=True,\n                         )\n-                        controlled_circ.mcrz(\n-                            phi, q_control, q_target[bit_indices[qargs[0]]], use_basis_gates=True\n-                        )\n+                        controlled_circ.mcp(phi, q_control, q_target[bit_indices[qargs[0]]])\n             elif gate.name == "z":\n                 controlled_circ.h(q_target[bit_indices[qargs[0]]])\n                 controlled_circ.mcx(q_control, q_target[bit_indices[qargs[0]]], q_ancillae)\ndiff --git a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py\nindex 547883a6f7a9..ce7f7861440b 100644\n--- a/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py\n+++ b/qiskit/circuit/library/standard_gates/multi_control_rotation_gates.py\n@@ -84,36 +84,44 @@ def _apply_mcu_graycode(circuit, theta, phi, lam, ctls, tgt, use_basis_gates):\n \n \n def _mcsu2_real_diagonal(\n-    circuit,\n     unitary: np.ndarray,\n-    controls: Union[QuantumRegister, List[Qubit]],\n-    target: Union[Qubit, int],\n-    ctrl_state: str = None,\n-):\n+    num_controls: int,\n+    ctrl_state: Optional[str] = None,\n+    use_basis_gates: bool = False,\n+) -> QuantumCircuit:\n     """\n-    Apply multi-controlled SU(2) gate with a real main diagonal or secondary diagonal.\n-    https://arxiv.org/abs/2302.06377\n+    Return a multi-controlled SU(2) gate [1]_ with a real main diagonal or secondary diagonal.\n \n     Args:\n-        circuit (QuantumCircuit): The QuantumCircuit object to apply the diagonal operator on.\n-        unitary (ndarray): SU(2) unitary matrix with one real diagonal\n-        controls (QuantumRegister or list(Qubit)): The list of control qubits\n-        target (Qubit or int): The target qubit\n-        ctrl_state (str): control state of the operator SU(2) operator\n+        unitary: SU(2) unitary matrix with one real diagonal.\n+        num_controls: The number of control qubits.\n+        ctrl_state: The state on which the SU(2) operation is controlled. Defaults to all\n+            control qubits being in state 1.\n+        use_basis_gates: If ``True``, use ``[p, u, cx]`` gates to implement the decomposition.\n+\n+    Returns:\n+        A :class:`.QuantumCircuit` implementing the multi-controlled SU(2) gate.\n \n     Raises:\n-        QiskitError: parameter errors\n+        QiskitError: If the input matrix is invalid.\n+\n+    References:\n+\n+        .. [1]: R. Vale et al. Decomposition of Multi-controlled Special Unitary Single-Qubit Gates\n+            `arXiv:2302.06377 (2023) <https://arxiv.org/abs/2302.06377>`__\n+\n     """\n     # pylint: disable=cyclic-import\n-    from qiskit.circuit.library import MCXVChain\n+    from .x import MCXVChain\n     from qiskit.extensions import UnitaryGate\n     from qiskit.quantum_info.operators.predicates import is_unitary_matrix\n-\n-    if not is_unitary_matrix(unitary):\n-        raise QiskitError("parameter unitary in mcsu2_real_diagonal must be an unitary matrix")\n+    from qiskit.compiler import transpile\n \n     if unitary.shape != (2, 2):\n-        raise QiskitError("parameter unitary in mcsu2_real_diagonal must be a 2x2 matrix")\n+        raise QiskitError(f"The unitary must be a 2x2 matrix, but has shape {unitary.shape}.")\n+\n+    if not is_unitary_matrix(unitary):\n+        raise QiskitError(f"The unitary in must be an unitary matrix, but is {unitary}.")\n \n     is_main_diag_real = np.isclose(unitary[0, 0].imag, 0.0) and np.isclose(unitary[1, 1].imag, 0.0)\n     is_secondary_diag_real = np.isclose(unitary[0, 1].imag, 0.0) and np.isclose(\n@@ -121,7 +129,7 @@ def _mcsu2_real_diagonal(\n     )\n \n     if not is_main_diag_real and not is_secondary_diag_real:\n-        raise QiskitError("parameter unitary in mcsu2_real_diagonal must have one real diagonal")\n+        raise QiskitError("The unitary must have one real diagonal.")\n \n     if is_secondary_diag_real:\n         x = unitary[0, 1]\n@@ -130,27 +138,34 @@ def _mcsu2_real_diagonal(\n         x = -unitary[0, 1].real\n         z = unitary[1, 1] - unitary[0, 1].imag * 1.0j\n \n-    alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0)\n-    alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0)))\n-    alpha = alpha_r + 1.0j * alpha_i\n-    beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0)))\n+    if np.isclose(z, -1):\n+        s_op = [[1.0, 0.0], [0.0, 1.0j]]\n+    else:\n+        alpha_r = np.sqrt((np.sqrt((z.real + 1.0) / 2.0) + 1.0) / 2.0)\n+        alpha_i = z.imag / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0)))\n+        alpha = alpha_r + 1.0j * alpha_i\n+        beta = x / (2.0 * np.sqrt((z.real + 1.0) * (np.sqrt((z.real + 1.0) / 2.0) + 1.0)))\n+\n+        # S gate definition\n+        s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]])\n \n-    # S gate definition\n-    s_op = np.array([[alpha, -np.conj(beta)], [beta, np.conj(alpha)]])\n     s_gate = UnitaryGate(s_op)\n \n-    num_ctrl = len(controls)\n-    k_1 = int(np.ceil(num_ctrl / 2.0))\n-    k_2 = int(np.floor(num_ctrl / 2.0))\n+    k_1 = int(np.ceil(num_controls / 2.0))\n+    k_2 = int(np.floor(num_controls / 2.0))\n \n     ctrl_state_k_1 = None\n     ctrl_state_k_2 = None\n \n     if ctrl_state is not None:\n-        str_ctrl_state = f"{ctrl_state:0{num_ctrl}b}"\n+        str_ctrl_state = f"{ctrl_state:0{num_controls}b}"\n         ctrl_state_k_1 = str_ctrl_state[::-1][:k_1][::-1]\n         ctrl_state_k_2 = str_ctrl_state[::-1][k_1:][::-1]\n \n+    circuit = QuantumCircuit(num_controls + 1, name="MCSU2")\n+    controls = list(range(num_controls))  # control indices, defined for code legibility\n+    target = num_controls  # target index, defined for code legibility\n+\n     if not is_secondary_diag_real:\n         circuit.h(target)\n \n@@ -178,6 +193,11 @@ def _mcsu2_real_diagonal(\n     if not is_secondary_diag_real:\n         circuit.h(target)\n \n+    if use_basis_gates:\n+        circuit = transpile(circuit, basis_gates=["p", "u", "cx"])\n+\n+    return circuit\n+\n \n def mcrx(\n     self,\n@@ -232,7 +252,12 @@ def mcrx(\n             use_basis_gates=use_basis_gates,\n         )\n     else:\n-        _mcsu2_real_diagonal(self, RXGate(theta).to_matrix(), control_qubits, target_qubit)\n+        cgate = _mcsu2_real_diagonal(\n+            RXGate(theta).to_matrix(),\n+            num_controls=len(control_qubits),\n+            use_basis_gates=use_basis_gates,\n+        )\n+        self.compose(cgate, control_qubits + [target_qubit], inplace=True)\n \n \n def mcry(\n@@ -302,7 +327,12 @@ def mcry(\n                 use_basis_gates=use_basis_gates,\n             )\n         else:\n-            _mcsu2_real_diagonal(self, RYGate(theta).to_matrix(), control_qubits, target_qubit)\n+            cgate = _mcsu2_real_diagonal(\n+                RYGate(theta).to_matrix(),\n+                num_controls=len(control_qubits),\n+                use_basis_gates=use_basis_gates,\n+            )\n+            self.compose(cgate, control_qubits + [target_qubit], inplace=True)\n     else:\n         raise QiskitError(f"Unrecognized mode for building MCRY circuit: {mode}.")\n \n@@ -327,6 +357,8 @@ def mcrz(\n     Raises:\n         QiskitError: parameter errors\n     """\n+    from .rz import CRZGate, RZGate\n+\n     control_qubits = self.qbit_argument_conversion(q_controls)\n     target_qubit = self.qbit_argument_conversion(q_target)\n     if len(target_qubit) != 1:\n@@ -336,13 +368,21 @@ def mcrz(\n     self._check_dups(all_qubits)\n \n     n_c = len(control_qubits)\n-    if n_c == 1:  # cu\n-        _apply_cu(self, 0, 0, lam, control_qubits[0], target_qubit, use_basis_gates=use_basis_gates)\n+    if n_c == 1:\n+        if use_basis_gates:\n+            self.u(0, 0, lam / 2, target_qubit)\n+            self.cx(control_qubits[0], target_qubit)\n+            self.u(0, 0, -lam / 2, target_qubit)\n+            self.cx(control_qubits[0], target_qubit)\n+        else:\n+            self.append(CRZGate(lam), control_qubits + [target_qubit])\n     else:\n-        lam_step = lam * (1 / (2 ** (n_c - 1)))\n-        _apply_mcu_graycode(\n-            self, 0, 0, lam_step, control_qubits, target_qubit, use_basis_gates=use_basis_gates\n+        cgate = _mcsu2_real_diagonal(\n+            RZGate(lam).to_matrix(),\n+            num_controls=len(control_qubits),\n+            use_basis_gates=use_basis_gates,\n         )\n+        self.compose(cgate, control_qubits + [target_qubit], inplace=True)\n \n \n QuantumCircuit.mcrx = mcrx\ndiff --git a/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml b/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml\nnew file mode 100644\nindex 000000000000..aa89abeb662d\n--- /dev/null\n+++ b/releasenotes/notes/fix-mcrz-relative-phase-6ea81a369f8bda38.yaml\n@@ -0,0 +1,7 @@\n+---\n+fixes:\n+  - |\n+    Fixed the gate decomposition of multi-controlled Z rotation gates added via\n+    :meth:`.QuantumCircuit.mcrz`. Previously, this method implemented a multi-controlled\n+    phase gate, which has a relative phase difference  to the Z rotation. To obtain the\n+    previous `.QuantumCircuit.mcrz` behaviour, use `.QuantumCircuit.mcp`.\n

diff --git a/test/python/circuit/test_controlled_gate.py b/test/python/circuit/test_controlled_gate.py\nindex a40f7747e2e7..248aabab7839 100644\n--- a/test/python/circuit/test_controlled_gate.py\n+++ b/test/python/circuit/test_controlled_gate.py\n@@ -610,9 +610,8 @@ def test_mcsu2_real_diagonal(self):\n         """Test mcsu2_real_diagonal"""\n         num_ctrls = 6\n         theta = 0.3\n-        qc = QuantumCircuit(num_ctrls + 1)\n         ry_matrix = RYGate(theta).to_matrix()\n-        _mcsu2_real_diagonal(qc, ry_matrix, list(range(num_ctrls)), num_ctrls)\n+        qc = _mcsu2_real_diagonal(ry_matrix, num_ctrls)\n \n         mcry_matrix = _compute_control_matrix(ry_matrix, 6)\n         self.assertTrue(np.allclose(mcry_matrix, Operator(qc).to_matrix()))\n@@ -657,6 +656,11 @@ def test_multi_controlled_rotation_gate_matrices(\n                 if bit == "0":\n                     qc.x(q_controls[idx])\n \n+            if use_basis_gates:\n+                with self.subTest(msg="check only basis gates used"):\n+                    gates_used = set(qc.count_ops().keys())\n+                    self.assertTrue(gates_used.issubset({"x", "u", "p", "cx"}))\n+\n             backend = BasicAer.get_backend("unitary_simulator")\n             simulated = execute(qc, backend).result().get_unitary(qc)\n \n@@ -665,7 +669,7 @@ def test_multi_controlled_rotation_gate_matrices(\n             elif base_gate_name == "y":\n                 rot_mat = RYGate(theta).to_matrix()\n             else:  # case 'z'\n-                rot_mat = U1Gate(theta).to_matrix()\n+                rot_mat = RZGate(theta).to_matrix()\n \n             expected = _compute_control_matrix(rot_mat, num_controls, ctrl_state=ctrl_state)\n             with self.subTest(msg=f"control state = {ctrl_state}"):\ndiff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py\nindex ef01d3873e39..2a4b112e1508 100755\n--- a/test/qpy_compat/test_qpy.py\n+++ b/test/qpy_compat/test_qpy.py\n@@ -603,15 +603,14 @@ def generate_circuits(version_parts):\n     if version_parts >= (0, 19, 2):\n         output_circuits["control_flow.qpy"] = generate_control_flow_circuits()\n     if version_parts >= (0, 21, 0):\n-        output_circuits["controlled_gates.qpy"] = generate_controlled_gates()\n         output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks()\n         output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits()\n-    if version_parts >= (0, 21, 2):\n-        output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates()\n     if version_parts >= (0, 24, 0):\n         output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule()\n         output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits()\n-\n+    if version_parts >= (0, 25, 0):\n+        output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates()\n+        output_circuits["controlled_gates.qpy"] = generate_controlled_gates()\n     return output_circuits\n \n \n


Fixes #3755 (rdkit/rdkit#3758)

#3755 is caused by two separate problems:

1) There is an inconsistency in the exhaustiveness of the tautomer enumeration between the chiral and the non-chiral versions of serine. This is caused by a bug in the logic that checks when the enumeration should be considered as complete.
As a consequence, the enumeration for the chiral molecule is quit too early, and the hydrate tautomer is not generated. Therefore, the expected carboxylic acid tautomer is generated, and the result appears to be correct (because the competing hydrate tautomer is not generated).
Instead, for the achiral molecule the enumeration is indeed complete, the hydrate tautomer scores the same as the carboxylic acid tautomer, but the hydrate tautomer is preferred as its SMILES comes first in lexicographic order.

This PR fixes the bug in the logic, so the enumeration is now complete also for the chiral molecule case.

* I also fixed the debug build with `VERBOSE_ENUMERATION` defined (which was failing to build)
* I moved the comments around to make them more useful during debugging
* I removed an unnecessary copying of the molecule
* I removed the `done` stack altogether

The two latter points should result in improved performance.

2) The hydrate tautomer scores the same as the carboxylic acid tautomer. This already happened in the original Python implementation of MolVS:

```
from rdkit import Chem
from rdkit.Chem.Draw import MolsToGridImage
from rdkit.Chem.MolStandardize import tautomer

orig_vs_expected = [
    ("OC(=O)C(N)CO", "NC(CO)C(=O)O"),
    ("C([C@@H](C(=O)O)N)O", "NC(CO)C(=O)O"),
    ("OC(=O)C(N)CN", "NCC(N)C(=O)O"),
    ("NC(=O)C(N)CO", "NC(=O)C(N)CO"),
    ("NC(=N)C(N)CO", "N=C(N)C(N)CO"),
    ("NC(=N)NC(N)CO", "N=C(N)NC(N)CO")
]

enumerator = tautomer.TautomerCanonicalizer()
orig_vs_canonical_mols = []
for orig, expected in orig_vs_expected:
    orig_mol = Chem.MolFromSmiles(orig)
    orig_vs_canonical_mols.append(orig_mol)
    canonical = enumerator.canonicalize(orig_mol)
    orig_vs_canonical_mols.append(canonical)

MolsToGridImage(orig_vs_canonical_mols, molsPerRow=2, subImgSize=(200,100))
```
![image](https://user-images.githubusercontent.com/5244385/105371916-60968080-5c05-11eb-960f-06faa6715580.png)

As you may see, there are a number of molecules which are converted to hydrates or aminals.
This PR fixes this issue by adding an additional substructure rule:
```
{"C(=hetero)-hetero", "[C](=[!#1;!#6])[!#1;!#6]", 2},
```
which gives a higher score to carboxylic acids/amides/amidines vs their reduced counterparts.

Inconsistent canonical tautomer on repeated application

**Describe the bug**
Tautomer canonicalization seems to be occasionally inconsistent, repeated canonicalizations will produce different results.

**To Reproduce**

Repeatedly pick the canonical tautomer for Serine.

```python
from rdkit import Chem
from rdkit.Chem.MolStandardize import rdMolStandardize

enumerator = rdMolStandardize.TautomerEnumerator()

mol1 = Chem.MolFromSmiles('C([C@@H](C(=O)O)N)O')
mol2 = enumerator.Canonicalize(mol1)
mol3 = enumerator.Canonicalize(mol2)

print('\n'.join([
    Chem.MolToSmiles(mol1),
    Chem.MolToSmiles(mol2),
    Chem.MolToSmiles(mol3)
]))
```

Output:
```
N[C@@H](CO)C(=O)O
NC(CO)C(=O)O
NC(C=O)C(O)O
```
**Expected behavior**

I would expect mol2 to be the same as mol3.  The canonical tautomer of the canonical tautomer should still be the original canonical tautomer (unless we hit MaxTautomers or something like that, which is not occurring here).

Instead mol2 and mol3 differ in where they've put the double bond.

**Configuration (please complete the following information):**
 - RDKit version: 2020.09.3
 - OS: Ubuntu 20.04
 - Python version (if relevant): 3.7.9
 - Are you using conda? Yes
 - If you are using conda, which channel did you install the rdkit from? conda-forge

diff --git a/Code/GraphMol/MolStandardize/Tautomer.cpp b/Code/GraphMol/MolStandardize/Tautomer.cpp
index 8a39a7f7b3c..8c36786a9b8 100644
--- a/Code/GraphMol/MolStandardize/Tautomer.cpp
+++ b/Code/GraphMol/MolStandardize/Tautomer.cpp
@@ -113,6 +113,7 @@ int scoreSubstructs(const ROMol &mol) {
       {"N=O", "[#7]=,:[#8]", 2},
       {"P=O", "[#15]=,:[#8]", 2},
       {"C=hetero", "[C]=[!#1;!#6]", 1},
+      {"C(=hetero)-hetero", "[C](=[!#1;!#6])[!#1;!#6]", 2},
       {"aromatic C = exocyclic N", "[c]=!@[N]", -1},
       {"methyl", "[CX4H3]", 1},
       {"guanidine terminal=N", "[#7]C(=[NR0])[#7H0]", 1},
@@ -279,7 +280,6 @@ TautomerEnumeratorResult TautomerEnumerator::enumerate(const ROMol &mol) const {
   RWMOL_SPTR kekulized(new RWMol(mol));
   MolOps::Kekulize(*kekulized, false);
   res.d_tautomers = {{smi, Tautomer(taut, kekulized, 0, 0)}};
-  std::set<std::string> done;
   res.d_modifiedAtoms.resize(mol.getNumAtoms());
   res.d_modifiedBonds.resize(mol.getNumBonds());
   bool completed = false;
@@ -292,184 +292,191 @@ TautomerEnumeratorResult TautomerEnumerator::enumerate(const ROMol &mol) const {
   while (!completed && !bailOut) {
     // std::map automatically sorts res.d_tautomers into alphabetical order
     // (SMILES)
-    for (const auto &tautomer : res.d_tautomers) {
+    for (auto &smilesTautomerPair : res.d_tautomers) {
 #ifdef VERBOSE_ENUMERATION
-      std::cout << "Done : " << std::endl;
-      for (const auto d : done) {
-        std::cout << d << std::endl;
+      std::cout << "Current tautomers: " << std::endl;
+      for (const auto &smilesTautomerPair : res.d_tautomers) {
+        std::cout << smilesTautomerPair.first << " done "
+                  << smilesTautomerPair.second.d_done << std::endl;
       }
-      std::cout << "Looking at tautomer: " << tautomer.first << std::endl;
 #endif
       std::string tsmiles;
-      if (done.count(tautomer.first)) {
+      if (smilesTautomerPair.second.d_done) {
+#ifdef VERBOSE_ENUMERATION
+        std::cout << "Skipping " << smilesTautomerPair.first << " as already done" << std::endl;
+#endif
         continue;
-      } else {
-        // done does not contain tautomer
-        for (const auto &transform : transforms) {
-          if (bailOut) {
-            break;
-          }
-          // kmol is the kekulized version of the tautomer
-          const auto &kmol = tautomer.second.kekulized;
-          std::vector<MatchVectType> matches;
-          unsigned int matched =
-              SubstructMatch(*kmol, *(transform.Mol), matches);
+      }
+#ifdef VERBOSE_ENUMERATION
+      std::cout << "Looking at tautomer: " << smilesTautomerPair.first
+                << std::endl;
+#endif
+      // tautomer not yet done
+      for (const auto &transform : transforms) {
+        if (bailOut) {
+          break;
+        }
+        // kmol is the kekulized version of the tautomer
+        const auto &kmol = smilesTautomerPair.second.kekulized;
+        std::vector<MatchVectType> matches;
+        unsigned int matched =
+            SubstructMatch(*kmol, *(transform.Mol), matches);

-          if (!matched) {
-            continue;
-          }
-          ++nTransforms;
+        if (!matched) {
+          continue;
+        }
+        ++nTransforms;
 #ifdef VERBOSE_ENUMERATION
-          std::string name;
-          (transform.Mol)->getProp(common_properties::_Name, name);
-          std::cout << "kmol: " << kmol->first << std::endl;
-          std::cout << MolToSmiles(*(kmol->second)) << std::endl;
-          std::cout << "transform mol: " << MolToSmarts(*(transform.Mol))
-                    << std::endl;
+        std::string name;
+        (transform.Mol)->getProp(common_properties::_Name, name);
+        std::cout << "kmol for " << smilesTautomerPair.first << " : " << MolToSmiles(*kmol) << std::endl;
+        std::cout << "transform mol: " << MolToSmarts(*(transform.Mol))
+                  << std::endl;

-          std::cout << "Matched: " << name << std::endl;
+        std::cout << "Matched: " << name << std::endl;
 #endif
-          // loop over transform matches
-          for (const auto &match : matches) {
-            if (nTransforms >= d_maxTransforms) {
-              res.d_status = TautomerEnumeratorStatus::MaxTransformsReached;
-              bailOut = true;
-            } else if (res.d_tautomers.size() >= d_maxTautomers) {
-              res.d_status = TautomerEnumeratorStatus::MaxTautomersReached;
-              bailOut = true;
-            } else if (d_callback.get() && !(*d_callback)(mol, res)) {
-              res.d_status = TautomerEnumeratorStatus::Canceled;
-              bailOut = true;
-            }
-            if (bailOut) {
-              break;
-            }
-            // Create a copy of in the input molecule so we can modify it
-            // Use kekule form so bonds are explicitly single/double instead of
-            // aromatic
-            ROMOL_SPTR product(new ROMol(*kmol));
-            // Remove a hydrogen from the first matched atom and add one to the
-            // last
-            int firstIdx = match.front().second;
-            int lastIdx = match.back().second;
-            Atom *first = product->getAtomWithIdx(firstIdx);
-            Atom *last = product->getAtomWithIdx(lastIdx);
-            res.d_modifiedAtoms.set(firstIdx);
-            res.d_modifiedAtoms.set(lastIdx);
-            first->setNumExplicitHs(
-                std::max(0, static_cast<int>(first->getTotalNumHs()) - 1));
-            last->setNumExplicitHs(last->getTotalNumHs() + 1);
-            // Remove any implicit hydrogens from the first and last atoms
-            // now we have set the count explicitly
-            first->setNoImplicit(true);
-            last->setNoImplicit(true);
-            // Adjust bond orders
-            unsigned int bi = 0;
-            for (size_t i = 0; i < match.size() - 1; ++i) {
-              Bond *bond = product->getBondBetweenAtoms(match[i].second,
-                                                        match[i + 1].second);
-              ASSERT_INVARIANT(bond, "required bond not found");
-              // check if bonds is specified in tautomer.in file
-              if (!transform.BondTypes.empty()) {
-                bond->setBondType(transform.BondTypes[bi]);
-                ++bi;
-              } else {
-                Bond::BondType bondtype = bond->getBondType();
+        // loop over transform matches
+        for (const auto &match : matches) {
+          if (nTransforms >= d_maxTransforms) {
+            res.d_status = TautomerEnumeratorStatus::MaxTransformsReached;
+            bailOut = true;
+          } else if (res.d_tautomers.size() >= d_maxTautomers) {
+            res.d_status = TautomerEnumeratorStatus::MaxTautomersReached;
+            bailOut = true;
+          } else if (d_callback.get() && !(*d_callback)(mol, res)) {
+            res.d_status = TautomerEnumeratorStatus::Canceled;
+            bailOut = true;
+          }
+          if (bailOut) {
+            break;
+          }
+          // Create a copy of in the input molecule so we can modify it
+          // Use kekule form so bonds are explicitly single/double instead of
+          // aromatic
+          RWMOL_SPTR product(new RWMol(*kmol));
+          // Remove a hydrogen from the first matched atom and add one to the
+          // last
+          int firstIdx = match.front().second;
+          int lastIdx = match.back().second;
+          Atom *first = product->getAtomWithIdx(firstIdx);
+          Atom *last = product->getAtomWithIdx(lastIdx);
+          res.d_modifiedAtoms.set(firstIdx);
+          res.d_modifiedAtoms.set(lastIdx);
+          first->setNumExplicitHs(
+              std::max(0, static_cast<int>(first->getTotalNumHs()) - 1));
+          last->setNumExplicitHs(last->getTotalNumHs() + 1);
+          // Remove any implicit hydrogens from the first and last atoms
+          // now we have set the count explicitly
+          first->setNoImplicit(true);
+          last->setNoImplicit(true);
+          // Adjust bond orders
+          unsigned int bi = 0;
+          for (size_t i = 0; i < match.size() - 1; ++i) {
+            Bond *bond = product->getBondBetweenAtoms(match[i].second,
+                                                      match[i + 1].second);
+            ASSERT_INVARIANT(bond, "required bond not found");
+            // check if bonds is specified in tautomer.in file
+            if (!transform.BondTypes.empty()) {
+              bond->setBondType(transform.BondTypes[bi]);
+              ++bi;
+            } else {
+              Bond::BondType bondtype = bond->getBondType();
 #ifdef VERBOSE_ENUMERATION
-                std::cout << "Bond as double: " << bond->getBondTypeAsDouble()
-                          << std::endl;
-                std::cout << bondtype << std::endl;
+              std::cout << "Bond as double: " << bond->getBondTypeAsDouble()
+                        << std::endl;
+              std::cout << bondtype << std::endl;
 #endif
-                if (bondtype == Bond::SINGLE) {
-                  bond->setBondType(Bond::DOUBLE);
+              if (bondtype == Bond::SINGLE) {
+                bond->setBondType(Bond::DOUBLE);
 #ifdef VERBOSE_ENUMERATION
-                  std::cout << "Set bond to double" << std::endl;
+                std::cout << "Set bond to double" << std::endl;
 #endif
-                }
-                if (bondtype == Bond::DOUBLE) {
-                  bond->setBondType(Bond::SINGLE);
+              }
+              if (bondtype == Bond::DOUBLE) {
+                bond->setBondType(Bond::SINGLE);
 #ifdef VERBOSE_ENUMERATION
-                  std::cout << "Set bond to single" << std::endl;
+                std::cout << "Set bond to single" << std::endl;
 #endif
-                }
               }
-              res.d_modifiedBonds.set(bond->getIdx());
             }
-            // TODO adjust charges
-            if (!transform.Charges.empty()) {
-              unsigned int ci = 0;
-              for (const auto &pair : match) {
-                Atom *atom = product->getAtomWithIdx(pair.second);
-                atom->setFormalCharge(atom->getFormalCharge() +
-                                      transform.Charges[ci++]);
-              }
+            res.d_modifiedBonds.set(bond->getIdx());
+          }
+          // TODO adjust charges
+          if (!transform.Charges.empty()) {
+            unsigned int ci = 0;
+            for (const auto &pair : match) {
+              Atom *atom = product->getAtomWithIdx(pair.second);
+              atom->setFormalCharge(atom->getFormalCharge() +
+                                    transform.Charges[ci++]);
             }
+          }

-            RWMOL_SPTR wproduct(new RWMol(*product));
+          unsigned int failedOp;
+          MolOps::sanitizeMol(*product, failedOp,
+                              MolOps::SANITIZE_KEKULIZE |
+                                  MolOps::SANITIZE_SETAROMATICITY |
+                                  MolOps::SANITIZE_SETCONJUGATION |
+                                  MolOps::SANITIZE_SETHYBRIDIZATION |
+                                  MolOps::SANITIZE_ADJUSTHS);
 #ifdef VERBOSE_ENUMERATION
-            std::cout << "pre-sanitization: "
-                      << MolToSmiles(*wproduct, true, true) << std::endl;
-#endif
-            unsigned int failedOp;
-            MolOps::sanitizeMol(*wproduct, failedOp,
-                                MolOps::SANITIZE_KEKULIZE |
-                                    MolOps::SANITIZE_SETAROMATICITY |
-                                    MolOps::SANITIZE_SETCONJUGATION |
-                                    MolOps::SANITIZE_SETHYBRIDIZATION |
-                                    MolOps::SANITIZE_ADJUSTHS);
-            setTautomerStereoAndIsoHs(mol, *wproduct, res);
-            tsmiles = MolToSmiles(*wproduct, true);
-#ifdef VERBOSE_ENUMERATION
-            std::string name;
-            (transform.Mol)->getProp(common_properties::_Name, name);
-            std::cout << "Applied rule: " << name << " to " << tautomer.first
-                      << std::endl;
+          std::cout << "pre-setTautomerStereo: " << MolToSmiles(*product, true)
+                    << std::endl;
 #endif
-            if (res.d_tautomers.find(tsmiles) != res.d_tautomers.end()) {
+          setTautomerStereoAndIsoHs(mol, *product, res);
+          tsmiles = MolToSmiles(*product, true);
 #ifdef VERBOSE_ENUMERATION
-              std::cout << "Previous tautomer produced again: " << tsmiles
-                        << std::endl;
+          (transform.Mol)->getProp(common_properties::_Name, name);
+          std::cout << "Applied rule: " << name << " to " << smilesTautomerPair.first
+                    << std::endl;
 #endif
-              continue;
-            }
+          if (res.d_tautomers.find(tsmiles) != res.d_tautomers.end()) {
 #ifdef VERBOSE_ENUMERATION
-            std::cout << "New tautomer produced: " << tsmiles << std::endl;
+            std::cout << "Previous tautomer produced again: " << tsmiles
+                      << std::endl;
 #endif
-            // in addition to the above transformations, sanitzation may modify
-            // bonds, e.g. Cc1nc2ccccc2[nH]1
-            for (size_t i = 0; i < mol.getNumBonds(); i++) {
-              auto molBondType = mol.getBondWithIdx(i)->getBondType();
-              auto tautBondType = wproduct->getBondWithIdx(i)->getBondType();
-              if (molBondType != tautBondType && !res.d_modifiedBonds.test(i)) {
+            continue;
+          }
+          // in addition to the above transformations, sanitization may modify
+          // bonds, e.g. Cc1nc2ccccc2[nH]1
+          for (size_t i = 0; i < mol.getNumBonds(); i++) {
+            auto molBondType = mol.getBondWithIdx(i)->getBondType();
+            auto tautBondType = product->getBondWithIdx(i)->getBondType();
+            if (molBondType != tautBondType && !res.d_modifiedBonds.test(i)) {
 #ifdef VERBOSE_ENUMERATION
-                std::cout << "Sanitization has modified bond " << i
-                          << std::endl;
+              std::cout << "Sanitization has modified bond " << i
+                        << std::endl;
 #endif
-                res.d_modifiedBonds.set(i);
-              }
+              res.d_modifiedBonds.set(i);
             }
-            RWMOL_SPTR kekulized_product(new RWMol(*wproduct));
-            MolOps::Kekulize(*kekulized_product, false);
-            res.d_tautomers[tsmiles] = Tautomer(
-                std::move(wproduct), std::move(kekulized_product),
-                res.d_modifiedAtoms.count(), res.d_modifiedBonds.count());
-
+          }
+          RWMOL_SPTR kekulized_product(new RWMol(*product));
+          MolOps::Kekulize(*kekulized_product, false);
 #ifdef VERBOSE_ENUMERATION
-            std::cout << "Now completed: " << std::endl;
-            for (const auto &tautomer : res.d_tautomers) {
-              std::cout << tautomer.first << std::endl;
-            }
-#endif
+          auto it = res.d_tautomers.find(tsmiles);
+          if (it == res.d_tautomers.end()) {
+            std::cout << "New tautomer added as ";
+          } else {
+            std::cout << "New tautomer replaced for ";
           }
+          std::cout << tsmiles << ", taut: " << MolToSmiles(*product)
+                    << ", kek: " << MolToSmiles(*kekulized_product, true, true)
+                    << std::endl;
+#endif
+          res.d_tautomers[tsmiles] = Tautomer(
+              std::move(product), std::move(kekulized_product),
+              res.d_modifiedAtoms.count(), res.d_modifiedBonds.count());
         }
       }
-      done.insert(tautomer.first);
+      smilesTautomerPair.second.d_done = true;
     }
-    completed = (res.d_tautomers.size() <= done.size());
+    completed = true;
     size_t maxNumModifiedAtoms = res.d_modifiedAtoms.count();
     size_t maxNumModifiedBonds = res.d_modifiedBonds.count();
     for (auto it = res.d_tautomers.begin(); it != res.d_tautomers.end();) {
       auto &taut = it->second;
+      if (!taut.d_done) {
+        completed = false;
+      }
       if ((taut.d_numModifiedAtoms < maxNumModifiedAtoms ||
            taut.d_numModifiedBonds < maxNumModifiedBonds) &&
           setTautomerStereoAndIsoHs(mol, *taut.tautomer, res)) {
diff --git a/Code/GraphMol/MolStandardize/Tautomer.h b/Code/GraphMol/MolStandardize/Tautomer.h
index bf589f05060..ea6226c6b62 100644
--- a/Code/GraphMol/MolStandardize/Tautomer.h
+++ b/Code/GraphMol/MolStandardize/Tautomer.h
@@ -54,18 +54,20 @@ class Tautomer {
   friend class TautomerEnumerator;

  public:
-  Tautomer() : d_numModifiedAtoms(0), d_numModifiedBonds(0) {}
+  Tautomer() : d_numModifiedAtoms(0), d_numModifiedBonds(0), d_done(false) {}
   Tautomer(const ROMOL_SPTR &t, const ROMOL_SPTR &k, size_t a = 0, size_t b = 0)
       : tautomer(t),
         kekulized(k),
         d_numModifiedAtoms(a),
-        d_numModifiedBonds(b) {}
+        d_numModifiedBonds(b),
+        d_done(false) {}
   ROMOL_SPTR tautomer;
   ROMOL_SPTR kekulized;

  private:
   size_t d_numModifiedAtoms;
   size_t d_numModifiedBonds;
+  bool d_done;
 };

 typedef std::map<std::string, Tautomer> SmilesTautomerMap;

diff --git a/Code/GraphMol/MolStandardize/testTautomer.cpp b/Code/GraphMol/MolStandardize/testTautomer.cpp
index 85b100abfa3..a4c9213a4ca 100644
--- a/Code/GraphMol/MolStandardize/testTautomer.cpp
+++ b/Code/GraphMol/MolStandardize/testTautomer.cpp
@@ -1416,6 +1416,24 @@ void testGithub3430() {
   }
 }

+void testGithub3755() {
+  BOOST_LOG(rdInfoLog) << "-----------------------\n testGithub3755"
+                       << std::endl;
+  // hydrates, aminals and hemiaminals should be scored lower than
+  // carboxylic acids, amides, amidines, and guanidines
+  std::vector<std::pair<std::string, std::string>> orig_vs_expected{
+      {"OC(=O)C(N)CO", "NC(CO)C(=O)O"}, {"C([C@@H](C(=O)O)N)O", "NC(CO)C(=O)O"},
+      {"OC(=O)C(N)CN", "NCC(N)C(=O)O"}, {"NC(=O)C(N)CO", "NC(=O)C(N)CO"},
+      {"NC(=N)C(N)CO", "N=C(N)C(N)CO"}, {"NC(=N)NC(N)CO", "N=C(N)NC(N)CO"}};
+  TautomerEnumerator te;
+  for (const auto &pair : orig_vs_expected) {
+    ROMOL_SPTR orig(SmilesToMol(pair.first));
+    TEST_ASSERT(orig);
+    ROMOL_SPTR canonical(te.canonicalize(*orig));
+    TEST_ASSERT(MolToSmiles(*canonical) == pair.second);
+  }
+}
+
 int main() {
   RDLog::InitLogs();
 #if 1
@@ -1433,5 +1451,6 @@ int main() {
   testPickCanonicalCIPChangeOnChiralCenter();
   testTautomerEnumeratorResult_const_iterator();
   testGithub3430();
+  testGithub3755();
   return 0;
 }