Repository: quantumlib/Cirq

Issue:
SingleQubitCliffordGate does not properly serialize to JSON
**Description of the issue**

`SingleQubitCliffordGate`s write incomplete information to JSON, and their type cannot be understood by the JSON deserializer.

**How to reproduce the issue**

```python
>>> import cirq
>>> print(cirq.__version__)
0.10.0
>>> q = cirq.LineQubit(0)
>>> json_text = cirq.to_json(cirq.SingleQubitCliffordGate.X(q))
>>> print(json_text)
{
  "cirq_type": "GateOperation",
  "gate": {
    "cirq_type": "SingleQubitCliffordGate"
  },
  "qubits": [
    {
      "cirq_type": "LineQubit",
      "x": 0
    }
  ]
}
>>> deserialized_gate = cirq.read_json(json_text=json_text)
ValueError: Could not resolve type 'SingleQubitCliffordGate' during deserialization
```

**Cirq version**

0.10.0




Discussion:
This is because `SingleQubitCliffordGate` is [not yet serializable](https://github.com/quantumlib/Cirq/blob/06e48928ed9a504dba0985ed9a4fba33709a783a/cirq/protocols/json_test_data/spec.py#L67)

However, I think we should try to enforce that objects which are not yet serializable throw an error instead out producing incomplete json. 
That would be seem a step in the right direction. Also would be nice if this was clear in the documentation somewhere (instead of only called out in test files).
I would like to work on this issue.
Hi @PoPaT1618 - thank you for volunteering! 

I saw that you are new to the project, so, just to clarify, the following needs to be done: 

- remove `SingleQubitCliffordGate` from this list:
https://github.com/quantumlib/Cirq/blob/a41df7f97a9eddba8fcaa4c19c6366acd67de4de/cirq-core/cirq/protocols/json_test_data/spec.py#L67 
- add the class between these two lines: 
https://github.com/quantumlib/Cirq/blob/a41df7f97a9eddba8fcaa4c19c6366acd67de4de/cirq-core/cirq/json_resolver_cache.py#L125-L126
- then we'll have to make the `cirq-core/cirq/protocols/json_serialization_test.py` pass (you can run it with `check/pytest cirq-core/cirq/protocols/json_serialization_test.py`)
    -  you'll need to implement the `_json_dict_` magic method for the json serialization protocol within the `SingleQubitCliffordGate` class, along the lines of: 
```
    def _json_dict_(self) -> Dict[str, Any]:
        return protocols.obj_to_dict_helper(
            self, ['_rotation_map', '_inverse_map']
        )
```

At this point you might be ready to commit, and create a pull request, but ping us if you run into issues.

Thanks!

This has been idle for a while. Would you mind if I take a shot at it?
Yes, please take it. I will be able to contribute to Cirq from the month of September.

Resolving patch:
diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py
--- a/cirq-core/cirq/json_resolver_cache.py
+++ b/cirq-core/cirq/json_resolver_cache.py
@@ -128,6 +128,7 @@ def two_qubit_matrix_gate(matrix):
         'QuantumFourierTransformGate': cirq.QuantumFourierTransformGate,
         'RepetitionsStoppingCriteria': cirq.work.RepetitionsStoppingCriteria,
         'ResetChannel': cirq.ResetChannel,
+        'SingleQubitCliffordGate': cirq.SingleQubitCliffordGate,
         'SingleQubitMatrixGate': single_qubit_matrix_gate,
         'SingleQubitPauliStringGateOperation': cirq.SingleQubitPauliStringGateOperation,
         'SingleQubitReadoutCalibrationResult': cirq.experiments.SingleQubitReadoutCalibrationResult,
diff --git a/cirq-core/cirq/ops/clifford_gate.py b/cirq-core/cirq/ops/clifford_gate.py
--- a/cirq-core/cirq/ops/clifford_gate.py
+++ b/cirq-core/cirq/ops/clifford_gate.py
@@ -436,6 +436,21 @@ def __repr__(self) -> str:
             f'Y:{y_sign}{y.to!s}, Z:{z_sign}{z.to!s})'
         )
 
+    @classmethod
+    def _from_json_dict_(cls, _rotation_map, _inverse_map, **kwargs):
+        return cls(
+            _rotation_map=dict([[k, PauliTransform(*v)] for k, v in _rotation_map]),
+            _inverse_map=dict([[k, PauliTransform(*v)] for k, v in _inverse_map]),
+        )
+
+    def _json_dict_(self) -> Dict[str, Any]:
+        return {
+            'cirq_type': self.__class__.__name__,
+            # JSON requires mappings to have string keys.
+            '_rotation_map': list(self._rotation_map.items()),
+            '_inverse_map': list(self._inverse_map.items()),
+        }
+
     def _circuit_diagram_info_(
         self, args: 'cirq.CircuitDiagramInfoArgs'
     ) -> 'cirq.CircuitDiagramInfo':


Test patch:
diff --git a/cirq-core/cirq/ops/clifford_gate_test.py b/cirq-core/cirq/ops/clifford_gate_test.py
--- a/cirq-core/cirq/ops/clifford_gate_test.py
+++ b/cirq-core/cirq/ops/clifford_gate_test.py
@@ -443,6 +443,11 @@ def test_commutes_single_qubit_gate(gate, other):
     assert_allclose_up_to_global_phase(mat, mat_swap, rtol=1e-7, atol=1e-7)
 
 
+@pytest.mark.parametrize('gate', _all_clifford_gates())
+def test_parses_single_qubit_gate(gate):
+    assert gate == cirq.read_json(json_text=(cirq.to_json(gate)))
+
+
 @pytest.mark.parametrize(
     'gate,pauli,half_turns',
     itertools.product(_all_clifford_gates(), _paulis, (1.0, 0.25, 0.5, -0.5)),
diff --git a/cirq-core/cirq/protocols/json_test_data/SingleQubitCliffordGate.json b/cirq-core/cirq/protocols/json_test_data/SingleQubitCliffordGate.json
new file mode 100644
--- /dev/null
+++ b/cirq-core/cirq/protocols/json_test_data/SingleQubitCliffordGate.json
@@ -0,0 +1,97 @@
+{
+  "cirq_type": "SingleQubitCliffordGate",
+  "_rotation_map": [
+    [
+      {
+        "cirq_type": "_PauliX",
+        "exponent": 1.0,
+        "global_shift": 0.0
+      },
+      [
+        {
+          "cirq_type": "_PauliX",
+          "exponent": 1.0,
+          "global_shift": 0.0
+        },
+        false
+      ]
+    ],
+    [
+      {
+        "cirq_type": "_PauliZ",
+        "exponent": 1.0,
+        "global_shift": 0.0
+      },
+      [
+        {
+          "cirq_type": "_PauliY",
+          "exponent": 1.0,
+          "global_shift": 0.0
+        },
+        false
+      ]
+    ],
+    [
+      {
+        "cirq_type": "_PauliY",
+        "exponent": 1.0,
+        "global_shift": 0.0
+      },
+      [
+        {
+          "cirq_type": "_PauliZ",
+          "exponent": 1.0,
+          "global_shift": 0.0
+        },
+        true
+      ]
+    ]
+  ],
+  "_inverse_map": [
+    [
+      {
+        "cirq_type": "_PauliX",
+        "exponent": 1.0,
+        "global_shift": 0.0
+      },
+      [
+        {
+          "cirq_type": "_PauliX",
+          "exponent": 1.0,
+          "global_shift": 0.0
+        },
+        false
+      ]
+    ],
+    [
+      {
+        "cirq_type": "_PauliY",
+        "exponent": 1.0,
+        "global_shift": 0.0
+      },
+      [
+        {
+          "cirq_type": "_PauliZ",
+          "exponent": 1.0,
+          "global_shift": 0.0
+        },
+        false
+      ]
+    ],
+    [
+      {
+        "cirq_type": "_PauliZ",
+        "exponent": 1.0,
+        "global_shift": 0.0
+      },
+      [
+        {
+          "cirq_type": "_PauliY",
+          "exponent": 1.0,
+          "global_shift": 0.0
+        },
+        true
+      ]
+    ]
+  ]
+}
\ No newline at end of file
diff --git a/cirq-core/cirq/protocols/json_test_data/SingleQubitCliffordGate.repr b/cirq-core/cirq/protocols/json_test_data/SingleQubitCliffordGate.repr
new file mode 100644
--- /dev/null
+++ b/cirq-core/cirq/protocols/json_test_data/SingleQubitCliffordGate.repr
@@ -0,0 +1,2 @@
+cirq.SingleQubitCliffordGate(_rotation_map={cirq.X: cirq.PauliTransform(to=cirq.X, flip=False), cirq.Z: cirq.PauliTransform(to=cirq.Y, flip=False), cirq.Y: cirq.PauliTransform(to=cirq.Z, flip=True)},
+_inverse_map={cirq.X: cirq.PauliTransform(to=cirq.X, flip=False), cirq.Y: cirq.PauliTransform(to=cirq.Z, flip=False), cirq.Z: cirq.PauliTransform(to=cirq.Y, flip=True)})
\ No newline at end of file
diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py
--- a/cirq-core/cirq/protocols/json_test_data/spec.py
+++ b/cirq-core/cirq/protocols/json_test_data/spec.py
@@ -64,7 +64,6 @@
         'QuilFormatter',
         'QuilOutput',
         'SimulationTrialResult',
-        'SingleQubitCliffordGate',
         'SparseSimulatorStep',
         'StateVectorMixin',
         'TextDiagramDrawer',