From 081afab2f005510003af72c93fc1a601f755f354 Mon Sep 17 00:00:00 2001 From: yinghui-hu <125706885+yinghui-hu@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:26:35 -0700 Subject: [PATCH] Add decomposition for CCZ gate and IonQTargetGateset when qubits are all-to-all connected (#6095) --- cirq-ionq/cirq_ionq/__init__.py | 2 +- cirq-ionq/cirq_ionq/ionq_gateset.py | 64 +++++++++++++++++++++++- cirq-ionq/cirq_ionq/ionq_gateset_test.py | 43 ++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) 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 e2ea3b4f294..849df1081ac 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 @@ -78,12 +79,21 @@ 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 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 ) ] @@ -104,3 +114,53 @@ 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.CCZPowGate', 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) + if cirq.is_parameterized(global_phase) and global_phase.is_complex # type: ignore + 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 1817469ca03..83ae015c065 100644 --- a/cirq-ionq/cirq_ionq/ionq_gateset_test.py +++ b/cirq-ionq/cirq_ionq/ionq_gateset_test.py @@ -131,3 +131,46 @@ def test_decompose_parameterized_operation(): atol=1e-6, ) 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 + ) + 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────────── +""", + )