Skip to content

Commit

Permalink
Add cirq.convert_to_target_gateset transformer and `cirq.Compilatio…
Browse files Browse the repository at this point in the history
…nTargetGateset` interface (#5005)

* Add convert_to_target_gateset transformer and CompilationTargetGateset interface

* Override validation_operation to not accept intermediate results
  • Loading branch information
tanujkhattar authored Feb 18, 2022
1 parent 3517306 commit 59b72a1
Show file tree
Hide file tree
Showing 10 changed files with 534 additions and 3 deletions.
2 changes: 2 additions & 0 deletions cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@
from cirq.transformers import (
align_left,
align_right,
CompilationTargetGateset,
compute_cphase_exponents_for_fsim_decomposition,
decompose_clifford_tableau_to_operations,
decompose_cphase_into_two_fsim,
Expand All @@ -380,6 +381,7 @@
merge_single_qubit_gates_to_phased_x_and_z,
merge_single_qubit_gates_to_phxz,
merge_single_qubit_moments_to_phxz,
optimize_for_target_gateset,
prepare_two_qubit_state_using_cz,
prepare_two_qubit_state_using_sqrt_iswap,
single_qubit_matrix_to_gates,
Expand Down
6 changes: 5 additions & 1 deletion cirq-core/cirq/protocols/decompose_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,11 @@ def decompose(
that doesn't satisfy the given `keep` predicate.
"""

if on_stuck_raise is not _value_error_describing_bad_operation and keep is None:
if (
on_stuck_raise is not _value_error_describing_bad_operation
and on_stuck_raise is not None
and keep is None
):
raise ValueError(
"Must specify 'keep' if specifying 'on_stuck_raise', because it's "
"not possible to get stuck if you don't have a criteria on what's "
Expand Down
3 changes: 1 addition & 2 deletions cirq-core/cirq/protocols/decompose_protocol_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def test_decompose_on_stuck_raise():
_ = cirq.decompose(NoMethod(), keep=lambda _: False)
# Unless there's no operations to be unhappy about.
assert cirq.decompose([], keep=lambda _: False) == []
assert cirq.decompose([], on_stuck_raise=None) == []
# Or you say you're fine.
assert cirq.decompose(no_method, keep=lambda _: False, on_stuck_raise=None) == [no_method]
assert cirq.decompose(no_method, keep=lambda _: False, on_stuck_raise=lambda _: None) == [
Expand All @@ -198,8 +199,6 @@ def test_decompose_on_stuck_raise():
)

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

Expand Down
2 changes: 2 additions & 0 deletions cirq-core/cirq/protocols/json_test_data/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
'ApplyMixtureArgs',
'ApplyUnitaryArgs',
'OperationTarget',
# Abstract base class for creating compilation targets.
'CompilationTargetGateset',
# Circuit optimizers are function-like. Only attributes
# are ignore_failures, tolerance, and other feature flags
'AlignLeft',
Expand Down
6 changes: 6 additions & 0 deletions cirq-core/cirq/transformers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
two_qubit_gate_product_tabulation,
)

from cirq.transformers.target_gatesets import (
CompilationTargetGateset,
)

from cirq.transformers.align import align_left, align_right

from cirq.transformers.stratify import stratified_circuit
Expand All @@ -49,6 +53,8 @@

from cirq.transformers.eject_phased_paulis import eject_phased_paulis

from cirq.transformers.optimize_for_target_gateset import optimize_for_target_gateset

from cirq.transformers.drop_empty_moments import drop_empty_moments

from cirq.transformers.drop_negligible_operations import drop_negligible_operations
Expand Down
130 changes: 130 additions & 0 deletions cirq-core/cirq/transformers/optimize_for_target_gateset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Copyright 2022 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 to rewrite a circuit using gates from a given target gateset."""

from typing import Optional, Callable, TYPE_CHECKING

from cirq.protocols import decompose_protocol as dp
from cirq.transformers import transformer_api, transformer_primitives

if TYPE_CHECKING:
import cirq


def _create_on_stuck_raise_error(gateset: 'cirq.Gateset'):
def _value_error_describing_bad_operation(op: 'cirq.Operation') -> ValueError:
return ValueError(f"Unable to convert {op} to target gateset {gateset!r}")

return _value_error_describing_bad_operation


@transformer_api.transformer
def _decompose_operations_to_target_gateset(
circuit: 'cirq.AbstractCircuit',
*,
context: Optional['cirq.TransformerContext'] = None,
gateset: Optional['cirq.Gateset'] = None,
decomposer: Callable[['cirq.Operation', int], dp.DecomposeResult] = lambda *_: NotImplemented,
ignore_failures: bool = True,
) -> 'cirq.Circuit':
"""Decomposes every operation to `gateset` using `cirq.decompose` and `decomposer`.
This transformer attempts to decompose every operation `op` in the given circuit to `gateset`
using `cirq.decompose` protocol with `decomposer` used as an intercepting decomposer. This
ensures that `op` is recursively decomposed using implicitly defined known decompositions
(eg: in `_decompose_` magic method on the gaet class) till either `decomposer` knows how to
decompose the given operation or the given operation belongs to `gateset`.
Args:
circuit: Input circuit to transform. It will not be modified.
context: `cirq.TransformerContext` storing common configurable options for transformers.
gateset: Target gateset, which the decomposed operations should belong to.
decomposer: A callable type which accepts an (operation, moment_index) and returns
- An equivalent `cirq.OP_TREE` implementing `op` using gates from `gateset`.
- `None` or `NotImplemented` if does not know how to decompose a given `op`.
ignore_failures: If set, operations that fail to convert are left unchanged. If not set,
conversion failures raise a ValueError.
Returns:
An equivalent circuit containing gates accepted by `gateset`.
Raises:
ValueError: If any input operation fails to convert and `ignore_failures` is False.
"""

def map_func(op: 'cirq.Operation', moment_index: int):
return dp.decompose(
op,
intercepting_decomposer=lambda o: decomposer(o, moment_index),
keep=gateset.validate if gateset else None,
on_stuck_raise=(
None
if ignore_failures or gateset is None
else _create_on_stuck_raise_error(gateset)
),
)

return transformer_primitives.map_operations_and_unroll(
circuit, map_func, tags_to_ignore=context.tags_to_ignore if context else ()
).unfreeze(copy=False)


@transformer_api.transformer
def optimize_for_target_gateset(
circuit: 'cirq.AbstractCircuit',
*,
context: Optional['cirq.TransformerContext'] = None,
gateset: Optional['cirq.CompilationTargetGateset'] = None,
ignore_failures: bool = True,
) -> 'cirq.Circuit':
"""Transforms the given circuit into an equivalent circuit using gates accepted by `gateset`.
1. Run all `gateset.preprocess_transformers`
2. Convert operations using built-in cirq decompose + `gateset.decompose_to_target_gateset`.
3. Run all `gateset.postprocess_transformers`
Args:
circuit: Input circuit to transform. It will not be modified.
context: `cirq.TransformerContext` storing common configurable options for transformers.
gateset: Target gateset, which should be an instance of `cirq.CompilationTargetGateset`.
ignore_failures: If set, operations that fail to convert are left unchanged. If not set,
conversion failures raise a ValueError.
Returns:
An equivalent circuit containing gates accepted by `gateset`.
Raises:
ValueError: If any input operation fails to convert and `ignore_failures` is False.
"""
if gateset is None:
return _decompose_operations_to_target_gateset(
circuit, context=context, ignore_failures=ignore_failures
)

for transformer in gateset.preprocess_transformers:
circuit = transformer(circuit, context=context)

circuit = _decompose_operations_to_target_gateset(
circuit,
context=context,
gateset=gateset,
decomposer=gateset.decompose_to_target_gateset,
ignore_failures=ignore_failures,
)

for transformer in gateset.postprocess_transformers:
circuit = transformer(circuit, context=context)

return circuit.unfreeze(copy=False)
Loading

0 comments on commit 59b72a1

Please sign in to comment.