From aa8077f5a36ab9f2159af16a734252249be02265 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Mon, 15 May 2023 20:25:07 -0700 Subject: [PATCH 01/14] Add all-to-all decomposition for CCZ gate --- cirq-core/cirq/ops/three_qubit_gates.py | 41 ++++++++++++++++---- cirq-core/cirq/ops/three_qubit_gates_test.py | 12 ++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/cirq-core/cirq/ops/three_qubit_gates.py b/cirq-core/cirq/ops/three_qubit_gates.py index 521233dd2cf..8864871babb 100644 --- a/cirq-core/cirq/ops/three_qubit_gates.py +++ b/cirq-core/cirq/ops/three_qubit_gates.py @@ -97,8 +97,15 @@ def _pauli_expansion_(self) -> value.LinearDict[str]: } ) - def _decompose_(self, qubits): - """An adjacency-respecting decomposition. + def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): + """If qubits are all-to-all connected, e.g. qubits in the same ion trap, + the decomposition will be: + 0: ──────────────@──────────────────@───@───p──────@─── + │ │ │ │ + 1: ───@──────────┼───────@───p──────┼───X───p^-1───X─── + │ │ │ │ + 2: ───X───p^-1───X───p───X───p^-1───X───p────────────── + Otherwise the adjacency-respecting decomposition will be returned: 0: ───p───@──────────────@───────@──────────@────────── │ │ │ │ @@ -110,12 +117,13 @@ def _decompose_(self, qubits): """ a, b, c = qubits - # Hacky magic: avoid the non-adjacent edge. - if hasattr(b, 'is_adjacent'): - if not b.is_adjacent(a): - b, c = c, b - elif not b.is_adjacent(c): - a, b = b, a + if (not all_to_all_connect): + # Hacky magic: avoid the non-adjacent edge. + if hasattr(b, 'is_adjacent'): + if not b.is_adjacent(a): + b, c = c, b + elif not b.is_adjacent(c): + a, b = b, a p = common_gates.T**self._exponent sweep_abc = [common_gates.CNOT(a, b), common_gates.CNOT(b, c)] @@ -130,6 +138,23 @@ def _decompose_(self, qubits): if protocols.is_parameterized(global_phase) or abs(global_phase - 1.0) > 0 else [] ) + + if (all_to_all_connect): + return global_phase_operation + [ + common_gates.CNOT(b, c), + p(c) ** -1, + common_gates.CNOT(a, c), + p(c), + common_gates.CNOT(b, c), + p(c) ** -1, + common_gates.CNOT(a, c), + p(b), + p(c), + common_gates.CNOT(a, b), + p(a), + p(b) ** -1, + common_gates.CNOT(a, b), + ] return global_phase_operation + [ p(a), p(b), diff --git a/cirq-core/cirq/ops/three_qubit_gates_test.py b/cirq-core/cirq/ops/three_qubit_gates_test.py index 1eb972e4f89..d08c3c74246 100644 --- a/cirq-core/cirq/ops/three_qubit_gates_test.py +++ b/cirq-core/cirq/ops/three_qubit_gates_test.py @@ -228,6 +228,18 @@ def test_decomposition_respects_locality(gate): dev.validate_circuit(circuit) +def test_decomposition_all_to_all_connectivity(): + decompose_result = cirq.CCZ._decompose_( + cirq.LineQubit.range(3), all_to_all_connect=True) + cirq.testing.assert_has_diagram(cirq.Circuit(decompose_result), """ +0: ──────────────@──────────────────@───@───T──────@─── + │ │ │ │ +1: ───@──────────┼───────@───T──────┼───X───T^-1───X─── + │ │ │ │ +2: ───X───T^-1───X───T───X───T^-1───X───T────────────── +""") + + def test_diagram(): a, b, c, d = cirq.LineQubit.range(4) circuit = cirq.Circuit( From d5f8c989bf5f06173c8f1fec9be71af279ae3b28 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Mon, 15 May 2023 20:31:34 -0700 Subject: [PATCH 02/14] Fix format check and type check --- cirq-core/cirq/ops/three_qubit_gates.py | 6 +++--- cirq-core/cirq/ops/three_qubit_gates_test.py | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cirq-core/cirq/ops/three_qubit_gates.py b/cirq-core/cirq/ops/three_qubit_gates.py index 8864871babb..3fe6366a532 100644 --- a/cirq-core/cirq/ops/three_qubit_gates.py +++ b/cirq-core/cirq/ops/three_qubit_gates.py @@ -117,7 +117,7 @@ def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): """ a, b, c = qubits - if (not all_to_all_connect): + if not all_to_all_connect: # Hacky magic: avoid the non-adjacent edge. if hasattr(b, 'is_adjacent'): if not b.is_adjacent(a): @@ -130,7 +130,7 @@ def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): global_phase = 1j ** (2 * self.global_shift * self._exponent) global_phase = ( complex(global_phase) - if protocols.is_parameterized(global_phase) and global_phase.is_complex + if protocols.is_parameterized(global_phase) else global_phase ) global_phase_operation = ( @@ -139,7 +139,7 @@ def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): else [] ) - if (all_to_all_connect): + if all_to_all_connect: return global_phase_operation + [ common_gates.CNOT(b, c), p(c) ** -1, diff --git a/cirq-core/cirq/ops/three_qubit_gates_test.py b/cirq-core/cirq/ops/three_qubit_gates_test.py index d08c3c74246..5834ca65bbd 100644 --- a/cirq-core/cirq/ops/three_qubit_gates_test.py +++ b/cirq-core/cirq/ops/three_qubit_gates_test.py @@ -229,15 +229,17 @@ def test_decomposition_respects_locality(gate): def test_decomposition_all_to_all_connectivity(): - decompose_result = cirq.CCZ._decompose_( - cirq.LineQubit.range(3), all_to_all_connect=True) - cirq.testing.assert_has_diagram(cirq.Circuit(decompose_result), """ + decompose_result = cirq.CCZ._decompose_(cirq.LineQubit.range(3), all_to_all_connect=True) + cirq.testing.assert_has_diagram( + cirq.Circuit(decompose_result), + """ 0: ──────────────@──────────────────@───@───T──────@─── │ │ │ │ 1: ───@──────────┼───────@───T──────┼───X───T^-1───X─── │ │ │ │ 2: ───X───T^-1───X───T───X───T^-1───X───T────────────── -""") +""", + ) def test_diagram(): From 9555f317064f7e2ae948021b6e1f812a0dc41f44 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Mon, 15 May 2023 20:35:22 -0700 Subject: [PATCH 03/14] Revert type check change --- cirq-core/cirq/ops/three_qubit_gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/three_qubit_gates.py b/cirq-core/cirq/ops/three_qubit_gates.py index 3fe6366a532..95e33da4ff7 100644 --- a/cirq-core/cirq/ops/three_qubit_gates.py +++ b/cirq-core/cirq/ops/three_qubit_gates.py @@ -130,7 +130,7 @@ def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): global_phase = 1j ** (2 * self.global_shift * self._exponent) global_phase = ( complex(global_phase) - if protocols.is_parameterized(global_phase) + if protocols.is_parameterized(global_phase) and global_phase.is_complex else global_phase ) global_phase_operation = ( From 219c710d75275f47dfe26b697adb3819472ffc9b Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Mon, 15 May 2023 22:26:09 -0700 Subject: [PATCH 04/14] Ignore type check for `global_phase` --- cirq-core/cirq/ops/three_qubit_gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/three_qubit_gates.py b/cirq-core/cirq/ops/three_qubit_gates.py index 95e33da4ff7..aa26951511f 100644 --- a/cirq-core/cirq/ops/three_qubit_gates.py +++ b/cirq-core/cirq/ops/three_qubit_gates.py @@ -130,7 +130,7 @@ def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): global_phase = 1j ** (2 * self.global_shift * self._exponent) global_phase = ( complex(global_phase) - if protocols.is_parameterized(global_phase) and global_phase.is_complex + if protocols.is_parameterized(global_phase) and global_phase.is_complex # type: ignore else global_phase ) global_phase_operation = ( From e31bc96692f3dec5dec0baece5f19765119e65e4 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Tue, 16 May 2023 11:15:38 -0700 Subject: [PATCH 05/14] Expect test_custom_value_not_implemented to pass --- cirq-core/cirq/study/resolver_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cirq-core/cirq/study/resolver_test.py b/cirq-core/cirq/study/resolver_test.py index c2e1b8dde4b..553ff6f75d9 100644 --- a/cirq-core/cirq/study/resolver_test.py +++ b/cirq-core/cirq/study/resolver_test.py @@ -241,7 +241,6 @@ def _resolved_value_(self): assert r.value_of(b) == 'Baz' -@pytest.mark.xfail(reason='this test requires sympy 1.12', strict=True) def test_custom_value_not_implemented(): class Bar: def _resolved_value_(self): From 1d8515879b9c85aa5ab4bcf16fae9edff8f1c6e0 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Tue, 16 May 2023 12:52:49 -0700 Subject: [PATCH 06/14] Sync test_custom_value_not_implemented --- cirq-core/cirq/study/resolver_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cirq-core/cirq/study/resolver_test.py b/cirq-core/cirq/study/resolver_test.py index 553ff6f75d9..d844f586e55 100644 --- a/cirq-core/cirq/study/resolver_test.py +++ b/cirq-core/cirq/study/resolver_test.py @@ -13,7 +13,9 @@ # limitations under the License. """Tests for parameter resolvers.""" + import fractions +import sys import numpy as np import pytest @@ -241,6 +243,10 @@ def _resolved_value_(self): assert r.value_of(b) == 'Baz' +# sympy 1.12 does not support Python 3.7 +@pytest.mark.xfail( + condition=sys.version_info < (3, 8, 0), reason='this test requires sympy 1.12', strict=True +) def test_custom_value_not_implemented(): class Bar: def _resolved_value_(self): From e12eda11a7b31a1d24316778db84a5427a1ca066 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Mon, 22 May 2023 21:35:14 -0700 Subject: [PATCH 07/14] IonQTargetGateset._decompose_multi_qubit_operation --- cirq-core/cirq/ops/__init__.py | 1 + cirq-core/cirq/ops/three_qubit_gates.py | 89 ++++++++++++-------- cirq-core/cirq/ops/three_qubit_gates_test.py | 3 +- cirq-ionq/cirq_ionq/ionq_gateset.py | 12 ++- cirq-ionq/cirq_ionq/ionq_gateset_test.py | 20 +++++ 5 files changed, 89 insertions(+), 36 deletions(-) diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index 516ebbbb505..a0f0b707279 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -199,6 +199,7 @@ CCXPowGate, CCZ, CCZPowGate, + decompose_all_to_all_connect_ccz_gate, CSWAP, CSwapGate, FREDKIN, diff --git a/cirq-core/cirq/ops/three_qubit_gates.py b/cirq-core/cirq/ops/three_qubit_gates.py index aa26951511f..17e075106df 100644 --- a/cirq-core/cirq/ops/three_qubit_gates.py +++ b/cirq-core/cirq/ops/three_qubit_gates.py @@ -97,15 +97,8 @@ def _pauli_expansion_(self) -> value.LinearDict[str]: } ) - def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): - """If qubits are all-to-all connected, e.g. qubits in the same ion trap, - the decomposition will be: - 0: ──────────────@──────────────────@───@───p──────@─── - │ │ │ │ - 1: ───@──────────┼───────@───p──────┼───X───p^-1───X─── - │ │ │ │ - 2: ───X───p^-1───X───p───X───p^-1───X───p────────────── - Otherwise the adjacency-respecting decomposition will be returned: + def _decompose_(self, qubits): + """An adjacency-respecting decomposition. 0: ───p───@──────────────@───────@──────────@────────── │ │ │ │ @@ -117,13 +110,12 @@ def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): """ a, b, c = qubits - if not all_to_all_connect: - # Hacky magic: avoid the non-adjacent edge. - if hasattr(b, 'is_adjacent'): - if not b.is_adjacent(a): - b, c = c, b - elif not b.is_adjacent(c): - a, b = b, a + # Hacky magic: avoid the non-adjacent edge. + if hasattr(b, 'is_adjacent'): + if not b.is_adjacent(a): + b, c = c, b + elif not b.is_adjacent(c): + a, b = b, a p = common_gates.T**self._exponent sweep_abc = [common_gates.CNOT(a, b), common_gates.CNOT(b, c)] @@ -138,23 +130,6 @@ def _decompose_(self, qubits, all_to_all_connect: Optional[bool] = None): if protocols.is_parameterized(global_phase) or abs(global_phase - 1.0) > 0 else [] ) - - if all_to_all_connect: - return global_phase_operation + [ - common_gates.CNOT(b, c), - p(c) ** -1, - common_gates.CNOT(a, c), - p(c), - common_gates.CNOT(b, c), - p(c) ** -1, - common_gates.CNOT(a, c), - p(b), - p(c), - common_gates.CNOT(a, b), - p(a), - p(b) ** -1, - common_gates.CNOT(a, b), - ] return global_phase_operation + [ p(a), p(b), @@ -241,6 +216,54 @@ def controlled( ) +def decompose_all_to_all_connect_ccz_gate( + ccz_gate: 'CCZPowGate', + qubits: Tuple['cirq.Qid', 'cirq.Qid', 'cirq.Qid'] +) -> 'cirq.OP_TREE': + """If qubits are all-to-all connected, e.g. qubits in the same ion trap, + the decomposition will be: + + 0: ──────────────@──────────────────@───@───p──────@─── + │ │ │ │ + 1: ───@──────────┼───────@───p──────┼───X───p^-1───X─── + │ │ │ │ + 2: ───X───p^-1───X───p───X───p^-1───X───p────────────── + + where p = T**ccz_gate._exponent + """ + a, b, c = qubits + + p = common_gates.T**ccz_gate._exponent + global_phase = 1j ** (2 * ccz_gate.global_shift * ccz_gate._exponent) + global_phase = ( + complex(global_phase) + # type: ignore + if protocols.is_parameterized(global_phase) and global_phase.is_complex + else global_phase + ) + global_phase_operation = ( + [global_phase_op.global_phase_operation(global_phase)] + if protocols.is_parameterized(global_phase) or abs(global_phase - 1.0) > 0 + else [] + ) + + return global_phase_operation + [ + common_gates.CNOT(b, c), + p(c) ** -1, + common_gates.CNOT(a, c), + p(c), + common_gates.CNOT(b, c), + p(c) ** -1, + common_gates.CNOT(a, c), + p(b), + p(c), + common_gates.CNOT(a, b), + p(a), + p(b) ** -1, + common_gates.CNOT(a, b), + ] + + @value.value_equality() class ThreeQubitDiagonalGate(raw_types.Gate): r"""A three qubit gate whose unitary is given by a diagonal $8 \times 8$ matrix. diff --git a/cirq-core/cirq/ops/three_qubit_gates_test.py b/cirq-core/cirq/ops/three_qubit_gates_test.py index 5834ca65bbd..3a91bfd805f 100644 --- a/cirq-core/cirq/ops/three_qubit_gates_test.py +++ b/cirq-core/cirq/ops/three_qubit_gates_test.py @@ -229,7 +229,8 @@ def test_decomposition_respects_locality(gate): def test_decomposition_all_to_all_connectivity(): - decompose_result = cirq.CCZ._decompose_(cirq.LineQubit.range(3), all_to_all_connect=True) + decompose_result = cirq.ops.decompose_all_to_all_connect_ccz_gate( + cirq.CCZ, cirq.LineQubit.range(3)) cirq.testing.assert_has_diagram( cirq.Circuit(decompose_result), """ diff --git a/cirq-ionq/cirq_ionq/ionq_gateset.py b/cirq-ionq/cirq_ionq/ionq_gateset.py index e2ea3b4f294..961352f7bc7 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset.py @@ -78,12 +78,20 @@ def _decompose_two_qubit_operation(self, op: cirq.Operation, _) -> cirq.OP_TREE: temp, k=1, rewriter=lambda op: self._decompose_single_qubit_operation(op, -1) ).all_operations() + def _decompose_multi_qubit_operation(self, op: cirq.Operation, _) -> cirq.OP_TREE: + if isinstance(op.gate, cirq.CCZPowGate): + return cirq.ops.decompose_all_to_all_connect_ccz_gate(op.gate, op.qubits) + return NotImplemented + @property def preprocess_transformers(self) -> List['cirq.TRANSFORMER']: - """List of transformers which should be run before decomposing individual operations.""" + """List of transformers which should be run before decomposing individual operations. + Decompose to three qubit gates because three qubit gates have different decomposition + for all-to-all connectivity between qubits. + """ return [ cirq.create_transformer_with_kwargs( - cirq.expand_composite, no_decomp=lambda op: cirq.num_qubits(op) <= self.num_qubits + cirq.expand_composite, no_decomp=lambda op: cirq.num_qubits(op) <= 3 ) ] diff --git a/cirq-ionq/cirq_ionq/ionq_gateset_test.py b/cirq-ionq/cirq_ionq/ionq_gateset_test.py index 1817469ca03..dcab1a4e4e0 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset_test.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset_test.py @@ -131,3 +131,23 @@ def test_decompose_parameterized_operation(): atol=1e-6, ) assert ionq_target_gateset.validate(decomposed_circuit) + + +def test_decompose_toffoli_gate(): + circuit = cirq.Circuit(cirq.TOFFOLI(*cirq.LineQubit.range(3))) + decomposed_circuit = cirq.optimize_for_target_gateset( + circuit, gateset=ionq_target_gateset, ignore_failures=False + ) + cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent( + circuit, decomposed_circuit, atol=1e-8 + ) + assert ionq_target_gateset.validate(decomposed_circuit) + cirq.testing.assert_has_diagram(decomposed_circuit, + """ +0: ──────────────────@──────────────────@───@───T──────@─── + │ │ │ │ +1: ───────@──────────┼───────@───T──────┼───X───T^-1───X─── + │ │ │ │ +2: ───H───X───T^-1───X───T───X───T^-1───X───T───H────────── +""", + ) From 79f9c3a8357da27fe22daa389ed51bf76e3cb0e2 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Mon, 22 May 2023 21:51:54 -0700 Subject: [PATCH 08/14] Fix format and type check --- cirq-core/cirq/ops/three_qubit_gates.py | 8 +++----- cirq-core/cirq/ops/three_qubit_gates_test.py | 3 ++- cirq-ionq/cirq_ionq/ionq_gateset_test.py | 7 ++++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cirq-core/cirq/ops/three_qubit_gates.py b/cirq-core/cirq/ops/three_qubit_gates.py index 17e075106df..e8a8bca03d9 100644 --- a/cirq-core/cirq/ops/three_qubit_gates.py +++ b/cirq-core/cirq/ops/three_qubit_gates.py @@ -122,7 +122,7 @@ def _decompose_(self, qubits): global_phase = 1j ** (2 * self.global_shift * self._exponent) global_phase = ( complex(global_phase) - if protocols.is_parameterized(global_phase) and global_phase.is_complex # type: ignore + if protocols.is_parameterized(global_phase) and global_phase.is_complex else global_phase ) global_phase_operation = ( @@ -217,8 +217,7 @@ def controlled( def decompose_all_to_all_connect_ccz_gate( - ccz_gate: 'CCZPowGate', - qubits: Tuple['cirq.Qid', 'cirq.Qid', 'cirq.Qid'] + ccz_gate: 'CCZPowGate', qubits: Tuple['cirq.Qid', 'cirq.Qid', 'cirq.Qid'] ) -> 'cirq.OP_TREE': """If qubits are all-to-all connected, e.g. qubits in the same ion trap, the decomposition will be: @@ -237,8 +236,7 @@ def decompose_all_to_all_connect_ccz_gate( global_phase = 1j ** (2 * ccz_gate.global_shift * ccz_gate._exponent) global_phase = ( complex(global_phase) - # type: ignore - if protocols.is_parameterized(global_phase) and global_phase.is_complex + if protocols.is_parameterized(global_phase) and global_phase.is_complex # type: ignore else global_phase ) global_phase_operation = ( diff --git a/cirq-core/cirq/ops/three_qubit_gates_test.py b/cirq-core/cirq/ops/three_qubit_gates_test.py index 3a91bfd805f..a0536a9cb68 100644 --- a/cirq-core/cirq/ops/three_qubit_gates_test.py +++ b/cirq-core/cirq/ops/three_qubit_gates_test.py @@ -230,7 +230,8 @@ def test_decomposition_respects_locality(gate): def test_decomposition_all_to_all_connectivity(): decompose_result = cirq.ops.decompose_all_to_all_connect_ccz_gate( - cirq.CCZ, cirq.LineQubit.range(3)) + cirq.CCZ, cirq.LineQubit.range(3) + ) cirq.testing.assert_has_diagram( cirq.Circuit(decompose_result), """ diff --git a/cirq-ionq/cirq_ionq/ionq_gateset_test.py b/cirq-ionq/cirq_ionq/ionq_gateset_test.py index dcab1a4e4e0..a8e6139450a 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset_test.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset_test.py @@ -142,12 +142,13 @@ def test_decompose_toffoli_gate(): circuit, decomposed_circuit, atol=1e-8 ) assert ionq_target_gateset.validate(decomposed_circuit) - cirq.testing.assert_has_diagram(decomposed_circuit, - """ + cirq.testing.assert_has_diagram( + decomposed_circuit, + """ 0: ──────────────────@──────────────────@───@───T──────@─── │ │ │ │ 1: ───────@──────────┼───────@───T──────┼───X───T^-1───X─── │ │ │ │ 2: ───H───X───T^-1───X───T───X───T^-1───X───T───H────────── """, - ) + ) From cf3c6408eaedb6ecd3b683a85bba446f8c7d7fe8 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Mon, 22 May 2023 22:04:32 -0700 Subject: [PATCH 09/14] Fix type for input `qubits` --- cirq-core/cirq/ops/three_qubit_gates.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cirq-core/cirq/ops/three_qubit_gates.py b/cirq-core/cirq/ops/three_qubit_gates.py index e8a8bca03d9..c7e66d3a338 100644 --- a/cirq-core/cirq/ops/three_qubit_gates.py +++ b/cirq-core/cirq/ops/three_qubit_gates.py @@ -217,7 +217,7 @@ def controlled( def decompose_all_to_all_connect_ccz_gate( - ccz_gate: 'CCZPowGate', qubits: Tuple['cirq.Qid', 'cirq.Qid', 'cirq.Qid'] + ccz_gate: 'CCZPowGate', qubits: Tuple['cirq.Qid', ...] ) -> 'cirq.OP_TREE': """If qubits are all-to-all connected, e.g. qubits in the same ion trap, the decomposition will be: @@ -230,6 +230,9 @@ def decompose_all_to_all_connect_ccz_gate( where p = T**ccz_gate._exponent """ + if len(qubits) != 3: + raise ValueError(f'Expect 3 qubits for CCZ gate, got {len(qubits)} qubits.') + a, b, c = qubits p = common_gates.T**ccz_gate._exponent From b4ca8d739acd719d52b4d3a5ca3a79c38fcd0b17 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Fri, 2 Jun 2023 11:31:06 -0700 Subject: [PATCH 10/14] Add test for ValueError --- cirq-core/cirq/ops/three_qubit_gates_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cirq-core/cirq/ops/three_qubit_gates_test.py b/cirq-core/cirq/ops/three_qubit_gates_test.py index a0536a9cb68..ec1a5fbbcda 100644 --- a/cirq-core/cirq/ops/three_qubit_gates_test.py +++ b/cirq-core/cirq/ops/three_qubit_gates_test.py @@ -229,6 +229,12 @@ def test_decomposition_respects_locality(gate): def test_decomposition_all_to_all_connectivity(): + """This function only accepts 3 qubits as input""" + with pytest.raises(ValueError): + decompose_result = cirq.ops.decompose_all_to_all_connect_ccz_gate( + cirq.CCZ, cirq.LineQubit.range(4) + ) + decompose_result = cirq.ops.decompose_all_to_all_connect_ccz_gate( cirq.CCZ, cirq.LineQubit.range(3) ) From 8c5afe74e7c14dd4f0130440175bbc3123997b4a Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Wed, 19 Jul 2023 14:03:18 -0700 Subject: [PATCH 11/14] Resolve comments --- cirq-core/cirq/ops/__init__.py | 1 - cirq-core/cirq/ops/three_qubit_gates.py | 49 ----------------- cirq-core/cirq/ops/three_qubit_gates_test.py | 22 -------- cirq-ionq/cirq_ionq/__init__.py | 2 +- cirq-ionq/cirq_ionq/ionq_gateset.py | 55 +++++++++++++++++++- cirq-ionq/cirq_ionq/ionq_gateset_test.py | 23 ++++++++ 6 files changed, 78 insertions(+), 74 deletions(-) diff --git a/cirq-core/cirq/ops/__init__.py b/cirq-core/cirq/ops/__init__.py index f6602c6ad2e..ae352645ce2 100644 --- a/cirq-core/cirq/ops/__init__.py +++ b/cirq-core/cirq/ops/__init__.py @@ -201,7 +201,6 @@ CCXPowGate, CCZ, CCZPowGate, - decompose_all_to_all_connect_ccz_gate, CSWAP, CSwapGate, FREDKIN, diff --git a/cirq-core/cirq/ops/three_qubit_gates.py b/cirq-core/cirq/ops/three_qubit_gates.py index 0681424ceba..03795728f10 100644 --- a/cirq-core/cirq/ops/three_qubit_gates.py +++ b/cirq-core/cirq/ops/three_qubit_gates.py @@ -217,55 +217,6 @@ def controlled( ) -def decompose_all_to_all_connect_ccz_gate( - ccz_gate: 'CCZPowGate', qubits: Tuple['cirq.Qid', ...] -) -> 'cirq.OP_TREE': - """If qubits are all-to-all connected, e.g. qubits in the same ion trap, - the decomposition will be: - - 0: ──────────────@──────────────────@───@───p──────@─── - │ │ │ │ - 1: ───@──────────┼───────@───p──────┼───X───p^-1───X─── - │ │ │ │ - 2: ───X───p^-1───X───p───X───p^-1───X───p────────────── - - where p = T**ccz_gate._exponent - """ - if len(qubits) != 3: - raise ValueError(f'Expect 3 qubits for CCZ gate, got {len(qubits)} qubits.') - - a, b, c = qubits - - p = common_gates.T**ccz_gate._exponent - global_phase = 1j ** (2 * ccz_gate.global_shift * ccz_gate._exponent) - global_phase = ( - complex(global_phase) - if protocols.is_parameterized(global_phase) and global_phase.is_complex # type: ignore - else global_phase - ) - global_phase_operation = ( - [global_phase_op.global_phase_operation(global_phase)] - if protocols.is_parameterized(global_phase) or abs(global_phase - 1.0) > 0 - else [] - ) - - return global_phase_operation + [ - common_gates.CNOT(b, c), - p(c) ** -1, - common_gates.CNOT(a, c), - p(c), - common_gates.CNOT(b, c), - p(c) ** -1, - common_gates.CNOT(a, c), - p(b), - p(c), - common_gates.CNOT(a, b), - p(a), - p(b) ** -1, - common_gates.CNOT(a, b), - ] - - @value.value_equality() class ThreeQubitDiagonalGate(raw_types.Gate): r"""A three qubit gate whose unitary is given by a diagonal $8 \times 8$ matrix. diff --git a/cirq-core/cirq/ops/three_qubit_gates_test.py b/cirq-core/cirq/ops/three_qubit_gates_test.py index 5ea97fd92a2..c3f405d1fc4 100644 --- a/cirq-core/cirq/ops/three_qubit_gates_test.py +++ b/cirq-core/cirq/ops/three_qubit_gates_test.py @@ -230,28 +230,6 @@ def test_decomposition_respects_locality(gate): dev.validate_circuit(circuit) -def test_decomposition_all_to_all_connectivity(): - """This function only accepts 3 qubits as input""" - with pytest.raises(ValueError): - decompose_result = cirq.ops.decompose_all_to_all_connect_ccz_gate( - cirq.CCZ, cirq.LineQubit.range(4) - ) - - decompose_result = cirq.ops.decompose_all_to_all_connect_ccz_gate( - cirq.CCZ, cirq.LineQubit.range(3) - ) - cirq.testing.assert_has_diagram( - cirq.Circuit(decompose_result), - """ -0: ──────────────@──────────────────@───@───T──────@─── - │ │ │ │ -1: ───@──────────┼───────@───T──────┼───X───T^-1───X─── - │ │ │ │ -2: ───X───T^-1───X───T───X───T^-1───X───T────────────── -""", - ) - - def test_diagram(): a, b, c, d = cirq.LineQubit.range(4) circuit = cirq.Circuit( diff --git a/cirq-ionq/cirq_ionq/__init__.py b/cirq-ionq/cirq_ionq/__init__.py index 6c595b0ed84..8403adbef36 100644 --- a/cirq-ionq/cirq_ionq/__init__.py +++ b/cirq-ionq/cirq_ionq/__init__.py @@ -18,7 +18,7 @@ from cirq_ionq.ionq_devices import IonQAPIDevice, decompose_to_device -from cirq_ionq.ionq_gateset import IonQTargetGateset +from cirq_ionq.ionq_gateset import IonQTargetGateset, decompose_all_to_all_connect_ccz_gate from cirq_ionq.ionq_exceptions import ( IonQException, diff --git a/cirq-ionq/cirq_ionq/ionq_gateset.py b/cirq-ionq/cirq_ionq/ionq_gateset.py index 961352f7bc7..0003e5447a0 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset.py @@ -16,6 +16,7 @@ from typing import Any from typing import Dict from typing import List +from typing import Tuple import cirq @@ -80,7 +81,7 @@ def _decompose_two_qubit_operation(self, op: cirq.Operation, _) -> cirq.OP_TREE: def _decompose_multi_qubit_operation(self, op: cirq.Operation, _) -> cirq.OP_TREE: if isinstance(op.gate, cirq.CCZPowGate): - return cirq.ops.decompose_all_to_all_connect_ccz_gate(op.gate, op.qubits) + return decompose_all_to_all_connect_ccz_gate(op.gate, op.qubits) return NotImplemented @property @@ -112,3 +113,55 @@ def _json_dict_(self) -> Dict[str, Any]: @classmethod def _from_json_dict_(cls, atol, **kwargs): return cls(atol=atol) + + +def decompose_all_to_all_connect_ccz_gate( + ccz_gate: 'cirq.CZPowGate', qubits: Tuple['cirq.Qid', ...] +) -> 'cirq.OP_TREE': + """Decomposition of all-to-all connected qubits are different from line qubits or grid qubits. + + For example, for qubits in the same ion trap, the decomposition of CCZ gate will be: + + 0: ──────────────@──────────────────@───@───p──────@─── + │ │ │ │ + 1: ───@──────────┼───────@───p──────┼───X───p^-1───X─── + │ │ │ │ + 2: ───X───p^-1───X───p───X───p^-1───X───p────────────── + + where p = T**ccz_gate._exponent + """ + if len(qubits) != 3: + raise ValueError( + f'Expect 3 qubits for CCZ gate, got {len(qubits)} qubits.') + + a, b, c = qubits + + p = cirq.T**ccz_gate._exponent + global_phase = 1j ** (2 * ccz_gate.global_shift * ccz_gate._exponent) + global_phase = ( + complex(global_phase) + # type: ignore + if cirq.is_parameterized(global_phase) and global_phase.is_complex + else global_phase + ) + global_phase_operation = ( + [cirq.global_phase_operation(global_phase)] + if cirq.is_parameterized(global_phase) or abs(global_phase - 1.0) > 0 + else [] + ) + + return global_phase_operation + [ + cirq.CNOT(b, c), + p(c) ** -1, + cirq.CNOT(a, c), + p(c), + cirq.CNOT(b, c), + p(c) ** -1, + cirq.CNOT(a, c), + p(b), + p(c), + cirq.CNOT(a, b), + p(a), + p(b) ** -1, + cirq.CNOT(a, b), + ] diff --git a/cirq-ionq/cirq_ionq/ionq_gateset_test.py b/cirq-ionq/cirq_ionq/ionq_gateset_test.py index a8e6139450a..19df9db0c43 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset_test.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset_test.py @@ -133,7 +133,30 @@ def test_decompose_parameterized_operation(): assert ionq_target_gateset.validate(decomposed_circuit) +def test_decomposition_all_to_all_connectivity(): + """This function only accepts 3 qubits as input""" + with pytest.raises(ValueError): + decompose_result = ionq.decompose_all_to_all_connect_ccz_gate( + cirq.CCZ, cirq.LineQubit.range(4) + ) + + decompose_result = ionq.decompose_all_to_all_connect_ccz_gate( + cirq.CCZ, cirq.LineQubit.range(3) + ) + cirq.testing.assert_has_diagram( + cirq.Circuit(decompose_result), + """ +0: ──────────────@──────────────────@───@───T──────@─── + │ │ │ │ +1: ───@──────────┼───────@───T──────┼───X───T^-1───X─── + │ │ │ │ +2: ───X───T^-1───X───T───X───T^-1───X───T────────────── +""", + ) + + def test_decompose_toffoli_gate(): + """Decompose result should reflect all-to-all connectivity""" circuit = cirq.Circuit(cirq.TOFFOLI(*cirq.LineQubit.range(3))) decomposed_circuit = cirq.optimize_for_target_gateset( circuit, gateset=ionq_target_gateset, ignore_failures=False From 4d835d7f57e4129f439c9affb7c1fca0775deee5 Mon Sep 17 00:00:00 2001 From: yinghui-hu <125706885+yinghui-hu@users.noreply.github.com> Date: Wed, 19 Jul 2023 14:04:20 -0700 Subject: [PATCH 12/14] Add a new line after summary line and before description Co-authored-by: Tanuj Khattar --- cirq-ionq/cirq_ionq/ionq_gateset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cirq-ionq/cirq_ionq/ionq_gateset.py b/cirq-ionq/cirq_ionq/ionq_gateset.py index 0003e5447a0..a3c630b9a3e 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset.py @@ -87,6 +87,7 @@ def _decompose_multi_qubit_operation(self, op: cirq.Operation, _) -> cirq.OP_TRE @property def preprocess_transformers(self) -> List['cirq.TRANSFORMER']: """List of transformers which should be run before decomposing individual operations. + Decompose to three qubit gates because three qubit gates have different decomposition for all-to-all connectivity between qubits. """ From 6a1f140c4b68e0f7c1e8c106888c3b2fa5ce1e9d Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Wed, 19 Jul 2023 14:48:09 -0700 Subject: [PATCH 13/14] Fix format, type and lint --- cirq-ionq/cirq_ionq/ionq_gateset.py | 7 +++---- cirq-ionq/cirq_ionq/ionq_gateset_test.py | 5 ++--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cirq-ionq/cirq_ionq/ionq_gateset.py b/cirq-ionq/cirq_ionq/ionq_gateset.py index a3c630b9a3e..3930b2cff03 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset.py @@ -87,7 +87,7 @@ def _decompose_multi_qubit_operation(self, op: cirq.Operation, _) -> cirq.OP_TRE @property def preprocess_transformers(self) -> List['cirq.TRANSFORMER']: """List of transformers which should be run before decomposing individual operations. - + Decompose to three qubit gates because three qubit gates have different decomposition for all-to-all connectivity between qubits. """ @@ -117,7 +117,7 @@ def _from_json_dict_(cls, atol, **kwargs): def decompose_all_to_all_connect_ccz_gate( - ccz_gate: 'cirq.CZPowGate', qubits: Tuple['cirq.Qid', ...] + ccz_gate: 'cirq.CCZPowGate', qubits: Tuple['cirq.Qid', ...] ) -> 'cirq.OP_TREE': """Decomposition of all-to-all connected qubits are different from line qubits or grid qubits. @@ -132,8 +132,7 @@ def decompose_all_to_all_connect_ccz_gate( where p = T**ccz_gate._exponent """ if len(qubits) != 3: - raise ValueError( - f'Expect 3 qubits for CCZ gate, got {len(qubits)} qubits.') + raise ValueError(f'Expect 3 qubits for CCZ gate, got {len(qubits)} qubits.') a, b, c = qubits diff --git a/cirq-ionq/cirq_ionq/ionq_gateset_test.py b/cirq-ionq/cirq_ionq/ionq_gateset_test.py index 19df9db0c43..83ae015c065 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset_test.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset_test.py @@ -140,9 +140,8 @@ def test_decomposition_all_to_all_connectivity(): cirq.CCZ, cirq.LineQubit.range(4) ) - decompose_result = ionq.decompose_all_to_all_connect_ccz_gate( - cirq.CCZ, cirq.LineQubit.range(3) - ) + decompose_result = ionq.decompose_all_to_all_connect_ccz_gate(cirq.CCZ, cirq.LineQubit.range(3)) + cirq.testing.assert_has_diagram( cirq.Circuit(decompose_result), """ From 02c49a0130146a0c66023ba263239b6710bcba04 Mon Sep 17 00:00:00 2001 From: Yinghui Hu Date: Wed, 19 Jul 2023 15:15:40 -0700 Subject: [PATCH 14/14] Ignore type check --- cirq-ionq/cirq_ionq/ionq_gateset.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cirq-ionq/cirq_ionq/ionq_gateset.py b/cirq-ionq/cirq_ionq/ionq_gateset.py index 3930b2cff03..849df1081ac 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset.py @@ -140,8 +140,7 @@ def decompose_all_to_all_connect_ccz_gate( global_phase = 1j ** (2 * ccz_gate.global_shift * ccz_gate._exponent) global_phase = ( complex(global_phase) - # type: ignore - if cirq.is_parameterized(global_phase) and global_phase.is_complex + if cirq.is_parameterized(global_phase) and global_phase.is_complex # type: ignore else global_phase ) global_phase_operation = (