Repository: quantumlib/Cirq

Issue:
[cirqflow] Quantum Executable workflow broken because Engine. _serialize_program doesn't support FrozenCircuit
**Description of the issue**
A call to `cg.execute(rt_config, executable_group)` to run circuits on the hardware is currently broken because with a processor that returns a `QuantumEngineSampler`, the `run_sweep` gets forwarded to `cg.Engine` and the following validation check fails:

https://github.com/quantumlib/Cirq/blob/a0497a411f494498778fae25b20011cd0b8b0eae/cirq-google/cirq_google/engine/engine.py#L633

**How to reproduce the issue**
```
cg.execute(rt_config, executable_group)
```

See details for the stack trace. 
<details>

```

TypeError                                 Traceback (most recent call last)
<ipython-input-76-66aab6e69b44> in <module>()
----> 1 rt_config_hdw.processor.get_sampler().run(executables[0].circuit)

4 frames
/usr/local/lib/python3.7/dist-packages/cirq/work/sampler.py in run(self, program, param_resolver, repetitions)
     53             Result for a run.
     54         """
---> 55         return self.run_sweep(program, study.ParamResolver(param_resolver), repetitions)[0]
     56 
     57     def sample(

/usr/local/lib/python3.7/dist-packages/cirq_google/engine/engine_sampler.py in run_sweep(self, program, params, repetitions)
     65                 repetitions=repetitions,
     66                 processor_ids=self._processor_ids,
---> 67                 gate_set=self._gate_set,
     68             )
     69         return job.results()

/usr/local/lib/python3.7/dist-packages/cirq_google/engine/engine.py in run_sweep(self, program, program_id, job_id, params, repetitions, processor_ids, gate_set, program_description, program_labels, job_description, job_labels)
    305             raise ValueError('No gate set provided')
    306         engine_program = self.create_program(
--> 307             program, program_id, gate_set, program_description, program_labels
    308         )
    309         return engine_program.run_sweep(

/usr/local/lib/python3.7/dist-packages/cirq_google/engine/engine.py in create_program(self, program, program_id, gate_set, description, labels)
    511             self.project_id,
    512             program_id,
--> 513             code=self._serialize_program(program, gate_set),
    514             description=description,
    515             labels=labels,

/usr/local/lib/python3.7/dist-packages/cirq_google/engine/engine.py in _serialize_program(self, program, gate_set)
    632     def _serialize_program(self, program: cirq.Circuit, gate_set: Serializer) -> any_pb2.Any:
    633         if not isinstance(program, cirq.Circuit):
--> 634             raise TypeError(f'Unrecognized program type: {type(program)}')
    635         program.device.validate_circuit(program)
    636 

TypeError: Unrecognized program type: <class 'cirq.circuits.frozen_circuit.FrozenCircuit'>

```

</details>

**Cirq version**
0.14.0.dev20211117174531


cc @mpharrigan 


Discussion:

Resolving patch:
diff --git a/cirq-google/cirq_google/engine/engine.py b/cirq-google/cirq_google/engine/engine.py
--- a/cirq-google/cirq_google/engine/engine.py
+++ b/cirq-google/cirq_google/engine/engine.py
@@ -189,7 +189,7 @@ def __str__(self) -> str:
 
     def run(
         self,
-        program: cirq.Circuit,
+        program: cirq.AbstractCircuit,
         program_id: Optional[str] = None,
         job_id: Optional[str] = None,
         param_resolver: cirq.ParamResolver = cirq.ParamResolver({}),
@@ -253,7 +253,7 @@ def run(
 
     def run_sweep(
         self,
-        program: cirq.Circuit,
+        program: cirq.AbstractCircuit,
         program_id: Optional[str] = None,
         job_id: Optional[str] = None,
         params: cirq.Sweepable = None,
@@ -475,7 +475,7 @@ def run_calibration(
 
     def create_program(
         self,
-        program: cirq.Circuit,
+        program: cirq.AbstractCircuit,
         program_id: Optional[str] = None,
         gate_set: Optional[Serializer] = None,
         description: Optional[str] = None,
@@ -629,8 +629,10 @@ def create_calibration_program(
             result_type=ResultType.Calibration,
         )
 
-    def _serialize_program(self, program: cirq.Circuit, gate_set: Serializer) -> any_pb2.Any:
-        if not isinstance(program, cirq.Circuit):
+    def _serialize_program(
+        self, program: cirq.AbstractCircuit, gate_set: Serializer
+    ) -> any_pb2.Any:
+        if not isinstance(program, cirq.AbstractCircuit):
             raise TypeError(f'Unrecognized program type: {type(program)}')
         program.device.validate_circuit(program)
 
diff --git a/cirq-google/cirq_google/engine/engine_sampler.py b/cirq-google/cirq_google/engine/engine_sampler.py
--- a/cirq-google/cirq_google/engine/engine_sampler.py
+++ b/cirq-google/cirq_google/engine/engine_sampler.py
@@ -12,7 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from typing import cast, List, Optional, Sequence, TYPE_CHECKING, Union
+from typing import List, Optional, Sequence, TYPE_CHECKING, Union
 
 import cirq
 from cirq_google import engine
@@ -60,7 +60,7 @@ def run_sweep(
             )
         else:
             job = self._engine.run_sweep(
-                program=cast(cirq.Circuit, program),
+                program=program,
                 params=params,
                 repetitions=repetitions,
                 processor_ids=self._processor_ids,
diff --git a/cirq-google/cirq_google/serialization/serializable_gate_set.py b/cirq-google/cirq_google/serialization/serializable_gate_set.py
--- a/cirq-google/cirq_google/serialization/serializable_gate_set.py
+++ b/cirq-google/cirq_google/serialization/serializable_gate_set.py
@@ -129,7 +129,7 @@ def serialize(
         if msg is None:
             msg = v2.program_pb2.Program()
         msg.language.gate_set = self.name
-        if isinstance(program, cirq.Circuit):
+        if isinstance(program, cirq.AbstractCircuit):
             constants: Optional[List[v2.program_pb2.Constant]] = [] if use_constants else None
             raw_constants: Optional[Dict[Any, int]] = {} if use_constants else None
             self._serialize_circuit(


Test patch:
diff --git a/cirq-google/cirq_google/engine/engine_sampler_test.py b/cirq-google/cirq_google/engine/engine_sampler_test.py
--- a/cirq-google/cirq_google/engine/engine_sampler_test.py
+++ b/cirq-google/cirq_google/engine/engine_sampler_test.py
@@ -21,14 +21,18 @@
 import cirq_google.engine.client.quantum
 
 
-def test_run_circuit():
+@pytest.mark.parametrize('circuit', [cirq.Circuit(), cirq.FrozenCircuit()])
+def test_run_circuit(circuit):
     engine = mock.Mock()
     sampler = cg.QuantumEngineSampler(engine=engine, processor_id='tmp', gate_set=cg.XMON)
-    circuit = cirq.Circuit()
     params = [cirq.ParamResolver({'a': 1})]
     sampler.run_sweep(circuit, params, 5)
     engine.run_sweep.assert_called_with(
-        gate_set=cg.XMON, params=params, processor_ids=['tmp'], program=circuit, repetitions=5
+        gate_set=cg.XMON,
+        params=params,
+        processor_ids=['tmp'],
+        program=circuit,
+        repetitions=5,
     )
 
 
diff --git a/cirq-google/cirq_google/engine/engine_test.py b/cirq-google/cirq_google/engine/engine_test.py
--- a/cirq-google/cirq_google/engine/engine_test.py
+++ b/cirq-google/cirq_google/engine/engine_test.py
@@ -34,7 +34,7 @@
 )
 
 
-_CIRCUIT2 = cirq.Circuit(
+_CIRCUIT2 = cirq.FrozenCircuit(
     cirq.Y(cirq.GridQubit(5, 2)) ** 0.5, cirq.measure(cirq.GridQubit(5, 2), key='result')
 )
 
diff --git a/cirq-google/cirq_google/serialization/serializable_gate_set_test.py b/cirq-google/cirq_google/serialization/serializable_gate_set_test.py
--- a/cirq-google/cirq_google/serialization/serializable_gate_set_test.py
+++ b/cirq-google/cirq_google/serialization/serializable_gate_set_test.py
@@ -155,8 +155,7 @@ def test_is_supported_operation_can_serialize_predicate():
 def test_serialize_deserialize_circuit():
     q0 = cirq.GridQubit(1, 1)
     q1 = cirq.GridQubit(1, 2)
-    circuit = cirq.Circuit(cirq.X(q0), cirq.X(q1), cirq.X(q0))
-
+    circuit_base = cirq.Circuit(cirq.X(q0), cirq.X(q1), cirq.X(q0))
     proto = v2.program_pb2.Program(
         language=v2.program_pb2.Language(arg_function_language='', gate_set='my_gate_set'),
         circuit=v2.program_pb2.Circuit(
@@ -174,8 +173,9 @@ def test_serialize_deserialize_circuit():
             ],
         ),
     )
-    assert proto == MY_GATE_SET.serialize(circuit)
-    assert MY_GATE_SET.deserialize(proto) == circuit
+    for circuit in [circuit_base, circuit_base.freeze()]:
+        assert proto == MY_GATE_SET.serialize(circuit)
+        assert MY_GATE_SET.deserialize(proto) == circuit
 
 
 def test_serialize_deserialize_circuit_with_tokens():