Skip to content

Commit 59b72a1

Browse files
authored
Add cirq.convert_to_target_gateset transformer and cirq.CompilationTargetGateset interface (#5005)
* Add convert_to_target_gateset transformer and CompilationTargetGateset interface * Override validation_operation to not accept intermediate results
1 parent 3517306 commit 59b72a1

10 files changed

+534
-3
lines changed

cirq-core/cirq/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@
355355
from cirq.transformers import (
356356
align_left,
357357
align_right,
358+
CompilationTargetGateset,
358359
compute_cphase_exponents_for_fsim_decomposition,
359360
decompose_clifford_tableau_to_operations,
360361
decompose_cphase_into_two_fsim,
@@ -380,6 +381,7 @@
380381
merge_single_qubit_gates_to_phased_x_and_z,
381382
merge_single_qubit_gates_to_phxz,
382383
merge_single_qubit_moments_to_phxz,
384+
optimize_for_target_gateset,
383385
prepare_two_qubit_state_using_cz,
384386
prepare_two_qubit_state_using_sqrt_iswap,
385387
single_qubit_matrix_to_gates,

cirq-core/cirq/protocols/decompose_protocol.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,11 @@ def decompose(
180180
that doesn't satisfy the given `keep` predicate.
181181
"""
182182

183-
if on_stuck_raise is not _value_error_describing_bad_operation and keep is None:
183+
if (
184+
on_stuck_raise is not _value_error_describing_bad_operation
185+
and on_stuck_raise is not None
186+
and keep is None
187+
):
184188
raise ValueError(
185189
"Must specify 'keep' if specifying 'on_stuck_raise', because it's "
186190
"not possible to get stuck if you don't have a criteria on what's "

cirq-core/cirq/protocols/decompose_protocol_test.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def test_decompose_on_stuck_raise():
182182
_ = cirq.decompose(NoMethod(), keep=lambda _: False)
183183
# Unless there's no operations to be unhappy about.
184184
assert cirq.decompose([], keep=lambda _: False) == []
185+
assert cirq.decompose([], on_stuck_raise=None) == []
185186
# Or you say you're fine.
186187
assert cirq.decompose(no_method, keep=lambda _: False, on_stuck_raise=None) == [no_method]
187188
assert cirq.decompose(no_method, keep=lambda _: False, on_stuck_raise=lambda _: None) == [
@@ -198,8 +199,6 @@ def test_decompose_on_stuck_raise():
198199
)
199200

200201
# There's a nice warning if you specify `on_stuck_raise` but not `keep`.
201-
with pytest.raises(ValueError, match='on_stuck_raise'):
202-
assert cirq.decompose([], on_stuck_raise=None)
203202
with pytest.raises(ValueError, match='on_stuck_raise'):
204203
assert cirq.decompose([], on_stuck_raise=TypeError('x'))
205204

cirq-core/cirq/protocols/json_test_data/spec.py

+2
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@
9292
'ApplyMixtureArgs',
9393
'ApplyUnitaryArgs',
9494
'OperationTarget',
95+
# Abstract base class for creating compilation targets.
96+
'CompilationTargetGateset',
9597
# Circuit optimizers are function-like. Only attributes
9698
# are ignore_failures, tolerance, and other feature flags
9799
'AlignLeft',

cirq-core/cirq/transformers/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
two_qubit_gate_product_tabulation,
4242
)
4343

44+
from cirq.transformers.target_gatesets import (
45+
CompilationTargetGateset,
46+
)
47+
4448
from cirq.transformers.align import align_left, align_right
4549

4650
from cirq.transformers.stratify import stratified_circuit
@@ -49,6 +53,8 @@
4953

5054
from cirq.transformers.eject_phased_paulis import eject_phased_paulis
5155

56+
from cirq.transformers.optimize_for_target_gateset import optimize_for_target_gateset
57+
5258
from cirq.transformers.drop_empty_moments import drop_empty_moments
5359

5460
from cirq.transformers.drop_negligible_operations import drop_negligible_operations
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# Copyright 2022 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Transformers to rewrite a circuit using gates from a given target gateset."""
16+
17+
from typing import Optional, Callable, TYPE_CHECKING
18+
19+
from cirq.protocols import decompose_protocol as dp
20+
from cirq.transformers import transformer_api, transformer_primitives
21+
22+
if TYPE_CHECKING:
23+
import cirq
24+
25+
26+
def _create_on_stuck_raise_error(gateset: 'cirq.Gateset'):
27+
def _value_error_describing_bad_operation(op: 'cirq.Operation') -> ValueError:
28+
return ValueError(f"Unable to convert {op} to target gateset {gateset!r}")
29+
30+
return _value_error_describing_bad_operation
31+
32+
33+
@transformer_api.transformer
34+
def _decompose_operations_to_target_gateset(
35+
circuit: 'cirq.AbstractCircuit',
36+
*,
37+
context: Optional['cirq.TransformerContext'] = None,
38+
gateset: Optional['cirq.Gateset'] = None,
39+
decomposer: Callable[['cirq.Operation', int], dp.DecomposeResult] = lambda *_: NotImplemented,
40+
ignore_failures: bool = True,
41+
) -> 'cirq.Circuit':
42+
"""Decomposes every operation to `gateset` using `cirq.decompose` and `decomposer`.
43+
44+
This transformer attempts to decompose every operation `op` in the given circuit to `gateset`
45+
using `cirq.decompose` protocol with `decomposer` used as an intercepting decomposer. This
46+
ensures that `op` is recursively decomposed using implicitly defined known decompositions
47+
(eg: in `_decompose_` magic method on the gaet class) till either `decomposer` knows how to
48+
decompose the given operation or the given operation belongs to `gateset`.
49+
50+
Args:
51+
circuit: Input circuit to transform. It will not be modified.
52+
context: `cirq.TransformerContext` storing common configurable options for transformers.
53+
gateset: Target gateset, which the decomposed operations should belong to.
54+
decomposer: A callable type which accepts an (operation, moment_index) and returns
55+
- An equivalent `cirq.OP_TREE` implementing `op` using gates from `gateset`.
56+
- `None` or `NotImplemented` if does not know how to decompose a given `op`.
57+
ignore_failures: If set, operations that fail to convert are left unchanged. If not set,
58+
conversion failures raise a ValueError.
59+
60+
Returns:
61+
An equivalent circuit containing gates accepted by `gateset`.
62+
63+
Raises:
64+
ValueError: If any input operation fails to convert and `ignore_failures` is False.
65+
"""
66+
67+
def map_func(op: 'cirq.Operation', moment_index: int):
68+
return dp.decompose(
69+
op,
70+
intercepting_decomposer=lambda o: decomposer(o, moment_index),
71+
keep=gateset.validate if gateset else None,
72+
on_stuck_raise=(
73+
None
74+
if ignore_failures or gateset is None
75+
else _create_on_stuck_raise_error(gateset)
76+
),
77+
)
78+
79+
return transformer_primitives.map_operations_and_unroll(
80+
circuit, map_func, tags_to_ignore=context.tags_to_ignore if context else ()
81+
).unfreeze(copy=False)
82+
83+
84+
@transformer_api.transformer
85+
def optimize_for_target_gateset(
86+
circuit: 'cirq.AbstractCircuit',
87+
*,
88+
context: Optional['cirq.TransformerContext'] = None,
89+
gateset: Optional['cirq.CompilationTargetGateset'] = None,
90+
ignore_failures: bool = True,
91+
) -> 'cirq.Circuit':
92+
"""Transforms the given circuit into an equivalent circuit using gates accepted by `gateset`.
93+
94+
1. Run all `gateset.preprocess_transformers`
95+
2. Convert operations using built-in cirq decompose + `gateset.decompose_to_target_gateset`.
96+
3. Run all `gateset.postprocess_transformers`
97+
98+
Args:
99+
circuit: Input circuit to transform. It will not be modified.
100+
context: `cirq.TransformerContext` storing common configurable options for transformers.
101+
gateset: Target gateset, which should be an instance of `cirq.CompilationTargetGateset`.
102+
ignore_failures: If set, operations that fail to convert are left unchanged. If not set,
103+
conversion failures raise a ValueError.
104+
105+
Returns:
106+
An equivalent circuit containing gates accepted by `gateset`.
107+
108+
Raises:
109+
ValueError: If any input operation fails to convert and `ignore_failures` is False.
110+
"""
111+
if gateset is None:
112+
return _decompose_operations_to_target_gateset(
113+
circuit, context=context, ignore_failures=ignore_failures
114+
)
115+
116+
for transformer in gateset.preprocess_transformers:
117+
circuit = transformer(circuit, context=context)
118+
119+
circuit = _decompose_operations_to_target_gateset(
120+
circuit,
121+
context=context,
122+
gateset=gateset,
123+
decomposer=gateset.decompose_to_target_gateset,
124+
ignore_failures=ignore_failures,
125+
)
126+
127+
for transformer in gateset.postprocess_transformers:
128+
circuit = transformer(circuit, context=context)
129+
130+
return circuit.unfreeze(copy=False)

0 commit comments

Comments
 (0)