From 9bd20faed03ed4e5ede16791a78a286d9b85716d Mon Sep 17 00:00:00 2001 From: Doug Strain Date: Mon, 23 May 2022 12:57:57 -0700 Subject: [PATCH] Change Pasqal to use transformers (#5377) - Add PasqalGateset that encapsulates gatesets used by the PasqalDevice classes. - Deprecate PasqalConverter and use cirq.convert_to_target_gateset instead. Fixes: #5130 --- cirq-pasqal/cirq_pasqal/__init__.py | 2 + .../cirq_pasqal/json_resolver_cache.py | 1 + .../json_test_data/PasqalGateset.json | 4 + .../json_test_data/PasqalGateset.repr | 1 + cirq-pasqal/cirq_pasqal/pasqal_device.py | 34 ++---- cirq-pasqal/cirq_pasqal/pasqal_gateset.py | 69 ++++++++++++ .../cirq_pasqal/pasqal_gateset_test.py | 105 ++++++++++++++++++ 7 files changed, 189 insertions(+), 27 deletions(-) create mode 100644 cirq-pasqal/cirq_pasqal/json_test_data/PasqalGateset.json create mode 100644 cirq-pasqal/cirq_pasqal/json_test_data/PasqalGateset.repr create mode 100644 cirq-pasqal/cirq_pasqal/pasqal_gateset.py create mode 100644 cirq-pasqal/cirq_pasqal/pasqal_gateset_test.py diff --git a/cirq-pasqal/cirq_pasqal/__init__.py b/cirq-pasqal/cirq_pasqal/__init__.py index e55b1229cc6..d7d35060db2 100644 --- a/cirq-pasqal/cirq_pasqal/__init__.py +++ b/cirq-pasqal/cirq_pasqal/__init__.py @@ -18,6 +18,8 @@ from cirq_pasqal.pasqal_qubits import ThreeDQubit, TwoDQubit +from cirq_pasqal.pasqal_gateset import PasqalGateset + from cirq_pasqal.pasqal_device import PasqalDevice, PasqalVirtualDevice, PasqalConverter from cirq_pasqal.pasqal_noise_model import PasqalNoiseModel diff --git a/cirq-pasqal/cirq_pasqal/json_resolver_cache.py b/cirq-pasqal/cirq_pasqal/json_resolver_cache.py index 840d84aa39d..983d14d1307 100644 --- a/cirq-pasqal/cirq_pasqal/json_resolver_cache.py +++ b/cirq-pasqal/cirq_pasqal/json_resolver_cache.py @@ -23,6 +23,7 @@ def _class_resolver_dictionary() -> Dict[str, ObjectFactory]: return { 'PasqalDevice': cirq_pasqal.PasqalDevice, + 'PasqalGateset': cirq_pasqal.PasqalGateset, 'PasqalVirtualDevice': cirq_pasqal.PasqalVirtualDevice, 'ThreeDQubit': cirq_pasqal.ThreeDQubit, 'TwoDQubit': cirq_pasqal.TwoDQubit, diff --git a/cirq-pasqal/cirq_pasqal/json_test_data/PasqalGateset.json b/cirq-pasqal/cirq_pasqal/json_test_data/PasqalGateset.json new file mode 100644 index 00000000000..2a713396dd5 --- /dev/null +++ b/cirq-pasqal/cirq_pasqal/json_test_data/PasqalGateset.json @@ -0,0 +1,4 @@ +{ + "cirq_type": "PasqalGateset", + "include_additional_controlled_ops": false +} diff --git a/cirq-pasqal/cirq_pasqal/json_test_data/PasqalGateset.repr b/cirq-pasqal/cirq_pasqal/json_test_data/PasqalGateset.repr new file mode 100644 index 00000000000..4f050a7b6c3 --- /dev/null +++ b/cirq-pasqal/cirq_pasqal/json_test_data/PasqalGateset.repr @@ -0,0 +1 @@ +cirq_pasqal.PasqalGateset(include_additional_controlled_ops=False) diff --git a/cirq-pasqal/cirq_pasqal/pasqal_device.py b/cirq-pasqal/cirq_pasqal/pasqal_device.py index 7506a06d9ba..86a37502649 100644 --- a/cirq-pasqal/cirq_pasqal/pasqal_device.py +++ b/cirq-pasqal/cirq_pasqal/pasqal_device.py @@ -18,7 +18,7 @@ import cirq from cirq import _compat, GridQubit, LineQubit from cirq.ops import NamedQubit -from cirq_pasqal import ThreeDQubit, TwoDQubit +from cirq_pasqal import ThreeDQubit, TwoDQubit, PasqalGateset @cirq.value.value_equality @@ -63,20 +63,7 @@ def __init__(self, qubits: Sequence[cirq.Qid]) -> None: 'qubits.'.format(type(self), self.maximum_qubit_number) ) - self.gateset = cirq.Gateset( - cirq.ParallelGateFamily(cirq.H), - cirq.ParallelGateFamily(cirq.PhasedXPowGate), - cirq.ParallelGateFamily(cirq.XPowGate), - cirq.ParallelGateFamily(cirq.YPowGate), - cirq.ParallelGateFamily(cirq.ZPowGate), - cirq.AnyIntegerPowerGateFamily(cirq.CNotPowGate), - cirq.AnyIntegerPowerGateFamily(cirq.CCNotPowGate), - cirq.AnyIntegerPowerGateFamily(cirq.CZPowGate), - cirq.AnyIntegerPowerGateFamily(cirq.CCZPowGate), - cirq.IdentityGate, - cirq.MeasurementGate, - unroll_circuit_op=False, - ) + self.gateset = PasqalGateset() self.qubits = qubits self._metadata = cirq.DeviceMetadata( qubits, nx.from_edgelist([(a, b) for a in qubits for b in qubits if a != b]) @@ -211,24 +198,14 @@ def __init__( 'Control_radius cannot be larger than 3 times' ' the minimal distance between qubits.' ) - self.control_radius = control_radius - self.exclude_gateset = cirq.Gateset( - cirq.AnyIntegerPowerGateFamily(cirq.CNotPowGate), - cirq.AnyIntegerPowerGateFamily(cirq.CCNotPowGate), - cirq.AnyIntegerPowerGateFamily(cirq.CCZPowGate), - ) - self.controlled_gateset = cirq.Gateset( - *self.exclude_gateset.gates, cirq.AnyIntegerPowerGateFamily(cirq.CZPowGate) - ) + self.gateset = PasqalGateset(include_additional_controlled_ops=False) + self.controlled_gateset = cirq.Gateset(cirq.AnyIntegerPowerGateFamily(cirq.CZPowGate)) @property def supported_qubit_type(self): return (ThreeDQubit, TwoDQubit, GridQubit, LineQubit) - def is_pasqal_device_op(self, op: cirq.Operation) -> bool: - return super().is_pasqal_device_op(op) and op not in self.exclude_gateset - def validate_operation(self, operation: cirq.Operation): """Raises an error if the given operation is invalid on this device. @@ -315,6 +292,9 @@ def _json_dict_(self) -> Dict[str, Any]: return cirq.protocols.obj_to_dict_helper(self, ['control_radius', 'qubits']) +@_compat.deprecated_class( + deadline='v0.16', fix='Use cirq.optimize_for_target_gateset(circuit, gateset=PasqalGateset()).' +) class PasqalConverter(cirq.neutral_atoms.ConvertToNeutralAtomGates): """A gate converter for compatibility with Pasqal processors. diff --git a/cirq-pasqal/cirq_pasqal/pasqal_gateset.py b/cirq-pasqal/cirq_pasqal/pasqal_gateset.py new file mode 100644 index 00000000000..3001c2e16e7 --- /dev/null +++ b/cirq-pasqal/cirq_pasqal/pasqal_gateset.py @@ -0,0 +1,69 @@ +# 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. +# +from typing import Any, Dict + + +import cirq + + +class PasqalGateset(cirq.neutral_atoms.NeutralAtomGateset): + """A Compilation target intended for Pasqal neutral atom devices. + This gateset supports single qubit gates that can be used + in a parallel fashion as well as CZ. + + This gateset can optionally include CNOT, CCNOT (TOFFOLI) gates, and + CCZ as well. + + Args: + include_additional_controlled_ops: Whether to include CCZ, CCNOT, and CNOT + gates (defaults to True). + """ + + def __init__(self, include_additional_controlled_ops: bool = True): + gate_families = [ + cirq.ParallelGateFamily(cirq.H), + cirq.ParallelGateFamily(cirq.PhasedXPowGate), + cirq.ParallelGateFamily(cirq.XPowGate), + cirq.ParallelGateFamily(cirq.YPowGate), + cirq.ParallelGateFamily(cirq.ZPowGate), + cirq.AnyIntegerPowerGateFamily(cirq.CZPowGate), + cirq.IdentityGate, + cirq.MeasurementGate, + ] + self.include_additional_controlled_ops = include_additional_controlled_ops + if self.include_additional_controlled_ops: + gate_families.append(cirq.AnyIntegerPowerGateFamily(cirq.CNotPowGate)) + gate_families.append(cirq.AnyIntegerPowerGateFamily(cirq.CCNotPowGate)) + gate_families.append(cirq.AnyIntegerPowerGateFamily(cirq.CCZPowGate)) + + # Call cirq.Gateset __init__ which is our grand-father inherited class + # pylint doesn't like this so disable checks on this. + # pylint: disable=bad-super-call + super(cirq.neutral_atoms.NeutralAtomGateset, self).__init__( + *gate_families, unroll_circuit_op=False + ) + + def __repr__(self): + return ( + f'cirq_pasqal.PasqalGateset(include_additional_controlled_ops=' + f'{self.include_additional_controlled_ops})' + ) + + @classmethod + def _from_json_dict_(cls, include_additional_controlled_ops, **kwargs): + return cls(include_additional_controlled_ops=include_additional_controlled_ops) + + def _json_dict_(self) -> Dict[str, Any]: + return cirq.protocols.obj_to_dict_helper(self, ['include_additional_controlled_ops']) diff --git a/cirq-pasqal/cirq_pasqal/pasqal_gateset_test.py b/cirq-pasqal/cirq_pasqal/pasqal_gateset_test.py new file mode 100644 index 00000000000..189a3caa919 --- /dev/null +++ b/cirq-pasqal/cirq_pasqal/pasqal_gateset_test.py @@ -0,0 +1,105 @@ +# 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. +# +import pytest +import cirq +import cirq_pasqal + +Q, Q2, Q3 = cirq.LineQubit.range(3) + + +@pytest.mark.parametrize( + "op,expected", + [ + (cirq.H(Q), True), + (cirq.HPowGate(exponent=0.5)(Q), False), + (cirq.PhasedXPowGate(exponent=0.25, phase_exponent=0.125)(Q), True), + (cirq.XPowGate(exponent=0.5)(Q), True), + (cirq.YPowGate(exponent=0.25)(Q), True), + (cirq.ZPowGate(exponent=0.125)(Q), True), + (cirq.CZPowGate(exponent=0.5)(Q, Q2), False), + (cirq.CZ(Q, Q2), True), + (cirq.CNOT(Q, Q2), True), + (cirq.SWAP(Q, Q2), False), + (cirq.ISWAP(Q, Q2), False), + (cirq.CCNOT(Q, Q2, Q3), True), + (cirq.CCZ(Q, Q2, Q3), True), + (cirq.ParallelGate(cirq.X, num_copies=3)(Q, Q2, Q3), True), + (cirq.ParallelGate(cirq.Y, num_copies=3)(Q, Q2, Q3), True), + (cirq.ParallelGate(cirq.Z, num_copies=3)(Q, Q2, Q3), True), + (cirq.X(Q).controlled_by(Q2, Q3), True), + (cirq.Z(Q).controlled_by(Q2, Q3), True), + (cirq.ZPowGate(exponent=0.5)(Q).controlled_by(Q2, Q3), False), + ], +) +def test_gateset(op: cirq.Operation, expected: bool): + gs = cirq_pasqal.PasqalGateset() + assert gs.validate(op) == expected + assert gs.validate(cirq.Circuit(op)) == expected + + +@pytest.mark.parametrize( + "op,expected", + [ + (cirq.H(Q), True), + (cirq.HPowGate(exponent=0.5)(Q), False), + (cirq.PhasedXPowGate(exponent=0.25, phase_exponent=0.125)(Q), True), + (cirq.ParallelGate(cirq.X, num_copies=3)(Q, Q2, Q3), True), + (cirq.CZPowGate(exponent=0.5)(Q, Q2), False), + (cirq.CZ(Q, Q2), True), + (cirq.CNOT(Q, Q2), False), + (cirq.CCNOT(Q, Q2, Q3), False), + (cirq.CCZ(Q, Q2, Q3), False), + (cirq.Z(Q).controlled_by(Q2), True), + (cirq.X(Q).controlled_by(Q2, Q3), False), + (cirq.Z(Q).controlled_by(Q2, Q3), False), + (cirq.ZPowGate(exponent=0.5)(Q).controlled_by(Q2, Q3), False), + ], +) +def test_control_gates_not_included(op: cirq.Operation, expected: bool): + gs = cirq_pasqal.PasqalGateset(include_additional_controlled_ops=False) + assert gs.validate(op) == expected + assert gs.validate(cirq.Circuit(op)) == expected + + +@pytest.mark.parametrize( + "op", + [ + cirq.X(Q), + cirq.SWAP(Q, Q2), + cirq.ISWAP(Q, Q2), + cirq.CCNOT(Q, Q2, Q3), + cirq.CCZ(Q, Q2, Q3), + cirq.ParallelGate(cirq.X, num_copies=3)(Q, Q2, Q3), + cirq.SWAP(Q, Q2).controlled_by(Q3), + ], +) +def test_decomposition(op: cirq.Operation): + circuit = cirq.Circuit(op) + gs = cirq_pasqal.PasqalGateset() + gs2 = cirq_pasqal.PasqalGateset(include_additional_controlled_ops=False) + for gateset in [gs, gs2]: + decomposed_circuit = cirq.optimize_for_target_gateset(circuit, gateset=gateset) + for new_op in decomposed_circuit.all_operations(): + assert gs.validate(new_op) + + +def test_repr(): + cirq.testing.assert_equivalent_repr( + cirq_pasqal.PasqalGateset(), setup_code='import cirq_pasqal' + ) + cirq.testing.assert_equivalent_repr( + cirq_pasqal.PasqalGateset(include_additional_controlled_ops=False), + setup_code='import cirq_pasqal', + )