Skip to content

Add a symbolize transformer #7307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@
single_qubit_matrix_to_phxz as single_qubit_matrix_to_phxz,
single_qubit_op_to_framed_phase_form as single_qubit_op_to_framed_phase_form,
stratified_circuit as stratified_circuit,
symbolize_single_qubit_gates_by_indexed_tags as symbolize_single_qubit_gates_by_indexed_tags,
synchronize_terminal_measurements as synchronize_terminal_measurements,
TRANSFORMER as TRANSFORMER,
TransformerContext as TransformerContext,
Expand Down
5 changes: 5 additions & 0 deletions cirq-core/cirq/transformers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@
unroll_circuit_op_greedy_frontier as unroll_circuit_op_greedy_frontier,
)

from cirq.transformers.symbolize import (
SymbolizeTag as SymbolizeTag,
symbolize_single_qubit_gates_by_indexed_tags as symbolize_single_qubit_gates_by_indexed_tags,
)

from cirq.transformers.gauge_compiling import (
CZGaugeTransformer as CZGaugeTransformer,
ConstantGauge as ConstantGauge,
Expand Down
101 changes: 101 additions & 0 deletions cirq-core/cirq/transformers/symbolize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Copyright 2025 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Transformers that symbolizes operations."""

import re
from typing import Hashable, Optional, TYPE_CHECKING

import attr
import sympy

from cirq import ops
from cirq.transformers import transformer_api, transformer_primitives

if TYPE_CHECKING:
import cirq


@attr.frozen
class SymbolizeTag:
prefix: str


@transformer_api.transformer
def symbolize_single_qubit_gates_by_indexed_tags(
circuit: 'cirq.AbstractCircuit',
*,
context: Optional['cirq.TransformerContext'] = None,
symbolize_tag: SymbolizeTag = SymbolizeTag(prefix="TO-PHXZ"),
) -> 'cirq.Circuit':
"""Symbolizes single qubit operations by indexed tags prefixed by symbolize_tag.prefix.

Example:
>>> from cirq import transformers
>>> q0, q1 = cirq.LineQubit.range(2)
>>> c = cirq.Circuit(
... cirq.X(q0).with_tags("phxz_0"),
... cirq.CZ(q0,q1),
... cirq.Y(q0).with_tags("phxz_1"),
... cirq.X(q0))
>>> print(c)
0: ───X[phxz_0]───@───Y[phxz_1]───X───
1: ───────────────@───────────────────
>>> new_circuit = cirq.symbolize_single_qubit_gates_by_indexed_tags(
... c, symbolize_tag=transformers.SymbolizeTag(prefix="phxz"))
>>> print(new_circuit)
0: ───PhXZ(a=a0,x=x0,z=z0)───@───PhXZ(a=a1,x=x1,z=z1)───X───
1: ──────────────────────────@──────────────────────────────

Args:
circuit: Input circuit to apply the transformations on. The input circuit is not mutated.
context: `cirq.TransformerContext` storing common configurable options for transformers.
symbolize_tag: The tag info used to symbolize the phxz gate. Prefix is required.

Returns:
Copy of the transformed input circuit.
"""

if not symbolize_tag.prefix:
raise ValueError("Tag prefix cannot be empty to symbolize phxz gates.")

def _map_func(op: 'cirq.Operation', _):
"""Maps an op with tag `{tag_prefix}_i` to a symbolzied `PhasedXZGate(xi,zi,ai)`."""
tags: set[Hashable] = set(op.tags)
tag_id: None | int = None
for tag in tags:
if re.fullmatch(f"{symbolize_tag.prefix}_\\d+", str(tag)):
if tag_id is None:
tag_id = int(str(tag).rsplit("_", maxsplit=-1)[-1])
else:
raise ValueError(f"Multiple tags are prefixed with {symbolize_tag.prefix}.")
if tag_id is None:
return op
tags.remove(f"{symbolize_tag.prefix}_{tag_id}")
phxz_params = {
"x_exponent": sympy.Symbol(f"x{tag_id}"),
"z_exponent": sympy.Symbol(f"z{tag_id}"),
"axis_phase_exponent": sympy.Symbol(f"a{tag_id}"),
}

return ops.PhasedXZGate(**phxz_params).on(*op.qubits).with_tags(*tags)

return transformer_primitives.map_operations(
circuit.freeze(),
_map_func,
deep=context.deep if context else False,
tags_to_ignore=context.tags_to_ignore if context else [],
).unfreeze(copy=False)
60 changes: 60 additions & 0 deletions cirq-core/cirq/transformers/symbolize_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2025 The Cirq Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest
import sympy

import cirq
from cirq.transformers.symbolize import SymbolizeTag


def test_symbolize_single_qubit_gates_by_indexed_tags_success():
q = cirq.NamedQubit("a")
input_circuit = cirq.Circuit(
cirq.X(q).with_tags("phxz_1"), cirq.Y(q).with_tags("tag1"), cirq.Z(q).with_tags("phxz_0")
)
output_circuit = cirq.symbolize_single_qubit_gates_by_indexed_tags(
input_circuit, symbolize_tag=SymbolizeTag(prefix="phxz")
)
cirq.testing.assert_same_circuits(
output_circuit,
cirq.Circuit(
cirq.PhasedXZGate(
x_exponent=sympy.Symbol("x1"),
z_exponent=sympy.Symbol("z1"),
axis_phase_exponent=sympy.Symbol("a1"),
).on(q),
cirq.Y(q).with_tags("tag1"),
cirq.PhasedXZGate(
x_exponent=sympy.Symbol("x0"),
z_exponent=sympy.Symbol("z0"),
axis_phase_exponent=sympy.Symbol("a0"),
).on(q),
),
)


def test_symbolize_single_qubit_gates_by_indexed_tags_multiple_tags():
q = cirq.NamedQubit("a")
input_circuit = cirq.Circuit(cirq.X(q).with_tags("TO-PHXZ_0", "TO-PHXZ_2"))

with pytest.raises(ValueError, match="Multiple tags are prefixed with TO-PHXZ."):
cirq.symbolize_single_qubit_gates_by_indexed_tags(input_circuit)


def test_symbolize_single_qubit_gates_by_indexed_tags_empty_prefix():
with pytest.raises(ValueError, match="Tag prefix cannot be empty to symbolize phxz gates."):
cirq.symbolize_single_qubit_gates_by_indexed_tags(
cirq.Circuit(), symbolize_tag=SymbolizeTag("")
)