Repository: quantumlib/Cirq

Issue:
Deep map_operations doesn't preserve CircuitOperation tags
**Description of the issue**
When calling `map_operations` with `deep=True`, tags on FrozenCircuits inside CircuitOperations are not preserved.

**How to reproduce the issue**

```python

c = cirq.Circuit.from_moments(cirq.FrozenCircuit().with_tags("asdf"))
print(c[0].operations[0].circuit.tags)

mapped = cirq.map_operations(c, lambda x, idx: x, deep=True)[0].operations[0].circuit.tags
print(mapped[0].operations[0].circuit.tags)

```

<details>

put long logs in details blocks *like this*

</details>

**Cirq version**
'1.4.0.dev20240220200443'




Discussion:
I think I see the issue. In this line, https://github.com/quantumlib/Cirq/blob/main/cirq-core/cirq/transformers/transformer_primitives.py#L174, we need to add `with_tags(*op.untagged.circuit.tags)`. Unfortunately I'm not setup to develop on Cirq at the moment...

Resolving patch:
diff --git a/cirq-core/cirq/transformers/transformer_primitives.py b/cirq-core/cirq/transformers/transformer_primitives.py
--- a/cirq-core/cirq/transformers/transformer_primitives.py
+++ b/cirq-core/cirq/transformers/transformer_primitives.py
@@ -53,12 +53,9 @@ def _to_target_circuit_type(
 
 
 def _create_target_circuit_type(ops: ops.OP_TREE, target_circuit: CIRCUIT_TYPE) -> CIRCUIT_TYPE:
-    return cast(
-        CIRCUIT_TYPE,
-        circuits.Circuit(ops)
-        if isinstance(target_circuit, circuits.Circuit)
-        else circuits.FrozenCircuit(ops),
-    )
+    if isinstance(target_circuit, circuits.FrozenCircuit):
+        return cast(CIRCUIT_TYPE, circuits.FrozenCircuit(ops).with_tags(*target_circuit.tags))
+    return cast(CIRCUIT_TYPE, circuits.Circuit(ops))
 
 
 def map_moments(


Test patch:
diff --git a/cirq-core/cirq/transformers/transformer_primitives_test.py b/cirq-core/cirq/transformers/transformer_primitives_test.py
--- a/cirq-core/cirq/transformers/transformer_primitives_test.py
+++ b/cirq-core/cirq/transformers/transformer_primitives_test.py
@@ -205,6 +205,33 @@ def map_func(op: cirq.Operation, _: int) -> cirq.OP_TREE:
 # pylint: enable=line-too-long
 
 
+@pytest.mark.parametrize("deep", [False, True])
+def test_map_operations_preserves_circuit_tags(deep: bool) -> None:
+    tag = "should be preserved"
+
+    def func(op: cirq.Operation, idx: int) -> cirq.Operation:
+        return cirq.Y(op.qubits[0]) if op.gate == cirq.X else op
+
+    x = cirq.X(cirq.q(0))
+    circuit = cirq.FrozenCircuit.from_moments(x, cirq.FrozenCircuit(x)).with_tags(tag)
+    mapped = cirq.map_operations(circuit, func, deep=deep)
+
+    assert mapped.tags == (tag,)
+
+
+def test_map_operations_deep_preserves_subcircuit_tags():
+    tag = "should be preserved"
+
+    def func(op: cirq.Operation, idx: int) -> cirq.Operation:
+        return cirq.Y(op.qubits[0]) if op.gate == cirq.X else op
+
+    x = cirq.X(cirq.q(0))
+    circuit = cirq.FrozenCircuit.from_moments(x, cirq.FrozenCircuit(x).with_tags(tag))
+    mapped = cirq.map_operations(circuit, func, deep=True)
+
+    assert mapped[1].operations[0].circuit.tags == (tag,)
+
+
 def test_map_operations_deep_respects_tags_to_ignore():
     q = cirq.LineQubit.range(2)
     c_nested = cirq.FrozenCircuit(cirq.CX(*q), cirq.CX(*q).with_tags("ignore"), cirq.CX(*q))