Repository: quantumlib/Cirq

Issue:
MergeInteractions optimizer doesn't behave well with TaggedOperations
### Description of the issue
`MergeInteractions` checks for `isinstance(op, GateOperation)` if `allowe_partial_czs = False` and hence it doesn't behave well with `TaggedOperations`. See the example below. 

### How to reproduce the issue

```python
q = cirq.LineQubit.range(2)
ops = []
for num_czs in [1, 2]:
    circuit = cirq.Circuit([(cirq.CZ(*q) ** 0.5).with_tags("mytag")] * num_czs)
    cirq.MergeInteractions(allow_partial_czs=False).optimize_circuit(circuit)
    print(circuit)
```
```python
# MergeInteractions fails to decompose a single instance of tagged partial CZ because of isinstance(op, GateOperation) check.
0: ───@['mytag']───
      │
1: ───@^0.5────────

# More than 1 occurrence in the trivial case works fine because new_op_count < old_op_count
0: ───Z^0.75───X^0.5───S^-1───Y^-0.5───@───S^-1───Y^0.5───Y^0.5───T────────
                                       │
1: ───T────────X^0.5───S^-1───Y^-0.5───@───S^-1───Y^0.5───Y^0.5───Z^0.75───
```

### Long Term Solution [needs discussion] 
- We should seriously work towards `cirq.match(op, GateType)`(#2536)  and replace all `isinstance()` checks with `cirq.match` which understands how to handle `ControlledOperations`, `TaggedOperations` etc. Right now, it's very easy for such inconsistencies to exist because of
  - Lack of backwards compatibility while introducing new operation classes like `TaggedOperation`
  - Easy to make mistakes due to the intricate details involved. 
- Related issues #4152, #4193

**Cirq version**
0.12.0.dev

Part of #3242 



Discussion:

Resolving patch:
diff --git a/cirq-core/cirq/optimizers/merge_interactions.py b/cirq-core/cirq/optimizers/merge_interactions.py
--- a/cirq-core/cirq/optimizers/merge_interactions.py
+++ b/cirq-core/cirq/optimizers/merge_interactions.py
@@ -55,16 +55,9 @@ def optimization_at(
 
         switch_to_new = False
         switch_to_new |= any(
-            len(old_op.qubits) == 2 and not isinstance(old_op.gate, ops.CZPowGate)
+            len(old_op.qubits) == 2 and not self._may_keep_old_op(old_op)
             for old_op in old_operations
         )
-        if not self.allow_partial_czs:
-            switch_to_new |= any(
-                isinstance(old_op, ops.GateOperation)
-                and isinstance(old_op.gate, ops.CZPowGate)
-                and old_op.gate.exponent != 1
-                for old_op in old_operations
-            )
 
         # This point cannot be optimized using this method
         if not switch_to_new and old_interaction_count <= 1:
@@ -89,6 +82,13 @@ def optimization_at(
             new_operations=new_operations,
         )
 
+    def _may_keep_old_op(self, old_op: 'cirq.Operation') -> bool:
+        """Returns True if the old two-qubit operation may be left unchanged
+        without decomposition."""
+        if self.allow_partial_czs:
+            return isinstance(old_op.gate, ops.CZPowGate)
+        return isinstance(old_op.gate, ops.CZPowGate) and old_op.gate.exponent == 1
+
     def _op_to_matrix(
         self, op: ops.Operation, qubits: Tuple['cirq.Qid', ...]
     ) -> Optional[np.ndarray]:


Test patch:
diff --git a/cirq-core/cirq/optimizers/merge_interactions_test.py b/cirq-core/cirq/optimizers/merge_interactions_test.py
--- a/cirq-core/cirq/optimizers/merge_interactions_test.py
+++ b/cirq-core/cirq/optimizers/merge_interactions_test.py
@@ -163,6 +163,16 @@ def test_optimizes_single_iswap():
     assert len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 2
 
 
+def test_optimizes_tagged_partial_cz():
+    a, b = cirq.LineQubit.range(2)
+    c = cirq.Circuit((cirq.CZ ** 0.5)(a, b).with_tags('mytag'))
+    assert_optimization_not_broken(c)
+    cirq.MergeInteractions(allow_partial_czs=False).optimize_circuit(c)
+    assert (
+        len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 2
+    ), 'It should take 2 CZ gates to decompose a CZ**0.5 gate'
+
+
 @pytest.mark.parametrize(
     'circuit',
     (