Repository: quantumlib/Cirq

Issue:
sqrtISWAP serializer doesn't like rz, ry, rx
The `cirq.rz` helper function ([cirq.ops.common_gates:1045](https://github.com/quantumlib/Cirq/blob/d998b3afe20dd6783e00dcc2590ba0f162b95af7/cirq/ops/common_gates.py#L1045)) converts inputs to half turns (radians?) by dividing by sympy.pi for parametrized inputs, but this division is actually expressed as `sympy.Mul(sympy.Pow(sympy.pi, sympy.Integer(-1))`

The `sympy.Pow` part of that expression then causes problems with serializers that expect only `Mul` and `Add` in Sympy expressions (i.e. [cirq.google.arg_func_langs:143](https://github.com/quantumlib/Cirq/blob/d998b3afe20dd6783e00dcc2590ba0f162b95af7/cirq/google/arg_func_langs.py#L143)) 

Would it make more sense to convert all forms of `rz` inputs (symbolic or not) with division by `np.pi`?

reproducing code:
```
# This introduces `Pow`:
contains_pow = sympy.Symbol('x') / sympy.pi
for arg in contains_pow.args:
  print(arg, type(arg))
>>> x <class 'sympy.core.symbol.Symbol'>
>>> 1/pi <class 'sympy.core.power.Pow'>

# This casts to float:
no_pow = sympy.Symbol('x') / np.pi
for arg in no_pow.args:
  print(arg, type(arg))

>>> 0.318309886183791 <class 'sympy.core.numbers.Float'>
>>> x <class 'sympy.core.symbol.Symbol'>

# Serializer chokes up on Pow due to sympy.pi division
q0 = cirq.GridQubit(0, 0)
c0 = cirq.Circuit(cirq.rz(sympy.Symbol('x')).on(q0))
c1 = cirq.Circuit(cirq.ZPowGate(exponent=sympy.Symbol('x')/np.pi, global_shift=-0.5).on(q0) )

cirq.google.SQRT_ISWAP_GATESET.serialize(c0) # Throws ValueError: Unrecognized arg type: <class 'sympy.core.power.Pow'>
cirq.google.SQRT_ISWAP_GATESET.serialize(c1) # Works fine
```

@dstrain115 


Discussion:
Was there a reason not to support `Pow` in the language (or division for that matter)?  Worry about divide by zero?

Resolving patch:
diff --git a/cirq/google/arg_func_langs.py b/cirq/google/arg_func_langs.py
--- a/cirq/google/arg_func_langs.py
+++ b/cirq/google/arg_func_langs.py
@@ -31,11 +31,12 @@
 SUPPORTED_FUNCTIONS_FOR_LANGUAGE: Dict[Optional[str], FrozenSet[str]] = {
     '': frozenset(),
     'linear': frozenset({'add', 'mul'}),
+    'exp': frozenset({'add', 'mul', 'pow'}),
     # None means any. Is used when inferring the language during serialization.
-    None: frozenset({'add', 'mul'}),
+    None: frozenset({'add', 'mul', 'pow'}),
 }
 
-SUPPORTED_SYMPY_OPS = (sympy.Symbol, sympy.Add, sympy.Mul)
+SUPPORTED_SYMPY_OPS = (sympy.Symbol, sympy.Add, sympy.Mul, sympy.Pow)
 
 # Argument types for gates.
 ARG_LIKE = Union[int, float, List[bool], str, sympy.Symbol, sympy.Add, sympy.
@@ -47,6 +48,7 @@
 LANGUAGE_ORDER = [
     '',
     'linear',
+    'exp',
 ]
 
 
@@ -85,6 +87,10 @@ def _function_languages_from_arg(arg_proto: v2.program_pb2.Arg
             yield 'linear'
             for a in arg_proto.func.args:
                 yield from _function_languages_from_arg(a)
+        if arg_proto.func.type in ['pow']:
+            yield 'exp'
+            for a in arg_proto.func.args:
+                yield from _function_languages_from_arg(a)
 
 
 def _arg_to_proto(value: ARG_LIKE,
@@ -144,6 +150,12 @@ def check_support(func_type: str) -> str:
             _arg_to_proto(arg,
                           arg_function_language=arg_function_language,
                           out=msg.func.args.add())
+    elif isinstance(value, sympy.Pow):
+        msg.func.type = check_support('pow')
+        for arg in value.args:
+            _arg_to_proto(arg,
+                          arg_function_language=arg_function_language,
+                          out=msg.func.args.add())
     else:
         raise ValueError(f'Unrecognized arg type: {type(value)}')
 
@@ -221,6 +233,14 @@ def _arg_from_proto(
                 for a in func.args
             ])
 
+        if func.type == 'pow':
+            return sympy.Pow(*[
+                _arg_from_proto(a,
+                                arg_function_language=arg_function_language,
+                                required_arg_name='A power argument')
+                for a in func.args
+            ])
+
     if required_arg_name is not None:
         raise ValueError(
             f'{required_arg_name} is missing or has an unrecognized '


Test patch:
diff --git a/cirq/google/arg_func_langs_test.py b/cirq/google/arg_func_langs_test.py
--- a/cirq/google/arg_func_langs_test.py
+++ b/cirq/google/arg_func_langs_test.py
@@ -74,6 +74,16 @@
             }]
         }
     }),
+    ('exp', sympy.Symbol('x')**sympy.Symbol('y'), {
+        'func': {
+            'type': 'pow',
+            'args': [{
+                'symbol': 'x'
+            }, {
+                'symbol': 'y'
+            }]
+        }
+    }),
 ])
 def test_correspondence(min_lang: str, value: ARG_LIKE,
                         proto: v2.program_pb2.Arg):
@@ -170,3 +180,7 @@ def test_infer_language():
     c_empty = cirq.Circuit(cirq.X(q)**b)
     packed = cirq.google.XMON.serialize(c_empty)
     assert packed.language.arg_function_language == ''
+
+    c_exp = cirq.Circuit(cirq.X(q)**(b**a))
+    packed = cirq.google.XMON.serialize(c_exp)
+    assert packed.language.arg_function_language == 'exp'