Skip to content
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

Add cirq.optimize_for_target_gateset transformer and cirq.CompilationTargetGateset interface #5005

Merged
merged 7 commits into from
Feb 18, 2022
Merged
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
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