Skill Tracer
Skills › Scientific Agent Skills › cirq › transformation.md

transformation.md

150 lines · skills/cirq/references/transformation.md at 5f2bc03

Open on GitHub
1# Circuit transformations
2
3Target: Cirq 1.7.0. The examples below use small unitary circuits so equivalence
4can be checked exactly. For measurement/feed-forward circuits, validate outcome
5semantics separately; a unitary comparison does not apply.
6

Circuit transformations

Target: Cirq 1.7.0. The examples below use small unitary circuits so equivalence can be checked exactly. For measurement/feed-forward circuits, validate outcome semantics separately; a unitary comparison does not apply.

Raw lines 1 to 6
7## Compile and simplify
8
9```python
10import cirq
11import numpy as np
12import networkx as nx
14qubits = list(cirq.LineQubit.range(3))
15q0, q1, q2 = qubits
16circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q2), cirq.T(q1))
17compiled = cirq.optimize_for_target_gateset(circuit, gateset=cirq.CZTargetGateset())
18cirq.testing.assert_allclose_up_to_global_phase(
19 cirq.unitary(circuit), cirq.unitary(compiled), atol=1e-7
22@cirq.transformer
23def optimization_pipeline(circuit, *, context=None):
24 circuit = cirq.merge_single_qubit_gates_to_phxz(circuit, context=context)
25 circuit = cirq.drop_negligible_operations(circuit, context=context, atol=1e-8)
26 circuit = cirq.eject_z(circuit, context=context)
27 return cirq.drop_empty_moments(circuit, context=context)
29optimized = optimization_pipeline(circuit)
30cirq.testing.assert_allclose_up_to_global_phase(
31 cirq.unitary(circuit), cirq.unitary(optimized), atol=1e-7
33print("Moments:", len(circuit), "->", len(optimized))
34```
36Compilation to a gateset does not ensure device connectivity. A validation
37`Gateset` is not necessarily a `CompilationTargetGateset`; choose a compiler
38explicitly, or use the device's advertised `compilation_target_gatesets`. Preserve
39routing maps, then compile and call `device.validate_circuit`.
41Dropping tiny rotations is approximate and its error accumulates; set a tolerance
42appropriate to the full circuit, not just an individual gate. Gate count and
43moment count are useful diagnostics, not a substitute for calibrated duration.

Compile and simplify

import cirq
import numpy as np
import networkx as nx

qubits = list(cirq.LineQubit.range(3))
q0, q1, q2 = qubits
circuit = cirq.Circuit(cirq.H(q0), cirq.CNOT(q0, q2), cirq.T(q1))
compiled = cirq.optimize_for_target_gateset(circuit, gateset=cirq.CZTargetGateset())
cirq.testing.assert_allclose_up_to_global_phase(
    cirq.unitary(circuit), cirq.unitary(compiled), atol=1e-7
)

@cirq.transformer
def optimization_pipeline(circuit, *, context=None):
    circuit = cirq.merge_single_qubit_gates_to_phxz(circuit, context=context)
    circuit = cirq.drop_negligible_operations(circuit, context=context, atol=1e-8)
    circuit = cirq.eject_z(circuit, context=context)
    return cirq.drop_empty_moments(circuit, context=context)

optimized = optimization_pipeline(circuit)
cirq.testing.assert_allclose_up_to_global_phase(
    cirq.unitary(circuit), cirq.unitary(optimized), atol=1e-7
)
print("Moments:", len(circuit), "->", len(optimized))

Compilation to a gateset does not ensure device connectivity. A validation Gateset is not necessarily a CompilationTargetGateset; choose a compiler explicitly, or use the device's advertised compilation_target_gatesets. Preserve routing maps, then compile and call device.validate_circuit.

Dropping tiny rotations is approximate and its error accumulates; set a tolerance appropriate to the full circuit, not just an individual gate. Gate count and moment count are useful diagnostics, not a substitute for calibrated duration.

Raw lines 7 to 44
45## Custom transformers that preserve the circuit
47Do not delete arbitrary Z gates or replace H with Ry(pi/2) alone. The latter needs
48a Z first: matrix multiplication gives Ry(pi/2) Z = H.
50```python
51@cirq.transformer(add_deep_support=True)
52class HToZRy:
53 def __call__(self, circuit, *, context=None):
54 def map_op(op, _):
55 # Only untagged exact H; fractional H powers and controls are unchanged.
56 if op.gate == cirq.H and not op.tags:
57 q = op.qubits[0]
58 return [cirq.Z(q), cirq.ry(np.pi / 2)(q)]
59 return op
61 ignored = () if context is None else context.tags_to_ignore
62 return cirq.map_operations_and_unroll(circuit, map_op, tags_to_ignore=ignored)
64transformed = HToZRy()(circuit)
65cirq.testing.assert_allclose_up_to_global_phase(
66 cirq.unitary(circuit), cirq.unitary(transformed), atol=1e-7
68```

Custom transformers that preserve the circuit

Do not delete arbitrary Z gates or replace H with Ry(pi/2) alone. The latter needs a Z first: matrix multiplication gives Ry(pi/2) Z = H.

@cirq.transformer(add_deep_support=True)
class HToZRy:
    def __call__(self, circuit, *, context=None):
        def map_op(op, _):
            # Only untagged exact H; fractional H powers and controls are unchanged.
            if op.gate == cirq.H and not op.tags:
                q = op.qubits[0]
                return [cirq.Z(q), cirq.ry(np.pi / 2)(q)]
            return op

        ignored = () if context is None else context.tags_to_ignore
        return cirq.map_operations_and_unroll(circuit, map_op, tags_to_ignore=ignored)

transformed = HToZRy()(circuit)
cirq.testing.assert_allclose_up_to_global_phase(
    cirq.unitary(circuit), cirq.unitary(transformed), atol=1e-7
)
Raw lines 45 to 69
70## Analytical two-qubit compilation
72Use the public matrix compiler rather than manually reconstructing KAK
73interaction angles. The input is a 4x4 two-qubit unitary, not a 2x2 matrix.
75```python
76def compile_two_qubit_unitary(unitary, qubits):
77 if len(qubits) != 2 or np.shape(unitary) != (4, 4):
78 raise ValueError("Expected two qubits and a 4x4 unitary")
79 return cirq.Circuit(cirq.two_qubit_matrix_to_cz_operations(
80 qubits[0], qubits[1], unitary, allow_partial_czs=False
81 ))
83matrix = cirq.unitary(cirq.FSimGate(theta=0.31, phi=0.17))
84decomposed = compile_two_qubit_unitary(matrix, [q0, q1])
85cirq.testing.assert_allclose_up_to_global_phase(matrix, cirq.unitary(decomposed), atol=1e-7)
86```
88For custom decomposable gates implement `_decompose_`; confirm the resulting
89unitary against an independent target matrix (for example `cirq.CCNOT`).
90`cirq.decompose` returns operations; wrap them in `cirq.Circuit` when needed.

Analytical two-qubit compilation

Use the public matrix compiler rather than manually reconstructing KAK interaction angles. The input is a 4x4 two-qubit unitary, not a 2x2 matrix.

def compile_two_qubit_unitary(unitary, qubits):
    if len(qubits) != 2 or np.shape(unitary) != (4, 4):
        raise ValueError("Expected two qubits and a 4x4 unitary")
    return cirq.Circuit(cirq.two_qubit_matrix_to_cz_operations(
        qubits[0], qubits[1], unitary, allow_partial_czs=False
    ))

matrix = cirq.unitary(cirq.FSimGate(theta=0.31, phi=0.17))
decomposed = compile_two_qubit_unitary(matrix, [q0, q1])
cirq.testing.assert_allclose_up_to_global_phase(matrix, cirq.unitary(decomposed), atol=1e-7)

For custom decomposable gates implement _decompose_; confirm the resulting unitary against an independent target matrix (for example cirq.CCNOT). cirq.decompose returns operations; wrap them in cirq.Circuit when needed.

Raw lines 70 to 91
92## Routing and final qubit permutations
94```python
95graph = nx.path_graph(qubits)
96router = cirq.RouteCQC(graph)
97routed, initial_map, final_swap_map = router.route_circuit(circuit)
98cirq.testing.assert_circuits_have_same_unitary_given_final_permutation(
99 routed, circuit.transform_qubits(initial_map), final_swap_map
101assert all(len(op.qubits) < 2 or graph.has_edge(*op.qubits)
102 for op in routed.all_operations())
103```
105`initial_map` maps logical inputs to physical qubits; `final_swap_map` records the
106final physical permutation. The final position of logical `q` is
107`final_swap_map[initial_map[q]]`. Preserve this when adding readout after routing
108or comparing states. If readout was already part of the routed circuit, its
109measurement keys/order travel with it; do not blindly permute results again.
111RouteCQC requires multi-qubit operations to have been decomposed to at most two
112qubits (including nested circuit operations as documented). For directed graphs,
113also check the final native gate directions. Manually inserting SWAPs without
114updating subsequent operations changes the algorithm.

Routing and final qubit permutations

graph = nx.path_graph(qubits)
router = cirq.RouteCQC(graph)
routed, initial_map, final_swap_map = router.route_circuit(circuit)
cirq.testing.assert_circuits_have_same_unitary_given_final_permutation(
    routed, circuit.transform_qubits(initial_map), final_swap_map
)
assert all(len(op.qubits) < 2 or graph.has_edge(*op.qubits)
           for op in routed.all_operations())

initial_map maps logical inputs to physical qubits; final_swap_map records the final physical permutation. The final position of logical q is final_swap_map[initial_map[q]]. Preserve this when adding readout after routing or comparing states. If readout was already part of the routed circuit, its measurement keys/order travel with it; do not blindly permute results again.

RouteCQC requires multi-qubit operations to have been decomposed to at most two qubits (including nested circuit operations as documented). For directed graphs, also check the final native gate directions. Manually inserting SWAPs without updating subsequent operations changes the algorithm.

Raw lines 92 to 115
116## Measurement transformations
118- `cirq.drop_terminal_measurements(circuit)` removes eligible terminal
119 measurements, to inspect the pre-measurement state.
120- `cirq.dephase_measurements(circuit)` replaces measurements with dephasing
121 channels for ensemble density-matrix calculations. It does not align or move
122 measurements and does not produce readout samples.
123- Classical controls need `cirq.defer_measurements` before dephasing; deferral
124 introduces ancillas, which changes memory requirements and output support.
126Never remove operations merely because they are after a measurement. Reset,
127feed-forward, and repeated measurements can be essential parts of the algorithm.

Measurement transformations

  • cirq.drop_terminal_measurements(circuit) removes eligible terminal measurements, to inspect the pre-measurement state.
  • cirq.dephase_measurements(circuit) replaces measurements with dephasing channels for ensemble density-matrix calculations. It does not align or move measurements and does not produce readout samples.
  • Classical controls need cirq.defer_measurements before dephasing; deferral introduces ancillas, which changes memory requirements and output support.

Never remove operations merely because they are after a measurement. Reset, feed-forward, and repeated measurements can be essential parts of the algorithm.

Raw lines 116 to 128
129## Google device example, entirely local
131```python
132import cirq_google as cg
134device = cg.Sycamore
135routed, initial_map, final_swap_map = cirq.RouteCQC(
136 device.metadata.nx_graph
137).route_circuit(circuit)
138compiled_for_device = cirq.optimize_for_target_gateset(
139 routed, gateset=cg.SycamoreTargetGateset()
141device.validate_circuit(compiled_for_device)
142```
144The bundled Sycamore model is useful for local topology checks; it does not
145establish access or describe every currently assigned Google processor.
147Sources: [transformers](https://quantumai.google/cirq/transform/transformers),
148[RouteCQC](https://quantumai.google/reference/python/cirq/RouteCQC),
149[two-qubit compilation](https://quantumai.google/reference/python/cirq/two_qubit_matrix_to_cz_operations),
150[dephasing measurements](https://quantumai.google/reference/python/cirq/dephase_measurements).

Google device example, entirely local

import cirq_google as cg

device = cg.Sycamore
routed, initial_map, final_swap_map = cirq.RouteCQC(
    device.metadata.nx_graph
).route_circuit(circuit)
compiled_for_device = cirq.optimize_for_target_gateset(
    routed, gateset=cg.SycamoreTargetGateset()
)
device.validate_circuit(compiled_for_device)

The bundled Sycamore model is useful for local topology checks; it does not establish access or describe every currently assigned Google processor.

Sources: transformers, RouteCQC, two-qubit compilation, dephasing measurements.

Raw lines 129 to 150