Repository: quantumlib/Cirq

Issue:
cirq.read_json() fails to read a json containing a QasmUGate
**Description of the issue**
The function cirq.read_json() fails to read a json containing a QasmUGate. Even if the json was created by cirq.to_json().

**How to reproduce the issue**

```python
import cirq.contrib.qasm_import
qasm = """OPENQASM 2.0;
include "qelib1.inc";
qreg q[1];
u3(3,1,2) q[0];
"""
circuit = cirq.contrib.qasm_import.circuit_from_qasm(qasm)
cirq.read_json(json_text=cirq.to_json(circuit))
```
This will produce the following error:
<details>

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-a9e721e629c2> in <module>
      6 """
      7 circuit = cirq.contrib.qasm_import.circuit_from_qasm(qasm)
----> 8 cirq.read_json(json_text=cirq.to_json(circuit))

~/.pyenv/versions/3.8.5/lib/python3.8/site-packages/cirq/protocols/json_serialization.py in read_json(file_or_fn, json_text, resolvers)
    628 
    629     if json_text is not None:
--> 630         return json.loads(json_text, object_hook=obj_hook)
    631 
    632     if isinstance(file_or_fn, (str, pathlib.Path)):

~/.pyenv/versions/3.8.5/lib/python3.8/json/__init__.py in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    368     if parse_constant is not None:
    369         kw['parse_constant'] = parse_constant
--> 370     return cls(**kw).decode(s)

~/.pyenv/versions/3.8.5/lib/python3.8/json/decoder.py in decode(self, s, _w)
    335 
    336         """
--> 337         obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338         end = _w(s, end).end()
    339         if end != len(s):

~/.pyenv/versions/3.8.5/lib/python3.8/json/decoder.py in raw_decode(self, s, idx)
    351         """
    352         try:
--> 353             obj, end = self.scan_once(s, idx)
    354         except StopIteration as err:
    355             raise JSONDecodeError("Expecting value", s, err.value) from None

~/.pyenv/versions/3.8.5/lib/python3.8/site-packages/cirq/protocols/json_serialization.py in obj_hook(x)
    625 
    626     def obj_hook(x):
--> 627         return _cirq_object_hook(x, resolvers, context_map)
    628 
    629     if json_text is not None:

~/.pyenv/versions/3.8.5/lib/python3.8/site-packages/cirq/protocols/json_serialization.py in _cirq_object_hook(d, resolvers, context_map)
    352             break
    353     else:
--> 354         raise ValueError(f"Could not resolve type '{d['cirq_type']}' during deserialization")
    355 
    356     from_json_dict = getattr(cls, '_from_json_dict_', None)

ValueError: Could not resolve type 'QasmUGate' during deserialization

</details>

**Cirq version**
0.13.1



Discussion:
The bug is caused because `QasmUGate` does not follow the JSON serialization rules defined by [serialization.md](https://github.com/quantumlib/Cirq/blob/master/docs/dev/serialization.md). An object is serialized to JSON with the help of a `_json_dict_` method and restored by a `_from_json_dict_` method. `QasmUGate` does not provide either of them. The above circuit example can be serialized because `QasmUGate` inherits `Gate`, which has a `_json_dict_` method. The generated JSON string is
```
{
  "cirq_type": "Circuit",
  "moments": [
    {
      "cirq_type": "Moment",
      "operations": [
        {
          "cirq_type": "GateOperation",
          "gate": {
            "cirq_type": "QasmUGate"
          },
          "qubits": [
            {
              "cirq_type": "NamedQubit",
              "name": "q_0"
            }
          ]
        }
      ]
    }
  ],
  "device": {
    "cirq_type": "_UnconstrainedDevice"
  }
}
```
It contains only the type of gate (`QasmUGate`) but lacks angle parameters. The datatype `QasmUGate` is not included in the file [json_resolver_cache.py](https://github.com/quantumlib/Cirq/blob/master/cirq-core/cirq/json_resolver_cache.py), so that the unserializing process has no idea what class to translate JSON type `QasmUGate` to.

I have added `_json_dict_` and `_from_json_dict_` methods to `QasmUGate` and the bug is resolved. If no one is working on this, I can create a PR. The PR should also add the serialization feature to other QASM-related classes, such as  `QasmTwoQubitGate` (are there more?).

Resolving patch:
diff --git a/cirq-core/cirq/circuits/qasm_output.py b/cirq-core/cirq/circuits/qasm_output.py
--- a/cirq-core/cirq/circuits/qasm_output.py
+++ b/cirq-core/cirq/circuits/qasm_output.py
@@ -83,6 +83,17 @@ def _decompose_(self, qubits):
     def _value_equality_values_(self):
         return self.lmda, self.theta, self.phi
 
+    def _json_dict_(self) -> Dict[str, float]:
+        return {
+            'theta': self.theta,
+            'phi': self.phi,
+            'lmda': self.lmda,
+        }
+
+    @classmethod
+    def _from_json_dict_(cls, theta: float, phi: float, lmda: float, **kwargs) -> 'QasmUGate':
+        return cls(theta, phi, lmda)
+
 
 @value.value_equality
 class QasmTwoQubitGate(ops.Gate):
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
@@ -51,39 +51,35 @@ def _parallel_gate_op(gate, qubits):
     import sympy
 
     return {
-        'AnyIntegerPowerGateFamily': cirq.AnyIntegerPowerGateFamily,
         'AmplitudeDampingChannel': cirq.AmplitudeDampingChannel,
+        'AnyIntegerPowerGateFamily': cirq.AnyIntegerPowerGateFamily,
         'AnyUnitaryGateFamily': cirq.AnyUnitaryGateFamily,
         'AsymmetricDepolarizingChannel': cirq.AsymmetricDepolarizingChannel,
         'BitFlipChannel': cirq.BitFlipChannel,
         'BitstringAccumulator': cirq.work.BitstringAccumulator,
         'BooleanHamiltonian': cirq.BooleanHamiltonian,
-        'ProductState': cirq.ProductState,
         'CCNotPowGate': cirq.CCNotPowGate,
         'CCXPowGate': cirq.CCXPowGate,
         'CCZPowGate': cirq.CCZPowGate,
-        'CNotPowGate': cirq.CNotPowGate,
+        'Circuit': cirq.Circuit,
+        'CircuitOperation': cirq.CircuitOperation,
         'ClassicallyControlledOperation': cirq.ClassicallyControlledOperation,
+        'CliffordState': cirq.CliffordState,
+        'CliffordTableau': cirq.CliffordTableau,
+        'CNotPowGate': cirq.CNotPowGate,
+        'ConstantQubitNoiseModel': cirq.ConstantQubitNoiseModel,
         'ControlledGate': cirq.ControlledGate,
         'ControlledOperation': cirq.ControlledOperation,
+        'CrossEntropyResult': CrossEntropyResult,
+        'CrossEntropyResultDict': CrossEntropyResultDict,
         'CSwapGate': cirq.CSwapGate,
         'CXPowGate': cirq.CXPowGate,
         'CZPowGate': cirq.CZPowGate,
-        'CrossEntropyResult': CrossEntropyResult,
-        'CrossEntropyResultDict': CrossEntropyResultDict,
-        'Circuit': cirq.Circuit,
-        'CircuitOperation': cirq.CircuitOperation,
-        'CliffordState': cirq.CliffordState,
-        'CliffordTableau': cirq.CliffordTableau,
+        'DensePauliString': cirq.DensePauliString,
         'DepolarizingChannel': cirq.DepolarizingChannel,
-        'ConstantQubitNoiseModel': cirq.ConstantQubitNoiseModel,
         'Duration': cirq.Duration,
         'FrozenCircuit': cirq.FrozenCircuit,
         'FSimGate': cirq.FSimGate,
-        'DensePauliString': cirq.DensePauliString,
-        'MutableDensePauliString': cirq.MutableDensePauliString,
-        'MutablePauliString': cirq.MutablePauliString,
-        'ObservableMeasuredResult': cirq.work.ObservableMeasuredResult,
         'GateFamily': cirq.GateFamily,
         'GateOperation': cirq.GateOperation,
         'Gateset': cirq.Gateset,
@@ -109,22 +105,22 @@ def _parallel_gate_op(gate, qubits):
         'MeasurementGate': cirq.MeasurementGate,
         '_MeasurementSpec': cirq.work._MeasurementSpec,
         'Moment': cirq.Moment,
-        '_XEigenState': cirq.value.product_state._XEigenState,  # type: ignore
-        '_YEigenState': cirq.value.product_state._YEigenState,  # type: ignore
-        '_ZEigenState': cirq.value.product_state._ZEigenState,  # type: ignore
+        'MutableDensePauliString': cirq.MutableDensePauliString,
+        'MutablePauliString': cirq.MutablePauliString,
         '_NoNoiseModel': _NoNoiseModel,
         'NamedQubit': cirq.NamedQubit,
         'NamedQid': cirq.NamedQid,
         'NoIdentifierQubit': cirq.testing.NoIdentifierQubit,
+        'ObservableMeasuredResult': cirq.work.ObservableMeasuredResult,
         'OpIdentifier': cirq.OpIdentifier,
-        '_PauliX': cirq.ops.pauli_gates._PauliX,
-        '_PauliY': cirq.ops.pauli_gates._PauliY,
-        '_PauliZ': cirq.ops.pauli_gates._PauliZ,
         'ParamResolver': cirq.ParamResolver,
         'ParallelGate': cirq.ParallelGate,
         'ParallelGateFamily': cirq.ParallelGateFamily,
         'PauliMeasurementGate': cirq.PauliMeasurementGate,
         'PauliString': cirq.PauliString,
+        '_PauliX': cirq.ops.pauli_gates._PauliX,
+        '_PauliY': cirq.ops.pauli_gates._PauliY,
+        '_PauliZ': cirq.ops.pauli_gates._PauliZ,
         'PhaseDampingChannel': cirq.PhaseDampingChannel,
         'PhaseFlipChannel': cirq.PhaseFlipChannel,
         'PhaseGradientGate': cirq.PhaseGradientGate,
@@ -132,39 +128,44 @@ def _parallel_gate_op(gate, qubits):
         'PhasedISwapPowGate': cirq.PhasedISwapPowGate,
         'PhasedXPowGate': cirq.PhasedXPowGate,
         'PhasedXZGate': cirq.PhasedXZGate,
-        'StatePreparationChannel': cirq.StatePreparationChannel,
+        'ProductState': cirq.ProductState,
         'ProjectorString': cirq.ProjectorString,
         'ProjectorSum': cirq.ProjectorSum,
-        'RandomGateChannel': cirq.RandomGateChannel,
+        'QasmUGate': cirq.circuits.qasm_output.QasmUGate,
+        '_QubitAsQid': raw_types._QubitAsQid,
         'QuantumFourierTransformGate': cirq.QuantumFourierTransformGate,
+        'RandomGateChannel': cirq.RandomGateChannel,
         'RepetitionsStoppingCriteria': cirq.work.RepetitionsStoppingCriteria,
         'ResetChannel': cirq.ResetChannel,
+        'Result': cirq.Result,
+        'Rx': cirq.Rx,
+        'Ry': cirq.Ry,
+        'Rz': cirq.Rz,
         'SingleQubitCliffordGate': cirq.SingleQubitCliffordGate,
         'SingleQubitPauliStringGateOperation': cirq.SingleQubitPauliStringGateOperation,
         'SingleQubitReadoutCalibrationResult': cirq.experiments.SingleQubitReadoutCalibrationResult,
         'StabilizerStateChForm': cirq.StabilizerStateChForm,
+        'StatePreparationChannel': cirq.StatePreparationChannel,
         'SwapPowGate': cirq.SwapPowGate,
         'SymmetricalQidPair': cirq.SymmetricalQidPair,
         'TaggedOperation': cirq.TaggedOperation,
         'TiltedSquareLattice': cirq.TiltedSquareLattice,
         'TrialResult': cirq.Result,  # keep support for Cirq < 0.11.
-        'Result': cirq.Result,
-        'Rx': cirq.Rx,
-        'Ry': cirq.Ry,
-        'Rz': cirq.Rz,
         '_UnconstrainedDevice': cirq.devices.unconstrained_device._UnconstrainedDevice,
         'VarianceStoppingCriteria': cirq.work.VarianceStoppingCriteria,
         'VirtualTag': cirq.VirtualTag,
         'WaitGate': cirq.WaitGate,
-        '_QubitAsQid': raw_types._QubitAsQid,
         # The formatter keeps putting this back
         # pylint: disable=line-too-long
         'XEBPhasedFSimCharacterizationOptions': cirq.experiments.XEBPhasedFSimCharacterizationOptions,
         # pylint: enable=line-too-long
+        '_XEigenState': cirq.value.product_state._XEigenState,  # type: ignore
         'XPowGate': cirq.XPowGate,
         'XXPowGate': cirq.XXPowGate,
+        '_YEigenState': cirq.value.product_state._YEigenState,  # type: ignore
         'YPowGate': cirq.YPowGate,
         'YYPowGate': cirq.YYPowGate,
+        '_ZEigenState': cirq.value.product_state._ZEigenState,  # type: ignore
         'ZPowGate': cirq.ZPowGate,
         'ZZPowGate': cirq.ZZPowGate,
         # Old types, only supported for backwards-compatibility


Test patch:
diff --git a/cirq-core/cirq/protocols/json_test_data/QasmUGate.json b/cirq-core/cirq/protocols/json_test_data/QasmUGate.json
new file mode 100644
--- /dev/null
+++ b/cirq-core/cirq/protocols/json_test_data/QasmUGate.json
@@ -0,0 +1,6 @@
+{
+    "cirq_type": "QasmUGate",
+    "theta": 0.1,
+    "phi": 0.2,
+    "lmda": 0.3
+  }
\ No newline at end of file
diff --git a/cirq-core/cirq/protocols/json_test_data/QasmUGate.repr b/cirq-core/cirq/protocols/json_test_data/QasmUGate.repr
new file mode 100644
--- /dev/null
+++ b/cirq-core/cirq/protocols/json_test_data/QasmUGate.repr
@@ -0,0 +1 @@
+cirq.circuits.qasm_output.QasmUGate(0.1, 0.2, 0.3)
\ No newline at end of file