diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 47871747723..8626eaac03d 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -318,6 +318,7 @@ merge_single_qubit_gates_into_phased_x_z, merge_single_qubit_gates_into_phxz, MergeInteractions, + MergeInteractionsToSqrtIswap, MergeSingleQubitGates, single_qubit_matrix_to_gates, single_qubit_matrix_to_pauli_rotations, diff --git a/cirq-core/cirq/circuits/circuit.py b/cirq-core/cirq/circuits/circuit.py index 6610fc6c707..f0a01cef49c 100644 --- a/cirq-core/cirq/circuits/circuit.py +++ b/cirq-core/cirq/circuits/circuit.py @@ -2312,7 +2312,7 @@ def _pick_inserted_ops_moment_indices( frontier = defaultdict(lambda: 0) moment_indices = [] for op in operations: - op_start = max(start, max(frontier[q] for q in op.qubits)) + op_start = max(start, max((frontier[q] for q in op.qubits), default=0)) moment_indices.append(op_start) for q in op.qubits: frontier[q] = max(frontier[q], op_start + 1) diff --git a/cirq-core/cirq/optimizers/__init__.py b/cirq-core/cirq/optimizers/__init__.py index acdd0812970..f3c739f90db 100644 --- a/cirq-core/cirq/optimizers/__init__.py +++ b/cirq-core/cirq/optimizers/__init__.py @@ -60,6 +60,10 @@ MergeInteractions, ) +from cirq.optimizers.merge_interactions_to_sqrt_iswap import ( + MergeInteractionsToSqrtIswap, +) + from cirq.optimizers.merge_single_qubit_gates import ( merge_single_qubit_gates_into_phased_x_z, merge_single_qubit_gates_into_phxz, diff --git a/cirq-core/cirq/optimizers/merge_interactions.py b/cirq-core/cirq/optimizers/merge_interactions.py index d63f079081b..96665bc92dc 100644 --- a/cirq-core/cirq/optimizers/merge_interactions.py +++ b/cirq-core/cirq/optimizers/merge_interactions.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""An optimization pass that combines adjacent single-qubit rotations.""" +"""An optimization pass that combines adjacent series of gates on two qubits.""" from typing import Callable, List, Optional, Sequence, Tuple, cast, TYPE_CHECKING +import abc import numpy as np from cirq import circuits, ops, protocols @@ -25,19 +26,25 @@ import cirq -class MergeInteractions(circuits.PointOptimizer): - """Combines series of adjacent one and two-qubit gates operating on a pair - of qubits.""" +class MergeInteractionsAbc(circuits.PointOptimizer, metaclass=abc.ABCMeta): + """Combines series of adjacent one- and two-qubit, non-parametrized gates + operating on a pair of qubits.""" def __init__( self, tolerance: float = 1e-8, - allow_partial_czs: bool = True, post_clean_up: Callable[[Sequence[ops.Operation]], ops.OP_TREE] = lambda op_list: op_list, ) -> None: + """ + Args: + tolerance: A limit on the amount of absolute error introduced by the + construction. + post_clean_up: This function is called on each set of optimized + operations before they are put into the circuit to replace the + old operations. + """ super().__init__(post_clean_up=post_clean_up) self.tolerance = tolerance - self.allow_partial_czs = allow_partial_czs def optimization_at( self, circuit: circuits.Circuit, index: int, op: ops.Operation @@ -63,10 +70,8 @@ def optimization_at( if not switch_to_new and old_interaction_count <= 1: return None - # Find a max-3-cz construction. - new_operations = two_qubit_decompositions.two_qubit_matrix_to_operations( - op.qubits[0], op.qubits[1], matrix, self.allow_partial_czs, self.tolerance, False - ) + # Find a (possibly ideal) decomposition of the merged operations. + new_operations = self._two_qubit_matrix_to_operations(op.qubits[0], op.qubits[1], matrix) new_interaction_count = len( [new_op for new_op in new_operations if len(new_op.qubits) == 2] ) @@ -82,12 +87,29 @@ def optimization_at( new_operations=new_operations, ) + @abc.abstractmethod def _may_keep_old_op(self, old_op: 'cirq.Operation') -> bool: """Returns True if the old two-qubit operation may be left unchanged without decomposition.""" - if self.allow_partial_czs: - return isinstance(old_op.gate, ops.CZPowGate) - return isinstance(old_op.gate, ops.CZPowGate) and old_op.gate.exponent == 1 + + @abc.abstractmethod + def _two_qubit_matrix_to_operations( + self, + q0: 'cirq.Qid', + q1: 'cirq.Qid', + mat: np.ndarray, + ) -> Sequence['cirq.Operation']: + """Decomposes the merged two-qubit gate unitary into the minimum number + of two-qubit gates. + + Args: + q0: The first qubit being operated on. + q1: The other qubit being operated on. + mat: Defines the operation to apply to the pair of qubits. + + Returns: + A list of operations implementing the matrix. + """ def _op_to_matrix( self, op: ops.Operation, qubits: Tuple['cirq.Qid', ...] @@ -130,7 +152,7 @@ def _op_to_matrix( def _scan_two_qubit_ops_into_matrix( self, circuit: circuits.Circuit, index: Optional[int], qubits: Tuple['cirq.Qid', ...] - ) -> Tuple[List[ops.Operation], List[int], np.ndarray]: + ) -> Tuple[Sequence[ops.Operation], List[int], np.ndarray]: """Accumulates operations affecting the given pair of qubits. The scan terminates when it hits the end of the circuit, finds an @@ -181,3 +203,55 @@ def _flip_kron_order(mat4x4: np.ndarray) -> np.ndarray: for j in range(4): result[order[i], order[j]] = mat4x4[i, j] return result + + +class MergeInteractions(MergeInteractionsAbc): + """Combines series of adjacent one- and two-qubit, non-parametrized gates + operating on a pair of qubits and replaces each series with the minimum + number of CZ gates.""" + + def __init__( + self, + tolerance: float = 1e-8, + allow_partial_czs: bool = True, + post_clean_up: Callable[[Sequence[ops.Operation]], ops.OP_TREE] = lambda op_list: op_list, + ) -> None: + """ + Args: + tolerance: A limit on the amount of absolute error introduced by the + construction. + allow_partial_czs: Enables the use of Partial-CZ gates. + post_clean_up: This function is called on each set of optimized + operations before they are put into the circuit to replace the + old operations. + """ + super().__init__(tolerance=tolerance, post_clean_up=post_clean_up) + self.allow_partial_czs = allow_partial_czs + + def _may_keep_old_op(self, old_op: 'cirq.Operation') -> bool: + """Returns True if the old two-qubit operation may be left unchanged + without decomposition.""" + if self.allow_partial_czs: + return isinstance(old_op.gate, ops.CZPowGate) + return isinstance(old_op.gate, ops.CZPowGate) and old_op.gate.exponent == 1 + + def _two_qubit_matrix_to_operations( + self, + q0: 'cirq.Qid', + q1: 'cirq.Qid', + mat: np.ndarray, + ) -> Sequence['cirq.Operation']: + """Decomposes the merged two-qubit gate unitary into the minimum number + of CZ gates. + + Args: + q0: The first qubit being operated on. + q1: The other qubit being operated on. + mat: Defines the operation to apply to the pair of qubits. + + Returns: + A list of operations implementing the matrix. + """ + return two_qubit_decompositions.two_qubit_matrix_to_operations( + q0, q1, mat, self.allow_partial_czs, self.tolerance, False + ) diff --git a/cirq-core/cirq/optimizers/merge_interactions_to_sqrt_iswap.py b/cirq-core/cirq/optimizers/merge_interactions_to_sqrt_iswap.py new file mode 100644 index 00000000000..9d3f38c0ffd --- /dev/null +++ b/cirq-core/cirq/optimizers/merge_interactions_to_sqrt_iswap.py @@ -0,0 +1,104 @@ +# 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. + +"""An optimization pass that combines adjacent series of gates on two qubits and +outputs a circuit with SQRT_ISWAP or SQRT_ISWAP_INV gates.""" + +from typing import Callable, Optional, Sequence, TYPE_CHECKING + +import numpy as np + +from cirq import ops +from cirq.optimizers import two_qubit_to_sqrt_iswap, merge_interactions + +if TYPE_CHECKING: + import cirq + + +class MergeInteractionsToSqrtIswap(merge_interactions.MergeInteractionsAbc): + """Combines series of adjacent one- and two-qubit, non-parametrized gates + operating on a pair of qubits and replaces each series with the minimum + number of SQRT_ISWAP gates. + + See also: ``two_qubit_matrix_to_sqrt_iswap_operations`` + """ + + def __init__( + self, + tolerance: float = 1e-8, + *, + required_sqrt_iswap_count: Optional[int] = None, + use_sqrt_iswap_inv: bool = False, + post_clean_up: Callable[[Sequence[ops.Operation]], ops.OP_TREE] = lambda op_list: op_list, + ) -> None: + """ + Args: + tolerance: A limit on the amount of absolute error introduced by the + construction. + required_sqrt_iswap_count: When specified, each merged group of + two-qubit gates will be decomposed into exactly this many + sqrt-iSWAP gates even if fewer is possible (maximum 3). Circuit + optimization will raise a ``ValueError`` if this number is 2 or + lower and synthesis of any set of merged interactions requires + more. + use_sqrt_iswap_inv: If True, optimizes circuits using + ``SQRT_ISWAP_INV`` gates instead of ``SQRT_ISWAP``. + post_clean_up: This function is called on each set of optimized + operations before they are put into the circuit to replace the + old operations. + + Raises: + ValueError: + If ``required_sqrt_iswap_count`` is not one of the supported + values 0, 1, 2, or 3. + """ + if required_sqrt_iswap_count is not None and not 0 <= required_sqrt_iswap_count <= 3: + raise ValueError('the argument `required_sqrt_iswap_count` must be 0, 1, 2, or 3.') + super().__init__(tolerance=tolerance, post_clean_up=post_clean_up) + self.required_sqrt_iswap_count = required_sqrt_iswap_count + self.use_sqrt_iswap_inv = use_sqrt_iswap_inv + + def _may_keep_old_op(self, old_op: 'cirq.Operation') -> bool: + """Returns True if the old two-qubit operation may be left unchanged + without decomposition.""" + if self.use_sqrt_iswap_inv: + return isinstance(old_op.gate, ops.ISwapPowGate) and old_op.gate.exponent == -0.5 + return isinstance(old_op.gate, ops.ISwapPowGate) and old_op.gate.exponent == 0.5 + + def _two_qubit_matrix_to_operations( + self, + q0: 'cirq.Qid', + q1: 'cirq.Qid', + mat: np.ndarray, + ) -> Sequence['cirq.Operation']: + """Decomposes the merged two-qubit gate unitary into the minimum number + of SQRT_ISWAP gates. + + Args: + q0: The first qubit being operated on. + q1: The other qubit being operated on. + mat: Defines the operation to apply to the pair of qubits. + + Returns: + A list of operations implementing the matrix. + """ + return two_qubit_to_sqrt_iswap.two_qubit_matrix_to_sqrt_iswap_operations( + q0, + q1, + mat, + required_sqrt_iswap_count=self.required_sqrt_iswap_count, + use_sqrt_iswap_inv=self.use_sqrt_iswap_inv, + atol=self.tolerance, + check_preconditions=False, + ) diff --git a/cirq-core/cirq/optimizers/merge_interactions_to_sqrt_iswap_test.py b/cirq-core/cirq/optimizers/merge_interactions_to_sqrt_iswap_test.py new file mode 100644 index 00000000000..12bd5415d8f --- /dev/null +++ b/cirq-core/cirq/optimizers/merge_interactions_to_sqrt_iswap_test.py @@ -0,0 +1,317 @@ +# 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. + +from typing import Callable, List + +import pytest + +import cirq + + +def assert_optimizes(before: cirq.Circuit, expected: cirq.Circuit, **kwargs): + """Check that optimizing the circuit ``before`` produces the circuit ``expected``. + + The optimized circuit is cleaned up with follow up optimizations to make the + comparison more robust to extra moments or extra gates nearly equal to + identity that don't matter. + + Args: + before: The input circuit to optimize. + expected: The expected result of optimization to compare against. + kwargs: Any extra arguments to pass to the + ``MergeInteractionsToSqrtIswap`` constructor. + """ + actual = before.copy() + opt = cirq.MergeInteractionsToSqrtIswap(**kwargs) + opt.optimize_circuit(actual) + + # Ignore differences that would be caught by follow-up optimizations. + followup_optimizations: List[Callable[[cirq.Circuit], None]] = [ + cirq.merge_single_qubit_gates_into_phased_x_z, + cirq.EjectPhasedPaulis().optimize_circuit, + cirq.EjectZ().optimize_circuit, + cirq.DropNegligible().optimize_circuit, + cirq.DropEmptyMoments().optimize_circuit, + ] + for post in followup_optimizations: + post(actual) + post(expected) + + assert actual == expected, f'ACTUAL {actual} : EXPECTED {expected}' + + +def assert_optimization_not_broken(circuit: cirq.Circuit, **kwargs): + """Check that the unitary matrix for the input circuit is the same (up to + global phase and rounding error) as the unitary matrix of the optimized + circuit.""" + u_before = circuit.unitary(sorted(circuit.all_qubits())) + c_sqrt_iswap = circuit.copy() + cirq.MergeInteractionsToSqrtIswap(**kwargs).optimize_circuit(c_sqrt_iswap) + u_after = c_sqrt_iswap.unitary(sorted(circuit.all_qubits())) + + # Not 1e-8 because of some unaccounted accumulated error in some of Cirq's linalg functions + cirq.testing.assert_allclose_up_to_global_phase(u_before, u_after, atol=1e-6) + + # Also test optimization with SQRT_ISWAP_INV + c_sqrt_iswap_inv = circuit.copy() + cirq.MergeInteractionsToSqrtIswap(use_sqrt_iswap_inv=True).optimize_circuit(c_sqrt_iswap_inv) + u_after2 = c_sqrt_iswap_inv.unitary(sorted(circuit.all_qubits())) + + cirq.testing.assert_allclose_up_to_global_phase(u_before, u_after2, atol=1e-6) + + +def test_clears_paired_cnot(): + a, b = cirq.LineQubit.range(2) + assert_optimizes( + before=cirq.Circuit( + [ + cirq.Moment([cirq.CNOT(a, b)]), + cirq.Moment([cirq.CNOT(a, b)]), + ] + ), + expected=cirq.Circuit(), + ) + + +def test_simplifies_sqrt_iswap(): + a, b = cirq.LineQubit.range(2) + assert_optimizes( + before=cirq.Circuit( + [ + # SQRT_ISWAP**8 == Identity + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + ] + ), + expected=cirq.Circuit( + [ + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + ] + ), + ) + + +def test_simplifies_sqrt_iswap_inv(): + a, b = cirq.LineQubit.range(2) + assert_optimizes( + use_sqrt_iswap_inv=True, + before=cirq.Circuit( + [ + # SQRT_ISWAP**8 == Identity + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP_INV(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + ] + ), + expected=cirq.Circuit( + [ + cirq.Moment([cirq.SQRT_ISWAP_INV(a, b)]), + ] + ), + ) + + +def test_works_with_tags(): + a, b = cirq.LineQubit.range(2) + assert_optimizes( + before=cirq.Circuit( + [ + cirq.Moment([cirq.SQRT_ISWAP(a, b).with_tags('mytag1')]), + cirq.Moment([cirq.SQRT_ISWAP(a, b).with_tags('mytag2')]), + cirq.Moment([cirq.SQRT_ISWAP_INV(a, b).with_tags('mytag3')]), + ] + ), + expected=cirq.Circuit( + [ + cirq.Moment([cirq.SQRT_ISWAP(a, b)]), + ] + ), + ) + + +def test_no_touch_single_sqrt_iswap(): + a, b = cirq.LineQubit.range(2) + assert_optimizes( + before=cirq.Circuit( + [ + cirq.Moment([cirq.SQRT_ISWAP(a, b).with_tags('mytag')]), + ] + ), + expected=cirq.Circuit( + [ + cirq.Moment([cirq.SQRT_ISWAP(a, b).with_tags('mytag')]), + ] + ), + ) + + +def test_no_touch_single_sqrt_iswap_inv(): + a, b = cirq.LineQubit.range(2) + assert_optimizes( + use_sqrt_iswap_inv=True, + before=cirq.Circuit( + [ + cirq.Moment([cirq.SQRT_ISWAP_INV(a, b).with_tags('mytag')]), + ] + ), + expected=cirq.Circuit( + [ + cirq.Moment([cirq.SQRT_ISWAP_INV(a, b).with_tags('mytag')]), + ] + ), + ) + + +def test_cnots_separated_by_single_gates_correct(): + a, b = cirq.LineQubit.range(2) + assert_optimization_not_broken( + cirq.Circuit( + cirq.CNOT(a, b), + cirq.H(b), + cirq.CNOT(a, b), + ) + ) + + +def test_czs_separated_by_single_gates_correct(): + a, b = cirq.LineQubit.range(2) + assert_optimization_not_broken( + cirq.Circuit( + cirq.CZ(a, b), + cirq.X(b), + cirq.X(b), + cirq.X(b), + cirq.CZ(a, b), + ) + ) + + +def test_inefficient_circuit_correct(): + t = 0.1 + v = 0.11 + a, b = cirq.LineQubit.range(2) + assert_optimization_not_broken( + cirq.Circuit( + cirq.H(b), + cirq.CNOT(a, b), + cirq.H(b), + cirq.CNOT(a, b), + cirq.CNOT(b, a), + cirq.H(a), + cirq.CNOT(a, b), + cirq.Z(a) ** t, + cirq.Z(b) ** -t, + cirq.CNOT(a, b), + cirq.H(a), + cirq.Z(b) ** v, + cirq.CNOT(a, b), + cirq.Z(a) ** -v, + cirq.Z(b) ** -v, + ) + ) + + +def test_optimizes_single_iswap(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.ISWAP(a, b)) + assert_optimization_not_broken(c) + cirq.MergeInteractionsToSqrtIswap().optimize_circuit(c) + assert len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 2 + + +def test_optimizes_single_inv_sqrt_iswap(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.SQRT_ISWAP_INV(a, b)) + assert_optimization_not_broken(c) + cirq.MergeInteractionsToSqrtIswap().optimize_circuit(c) + assert len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 1 + + +def test_init_raises(): + with pytest.raises(ValueError, match='must be 0, 1, 2, or 3'): + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=4) + + +def test_optimizes_single_iswap_require0(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.CNOT(a, b), cirq.CNOT(a, b)) # Minimum 0 sqrt-iSWAP + assert_optimization_not_broken(c, required_sqrt_iswap_count=0) + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=0).optimize_circuit(c) + assert len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 0 + + +def test_optimizes_single_iswap_require0_raises(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.CNOT(a, b)) # Minimum 2 sqrt-iSWAP + with pytest.raises(ValueError, match='cannot be decomposed into exactly 0 sqrt-iSWAP gates'): + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=0).optimize_circuit(c) + + +def test_optimizes_single_iswap_require1(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.SQRT_ISWAP_INV(a, b)) # Minimum 1 sqrt-iSWAP + assert_optimization_not_broken(c, required_sqrt_iswap_count=1) + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=1).optimize_circuit(c) + assert len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 1 + + +def test_optimizes_single_iswap_require1_raises(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.CNOT(a, b)) # Minimum 2 sqrt-iSWAP + with pytest.raises(ValueError, match='cannot be decomposed into exactly 1 sqrt-iSWAP gates'): + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=1).optimize_circuit(c) + + +def test_optimizes_single_iswap_require2(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.SQRT_ISWAP_INV(a, b)) # Minimum 1 sqrt-iSWAP but 2 possible + assert_optimization_not_broken(c, required_sqrt_iswap_count=2) + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=2).optimize_circuit(c) + assert len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 2 + + +def test_optimizes_single_iswap_require2_raises(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.SWAP(a, b)) # Minimum 3 sqrt-iSWAP + with pytest.raises(ValueError, match='cannot be decomposed into exactly 2 sqrt-iSWAP gates'): + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=2).optimize_circuit(c) + + +def test_optimizes_single_iswap_require3(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.ISWAP(a, b)) # Minimum 2 sqrt-iSWAP but 3 possible + assert_optimization_not_broken(c, required_sqrt_iswap_count=3) + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=3).optimize_circuit(c) + assert len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 3 + + +def test_optimizes_single_inv_sqrt_iswap_require3(): + a, b = cirq.LineQubit.range(2) + c = cirq.Circuit(cirq.SQRT_ISWAP_INV(a, b)) + assert_optimization_not_broken(c, required_sqrt_iswap_count=3) + cirq.MergeInteractionsToSqrtIswap(required_sqrt_iswap_count=3).optimize_circuit(c) + assert len([1 for op in c.all_operations() if len(op.qubits) == 2]) == 3 diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index e0641d60f41..e15ee68cb6a 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -106,6 +106,7 @@ 'ExpandComposite', 'MEASUREMENT_KEY_SEPARATOR', 'MergeInteractions', + 'MergeInteractionsToSqrtIswap', 'MergeSingleQubitGates', 'PointOptimizer', 'SynchronizeTerminalMeasurements',