Repository: quantumlib/Cirq

Issue:
cirq.Simulator doesn't support inverse gates that use sympy arguments for gates that don't define __pow__ 
Simulating the inverse of a gate that uses sympy Symbols as arguments causes a "Simulator doesn't support" Type Error, if the gate inherits its __pow__ magic method from `cirq.Gate`. Of particular note is the `cirq.PhasedFSimGate`, which [decomposes](https://github.com/quantumlib/Cirq/blob/782c14e0cc821794d21e82f30df8e6998f4b9992/cirq-core/cirq/ops/fsim_gate.py#L407) into the `cirq.FSimGate` and some `cirq.Z` gates. The `cirq.FSimGate` has it's own __pow__ method implemented, and can correctly be inverted and simulated when using sympy arguments. It seems likely that somewhere in the pipeline, the [_InverseCompositeGate](https://github.com/quantumlib/Cirq/blob/782c14e0cc821794d21e82f30df8e6998f4b9992/cirq-core/cirq/ops/raw_types.py#L319) produced by `cirq.Gate`'s __pow__ is being rejected as an unsupported gate instead of being decomposed, due to it's use of sympy arguments. @tanujkhattar, can you provide more insight on this? 

Replication: Create a circuit with an inverse PhasedFSimGate that uses sympy arguments and attempt to simulate it. This issue doesn't occur with FSimGate, which is the primary component of PhasedFSimGate's decomposition. 
```
import cirq
import sympy
simulator = cirq.Simulator()
qubit0, qubit1 = cirq.GridQubit(0,0), cirq.GridQubit(0,1)
theta, phi = sympy.Symbol('theta'), sympy.Symbol('phi')
for gate in [cirq.FSimGate(theta, phi), cirq.PhasedFSimGate(theta, phi)]: 
    for exponent in [1, -1]: 
        circuit = cirq.Circuit()
        circuit.append(pow(gate, exponent).on(qubit0, qubit1))
        results = simulator.simulate(circuit, param_resolver={'theta': 0, 'phi': 0})
        print(results)
```

Output log
<details>

```
measurements: (no measurements)
output vector: |00⟩
measurements: (no measurements)
output vector: |00⟩
measurements: (no measurements)
output vector: |00⟩
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/usr/local/lib/python3.7/dist-packages/cirq/sim/simulator_base.py in _core_iterator(self, circuit, sim_state, all_measurements_are_terminal)
    220                     # Simulate the operation
--> 221                     protocols.act_on(op, sim_state)
    222                 except TypeError:

11 frames
TypeError: Can't simulate operations that don't implement SupportsUnitary, SupportsConsistentApplyUnitary, SupportsMixture or is a measurement: (cirq.Z**sympy.Mul(sympy.Float('0.5', precision=53), sympy.Pow(sympy.pi, sympy.Integer(-1)), sympy.Symbol('phi'))).on(cirq.GridQubit(0, 1))

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
/usr/local/lib/python3.7/dist-packages/cirq/sim/simulator_base.py in _core_iterator(self, circuit, sim_state, all_measurements_are_terminal)
    221                     protocols.act_on(op, sim_state)
    222                 except TypeError:
--> 223                     raise TypeError(f"{self.__class__.__name__} doesn't support {op!r}")
    224 
    225             step_result = self._create_step_result(sim_state)

TypeError: Simulator doesn't support (cirq.PhasedFSimGate(theta=sympy.Symbol('theta'), zeta=sympy.Symbol('phi'), chi=0.0, gamma=0.0, phi=0.0)**-1).on(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))
```

</details>

**Cirq version**
0.14.0.dev20211110190326



Discussion:

Resolving patch:
diff --git a/cirq-core/cirq/ops/raw_types.py b/cirq-core/cirq/ops/raw_types.py
--- a/cirq-core/cirq/ops/raw_types.py
+++ b/cirq-core/cirq/ops/raw_types.py
@@ -819,6 +819,19 @@ def _has_unitary_(self):
             for op in protocols.decompose_once_with_qubits(self._original, qubits)
         )
 
+    def _is_parameterized_(self) -> bool:
+        return protocols.is_parameterized(self._original)
+
+    def _parameter_names_(self) -> AbstractSet[str]:
+        return protocols.parameter_names(self._original)
+
+    def _resolve_parameters_(
+        self, resolver: 'cirq.ParamResolver', recursive: bool
+    ) -> '_InverseCompositeGate':
+        return _InverseCompositeGate(
+            protocols.resolve_parameters(self._original, resolver, recursive)
+        )
+
     def _value_equality_values_(self):
         return self._original
 


Test patch:
diff --git a/cirq-core/cirq/ops/raw_types_test.py b/cirq-core/cirq/ops/raw_types_test.py
--- a/cirq-core/cirq/ops/raw_types_test.py
+++ b/cirq-core/cirq/ops/raw_types_test.py
@@ -698,6 +698,9 @@ def test_tagged_operation_resolves_parameterized_tags(resolve_fn):
 def test_inverse_composite_standards():
     @cirq.value_equality
     class Gate(cirq.Gate):
+        def __init__(self, param: 'cirq.TParamVal'):
+            self._param = param
+
         def _decompose_(self, qubits):
             return cirq.S.on(qubits[0])
 
@@ -708,14 +711,26 @@ def _has_unitary_(self):
             return True
 
         def _value_equality_values_(self):
-            return ()
+            return (self._param,)
 
-        def __repr__(self):
-            return 'C()'
+        def _parameter_names_(self) -> AbstractSet[str]:
+            return cirq.parameter_names(self._param)
 
-    cirq.testing.assert_implements_consistent_protocols(
-        cirq.inverse(Gate()), global_vals={'C': Gate}
-    )
+        def _is_parameterized_(self) -> bool:
+            return cirq.is_parameterized(self._param)
+
+        def _resolve_parameters_(self, resolver: 'cirq.ParamResolver', recursive: bool) -> 'Gate':
+            return Gate(cirq.resolve_parameters(self._param, resolver, recursive))
+
+        def __repr__(self):
+            return f'C({self._param})'
+
+    a = sympy.Symbol("a")
+    g = cirq.inverse(Gate(a))
+    assert cirq.is_parameterized(g)
+    assert cirq.parameter_names(g) == {'a'}
+    assert cirq.resolve_parameters(g, {a: 0}) == Gate(0) ** -1
+    cirq.testing.assert_implements_consistent_protocols(g, global_vals={'C': Gate, 'a': a})
 
 
 def test_tagged_act_on():