-
Notifications
You must be signed in to change notification settings - Fork 1k
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 map_operations
and map_moments
transformer primitives
#4692
Changes from 6 commits
0d61c80
836fe8c
665020d
eb2e3b1
5428d64
db7b5a8
3619b4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
# Copyright 2021 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. | ||
|
||
"""Defines primitives for common transformer patterns.""" | ||
|
||
from collections import defaultdict | ||
from typing import ( | ||
cast, | ||
Callable, | ||
Dict, | ||
Hashable, | ||
Optional, | ||
Sequence, | ||
TYPE_CHECKING, | ||
) | ||
|
||
from cirq import circuits, ops, protocols | ||
from cirq.circuits.circuit import CIRCUIT_TYPE | ||
|
||
if TYPE_CHECKING: | ||
import cirq | ||
|
||
MAPPED_CIRCUIT_OP_TAG = '<mapped_circuit_op>' | ||
|
||
|
||
def _to_target_circuit_type( | ||
circuit: circuits.AbstractCircuit, target_circuit: CIRCUIT_TYPE | ||
) -> CIRCUIT_TYPE: | ||
return cast( | ||
CIRCUIT_TYPE, | ||
circuit.unfreeze(copy=False) | ||
if isinstance(target_circuit, circuits.Circuit) | ||
else circuit.freeze(), | ||
) | ||
|
||
|
||
def _create_target_circuit_type(ops: ops.OP_TREE, target_circuit: CIRCUIT_TYPE) -> CIRCUIT_TYPE: | ||
return cast( | ||
CIRCUIT_TYPE, | ||
circuits.Circuit(ops) | ||
if isinstance(target_circuit, circuits.Circuit) | ||
else circuits.FrozenCircuit(ops), | ||
) | ||
|
||
|
||
def map_moments( | ||
circuit: CIRCUIT_TYPE, | ||
map_func: Callable[[ops.Moment, int], Sequence[ops.Moment]], | ||
) -> CIRCUIT_TYPE: | ||
"""Applies local transformation on moments, by calling `map_func(moment)` for each moment. | ||
|
||
Args: | ||
circuit: Input circuit to apply the transformations on. The input circuit is not mutated. | ||
map_func: Mapping function from (cirq.Moment, moment_index) to a sequence of moments. | ||
""" | ||
return _create_target_circuit_type( | ||
(map_func(circuit[i], i) for i in range(len(circuit))), circuit | ||
) | ||
|
||
|
||
def map_operations( | ||
circuit: CIRCUIT_TYPE, | ||
map_func: Callable[[ops.Operation, int], ops.OP_TREE], | ||
) -> CIRCUIT_TYPE: | ||
"""Applies local transformations on operations, by calling `map_func(op)` for each op. | ||
|
||
Note that the function assumes `issubset(qubit_set(map_func(op)), op.qubits)` is True. | ||
|
||
Args: | ||
circuit: Input circuit to apply the transformations on. The input circuit is not mutated. | ||
map_func: Mapping function from (cirq.Operation, moment_index) to a cirq.OP_TREE. If the | ||
resulting optree spans more than 1 moment, it's inserted in-place in the same moment as | ||
`cirq.CircuitOperation(cirq.FrozenCircuit(op_tree)).with_tags(MAPPED_CIRCUIT_OP_TAG)` | ||
to preserve moment structure. Utility methods like `cirq.unroll_circuit_op` can | ||
subsequently be used to unroll the mapped circuit operation. | ||
|
||
Raises: | ||
ValueError if `issubset(qubit_set(map_func(op)), op.qubits) is False`. | ||
""" | ||
|
||
def apply_map(op: ops.Operation, idx: int) -> ops.OP_TREE: | ||
c = circuits.FrozenCircuit(map_func(op, idx)) | ||
if not c.all_qubits().issubset(op.qubits): | ||
raise ValueError( | ||
f"Mapped operations {c.all_operations()} should act on a subset " | ||
f"of qubits of the original operation {op}" | ||
) | ||
if len(c) == 1: | ||
# All operations act in the same moment; so we don't need to wrap them in a circuit_op. | ||
return c[0].operations | ||
circuit_op = circuits.CircuitOperation(c).with_tags(MAPPED_CIRCUIT_OP_TAG) | ||
return circuit_op | ||
|
||
return map_moments(circuit, lambda m, i: [ops.Moment(apply_map(op, i) for op in m.operations)]) | ||
|
||
|
||
def map_operations_and_unroll( | ||
circuit: CIRCUIT_TYPE, | ||
map_func: Callable[[ops.Operation, int], ops.OP_TREE], | ||
) -> CIRCUIT_TYPE: | ||
"""Applies local transformations via `cirq.map_operations` & unrolls intermediate circuit ops. | ||
|
||
See `cirq.map_operations` and `cirq.unroll_circuit_op` for more details. | ||
|
||
Args: | ||
circuit: Input circuit to apply the transformations on. The input circuit is not mutated. | ||
map_func: Mapping function from (cirq.Operation, moment_index) to a cirq.OP_TREE. | ||
""" | ||
return unroll_circuit_op(map_operations(circuit, map_func)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this really needed ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
||
|
||
def _check_circuit_op(op, tags_to_check: Optional[Sequence[Hashable]]): | ||
return isinstance(op.untagged, circuits.CircuitOperation) and ( | ||
tags_to_check is None or any(tag in op.tags for tag in tags_to_check) | ||
) | ||
|
||
|
||
def unroll_circuit_op( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we merge these three unroll_circuit_op_*** into one ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think there's a clean way to merge the |
||
circuit: CIRCUIT_TYPE, *, tags_to_check: Optional[Sequence[Hashable]] = (MAPPED_CIRCUIT_OP_TAG,) | ||
) -> CIRCUIT_TYPE: | ||
"""Unrolls (tagged) `cirq.CircuitOperation`s while preserving the moment structure. | ||
|
||
Each moment containing a matching circuit operation is expanded into a list of moments with the | ||
unrolled operations, hence preserving the original moment structure. | ||
|
||
Args: | ||
circuit: Input circuit to apply the transformations on. The input circuit is not mutated. | ||
tags_to_check: If specified, only circuit operations tagged with one of the `tags_to_check` | ||
are unrolled. | ||
""" | ||
|
||
def map_func(m: ops.Moment, _: int): | ||
to_zip = [ | ||
cast(circuits.CircuitOperation, op.untagged).mapped_circuit() | ||
if _check_circuit_op(op, tags_to_check) | ||
else circuits.Circuit(op) | ||
for op in m | ||
] | ||
return circuits.Circuit.zip(*to_zip).moments | ||
|
||
return map_moments(circuit, map_func) | ||
|
||
|
||
def unroll_circuit_op_greedy_earliest( | ||
circuit: CIRCUIT_TYPE, *, tags_to_check=(MAPPED_CIRCUIT_OP_TAG,) | ||
) -> CIRCUIT_TYPE: | ||
"""Unrolls (tagged) `cirq.CircuitOperation`s by inserting operations using EARLIEST strategy. | ||
|
||
Each matching `cirq.CircuitOperation` is replaced by inserting underlying operations using the | ||
`cirq.InsertStrategy.EARLIEST` strategy. The greedy approach attempts to minimize circuit depth | ||
of the resulting circuit. | ||
|
||
Args: | ||
circuit: Input circuit to apply the transformations on. The input circuit is not mutated. | ||
tags_to_check: If specified, only circuit operations tagged with one of the `tags_to_check` | ||
are unrolled. | ||
""" | ||
batch_removals = [*circuit.findall_operations(lambda op: _check_circuit_op(op, tags_to_check))] | ||
batch_inserts = [(i, protocols.decompose_once(op)) for i, op in batch_removals] | ||
unrolled_circuit = circuit.unfreeze(copy=True) | ||
unrolled_circuit.batch_remove(batch_removals) | ||
unrolled_circuit.batch_insert(batch_inserts) | ||
return _to_target_circuit_type(unrolled_circuit, circuit) | ||
|
||
|
||
def unroll_circuit_op_greedy_frontier( | ||
circuit: CIRCUIT_TYPE, *, tags_to_check=(MAPPED_CIRCUIT_OP_TAG,) | ||
) -> CIRCUIT_TYPE: | ||
"""Unrolls (tagged) `cirq.CircuitOperation`s by inserting operations inline at qubit frontier. | ||
|
||
Each matching `cirq.CircuitOperation` is replaced by inserting underlying operations using the | ||
`circuit.insert_at_frontier` method. The greedy approach attempts to reuse any available space | ||
in existing moments on the right of circuit_op before inserting new moments. | ||
|
||
Args: | ||
circuit: Input circuit to apply the transformations on. The input circuit is not mutated. | ||
tags_to_check: If specified, only circuit operations tagged with one of the `tags_to_check` | ||
are unrolled. | ||
""" | ||
unrolled_circuit = circuit.unfreeze(copy=True) | ||
frontier: Dict['cirq.Qid', int] = defaultdict(lambda: 0) | ||
for idx, op in circuit.findall_operations(lambda op: _check_circuit_op(op, tags_to_check)): | ||
idx = max(idx, max(frontier[q] for q in op.qubits)) | ||
unrolled_circuit.clear_operations_touching(op.qubits, [idx]) | ||
frontier = unrolled_circuit.insert_at_frontier(protocols.decompose_once(op), idx, frontier) | ||
return _to_target_circuit_type(unrolled_circuit, circuit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the index
i
needed ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The index
i
is needed for situations when the mapping to be applied on the operation also depends upon which moment the operation is currently present in. For example, when characterizing and compensating for fsim gate angles, the moment index can be useful to determine what compensation to apply.