Repository: quantumlib/Cirq

Issue:
Single qubit clifford XZ decomposition is missing two Cliffords
**Description of the issue**
See title

**How to reproduce the issue**

```python
import cirq.experiments.qubit_characterizations as ceqc
print(len(ceqc._single_qubit_cliffords().c1_in_xz))  # should be 24, but is 22
```


**Cirq version**
0.9.0.dev



Discussion:
In addition, is possible to change the XZ decomposition to use ZXZ instead of XZX so that each clifford has at most 1 microwave gate?

Resolving patch:
diff --git a/cirq/experiments/qubit_characterizations.py b/cirq/experiments/qubit_characterizations.py
--- a/cirq/experiments/qubit_characterizations.py
+++ b/cirq/experiments/qubit_characterizations.py
@@ -12,21 +12,39 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import dataclasses
 import itertools
 
-from typing import Any, Iterator, List, NamedTuple, Optional, Sequence, Tuple
+from typing import Any, Iterator, List, Optional, Sequence, Tuple
 import numpy as np
 import sympy
 
 from matplotlib import pyplot as plt
-from mpl_toolkits.mplot3d import Axes3D  # type: ignore # pylint: disable=unused-import
 from cirq import circuits, devices, ops, protocols, study, work
 
-Cliffords = NamedTuple('Cliffords', [('c1_in_xy', List[List[ops.Gate]]),
-                                     ('c1_in_xz', List[List[ops.Gate]]),
-                                     ('s1', List[List[ops.Gate]]),
-                                     ('s1_x', List[List[ops.Gate]]),
-                                     ('s1_y', List[List[ops.Gate]])])
+
+@dataclasses.dataclass
+class Cliffords:
+    """The single-qubit Clifford group, decomposed into elementary gates.
+
+    The decomposition of the Cliffords follows those described in
+    Barends et al., Nature 508, 500 (https://arxiv.org/abs/1402.4848).
+
+    Decompositions of the Clifford group:
+        c1_in_xy: decomposed into XPowGate and YPowGate.
+        c1_in_xz: decomposed into XPowGate and ZPowGate, with at most one
+            XPowGate (one microwave gate) per Clifford.
+
+    Subsets used to generate the 2-qubit Clifford group (see paper table S7):
+        s1
+        s1_x
+        s1_y
+    """
+    c1_in_xy: List[List[ops.Gate]]
+    c1_in_xz: List[List[ops.Gate]]
+    s1: List[List[ops.Gate]]
+    s1_x: List[List[ops.Gate]]
+    s1_y: List[List[ops.Gate]]
 
 
 class RabiResult:
@@ -647,7 +665,7 @@ def _two_qubit_clifford(q_0: devices.GridQubit, q_1: devices.GridQubit,
     An integer (idx) from 0 to 11519 is used to generate a two-qubit Clifford
     gate which is constructed with single-qubit X and Y rotations and CZ gates.
     The decomposition of the Cliffords follow those described in the appendix
-    of Barends et al., Nature 508, 500.
+    of Barends et al., Nature 508, 500 (https://arxiv.org/abs/1402.4848).
 
     The integer idx is first decomposed into idx_0 (which ranges from 0 to
     23), idx_1 (ranging from 0 to 23) and idx_2 (ranging from 0 to 19). idx_0
@@ -744,33 +762,56 @@ def _single_qubit_gates(gate_seq: Sequence[ops.Gate],
 
 
 def _single_qubit_cliffords() -> Cliffords:
+    X, Y, Z = ops.X, ops.Y, ops.Z
+
     c1_in_xy = []  # type: List[List[ops.Gate]]
     c1_in_xz = []  # type: List[List[ops.Gate]]
 
     for phi_0, phi_1 in itertools.product([1.0, 0.5, -0.5], [0.0, 0.5, -0.5]):
-        c1_in_xy.append([ops.X**phi_0, ops.Y**phi_1])
-        c1_in_xy.append([ops.Y**phi_0, ops.X**phi_1])
-        c1_in_xz.append([ops.X**phi_0, ops.Z**phi_1])
-        c1_in_xz.append([ops.Z**phi_0, ops.X**phi_1])
-
-    c1_in_xy.append([ops.X**0.0])
-    c1_in_xy.append([ops.Y, ops.X])
-
-    phi_xy = [[-0.5, 0.5, 0.5], [-0.5, -0.5, 0.5], [0.5, 0.5, 0.5],
-              [-0.5, 0.5, -0.5]]
-    for phi in phi_xy:
-        c1_in_xy.append([ops.X**phi[0], ops.Y**phi[1], ops.X**phi[2]])
-
-    phi_xz = [[0.5, 0.5, -0.5], [0.5, -0.5, -0.5], [-0.5, -0.5, -0.5],
-              [-0.5, 0.5, -0.5]]
-    for phi in phi_xz:
-        c1_in_xz.append([ops.X**phi[0], ops.Z**phi[1], ops.X**phi[2]])
-
-    s1 = [[ops.X**0.0], [ops.Y**0.5, ops.X**0.5],
-          [ops.X**-0.5, ops.Y**-0.5]]  # type: List[List[ops.Gate]]
-    s1_x = [[ops.X**0.5], [ops.X**0.5, ops.Y**0.5, ops.X**0.5],
-            [ops.Y**-0.5]]  # type: List[List[ops.Gate]]
-    s1_y = [[ops.Y**0.5], [ops.X**-0.5, ops.Y**-0.5, ops.X**0.5],
-            [ops.Y, ops.X**0.5]]  # type: List[List[ops.Gate]]
+        c1_in_xy.append([X**phi_0, Y**phi_1])
+        c1_in_xy.append([Y**phi_0, X**phi_1])
+        c1_in_xz.append([X**phi_0, Z**phi_1])
+        c1_in_xz.append([Z**phi_0, X**phi_1])
+
+    # identity
+    c1_in_xy.append([X**0.0])
+    c1_in_xz.append([X**0.0])
+
+    c1_in_xy.append([Y, X])
+    c1_in_xz.append([Z, X])
+
+    phi_xy = [
+        [-0.5, 0.5, 0.5],
+        [-0.5, -0.5, 0.5],
+        [0.5, 0.5, 0.5],
+        [-0.5, 0.5, -0.5],
+    ]
+    for y0, x, y1 in phi_xy:
+        c1_in_xy.append([Y**y0, X**x, Y**y1])
+
+    phi_xz = [
+        [0.5, 0.5, -0.5],
+        [0.5, -0.5, -0.5],
+        [-0.5, -0.5, -0.5],
+        [-0.5, 0.5, -0.5],
+    ]
+    for z0, x, z1 in phi_xz:
+        c1_in_xz.append([Z**z0, X**x, Z**z1])
+
+    s1 = [
+        [X**0.0],
+        [Y**0.5, X**0.5],
+        [X**-0.5, Y**-0.5],
+    ]  # type: List[List[ops.Gate]]
+    s1_x = [
+        [X**0.5],
+        [X**0.5, Y**0.5, X**0.5],
+        [Y**-0.5],
+    ]  # type: List[List[ops.Gate]]
+    s1_y = [
+        [Y**0.5],
+        [X**-0.5, Y**-0.5, X**0.5],
+        [Y, X**0.5],
+    ]  # type: List[List[ops.Gate]]
 
     return Cliffords(c1_in_xy, c1_in_xz, s1, s1_x, s1_y)


Test patch:
diff --git a/cirq/experiments/qubit_characterizations_test.py b/cirq/experiments/qubit_characterizations_test.py
--- a/cirq/experiments/qubit_characterizations_test.py
+++ b/cirq/experiments/qubit_characterizations_test.py
@@ -17,6 +17,8 @@
 
 import matplotlib.pyplot as plt
 
+import cirq
+import cirq.experiments.qubit_characterizations as ceqc
 from cirq import GridQubit
 from cirq import circuits, ops, sim
 from cirq.experiments import (rabi_oscillations,
@@ -40,6 +42,44 @@ def test_rabi_oscillations():
     assert rms_err < 0.1
 
 
+def test_single_qubit_cliffords():
+    cliffords = ceqc._single_qubit_cliffords()
+    assert len(cliffords.c1_in_xy) == 24
+    assert len(cliffords.c1_in_xz) == 24
+
+    def unitary(gates):
+        U = np.eye(2)
+        for gate in gates:
+            U = cirq.unitary(gate) @ U
+        return U
+
+    xy_unitaries = [unitary(gates) for gates in cliffords.c1_in_xy]
+    xz_unitaries = [unitary(gates) for gates in cliffords.c1_in_xz]
+
+    def check_distinct(unitaries):
+        n = len(unitaries)
+        for i in range(n):
+            for j in range(i + 1, n):
+                Ui, Uj = unitaries[i], unitaries[j]
+                assert not cirq.allclose_up_to_global_phase(Ui, Uj), f'{i}, {j}'
+
+    # Check that unitaries in each decomposition are distinct.
+    check_distinct(xy_unitaries)
+    check_distinct(xz_unitaries)
+
+    # Check that each decomposition gives the same set of unitaries.
+    for Uxy in xy_unitaries:
+        assert any(
+            cirq.allclose_up_to_global_phase(Uxy, Uxz) for Uxz in xz_unitaries)
+
+    # Check that XZ decomposition has at most one X gate per clifford.
+    for gates in cliffords.c1_in_xz:
+        num_x = len([gate for gate in gates if isinstance(gate, cirq.XPowGate)])
+        num_z = len([gate for gate in gates if isinstance(gate, cirq.ZPowGate)])
+        assert num_x + num_z == len(gates)
+        assert num_x <= 1
+
+
 def test_single_qubit_randomized_benchmarking():
     # Check that the ground state population at the end of the Clifford
     # sequences is always unity.