diff --git a/cirq-google/cirq_google/__init__.py b/cirq-google/cirq_google/__init__.py index 0261f6060af..476666c7f52 100644 --- a/cirq-google/cirq_google/__init__.py +++ b/cirq-google/cirq_google/__init__.py @@ -94,6 +94,7 @@ from cirq_google.transformers import ( known_2q_op_to_sycamore_operations, two_qubit_matrix_to_sycamore_operations, + GoogleCZTargetGateset, SycamoreTargetGateset, ) diff --git a/cirq-google/cirq_google/json_resolver_cache.py b/cirq-google/cirq_google/json_resolver_cache.py index 13ae4e5127c..03c5397f4b2 100644 --- a/cirq-google/cirq_google/json_resolver_cache.py +++ b/cirq-google/cirq_google/json_resolver_cache.py @@ -83,4 +83,5 @@ def _old_xmon(*args, **kwargs): # pylint: enable=line-too-long 'cirq.google.EngineResult': cirq_google.EngineResult, 'cirq.google.GridDevice': cirq_google.GridDevice, + 'cirq.google.GoogleCZTargetGateset': cirq_google.GoogleCZTargetGateset, } diff --git a/cirq-google/cirq_google/json_test_data/cirq.google.GoogleCZTargetGateset.json b/cirq-google/cirq_google/json_test_data/cirq.google.GoogleCZTargetGateset.json new file mode 100644 index 00000000000..34fe247dce6 --- /dev/null +++ b/cirq-google/cirq_google/json_test_data/cirq.google.GoogleCZTargetGateset.json @@ -0,0 +1,33 @@ +[ + { + "cirq_type": "cirq.google.GoogleCZTargetGateset", + "atol": 1e-08, + "eject_paulis": false, + "additional_gates": [] + }, + { + "cirq_type": "cirq.google.GoogleCZTargetGateset", + "atol": 1e-06, + "eject_paulis": true, + "additional_gates": [ + { + "cirq_type": "GateFamily", + "gate": { + "cirq_type": "ISwapPowGate", + "exponent": 0.5, + "global_shift": 0.0 + }, + "name": "Instance GateFamily: ISWAP**0.5", + "description": "Accepts `cirq.Gate` instances `g` s.t. `g == ISWAP**0.5`", + "ignore_global_phase": true + }, + { + "cirq_type": "GateFamily", + "gate": "XPowGate", + "name": "Type GateFamily: cirq.ops.common_gates.XPowGate", + "description": "Accepts `cirq.Gate` instances `g` s.t. `isinstance(g, cirq.ops.common_gates.XPowGate)`", + "ignore_global_phase": true + } + ] + } +] \ No newline at end of file diff --git a/cirq-google/cirq_google/json_test_data/cirq.google.GoogleCZTargetGateset.repr b/cirq-google/cirq_google/json_test_data/cirq.google.GoogleCZTargetGateset.repr new file mode 100644 index 00000000000..51829172bb3 --- /dev/null +++ b/cirq-google/cirq_google/json_test_data/cirq.google.GoogleCZTargetGateset.repr @@ -0,0 +1,4 @@ +[ + cirq_google.GoogleCZTargetGateset(atol=1e-08, eject_paulis=False, additional_gates=[]), + cirq_google.GoogleCZTargetGateset(atol=1e-06, eject_paulis=True, additional_gates=[(cirq.ISWAP**0.5), cirq.ops.common_gates.XPowGate]) +] \ No newline at end of file diff --git a/cirq-google/cirq_google/json_test_data/spec.py b/cirq-google/cirq_google/json_test_data/spec.py index fcf319062f6..753b3fb3045 100644 --- a/cirq-google/cirq_google/json_test_data/spec.py +++ b/cirq-google/cirq_google/json_test_data/spec.py @@ -62,6 +62,7 @@ 'SimulatedProcessorWithLocalDeviceRecord', 'EngineResult', 'GridDevice', + 'GoogleCZTargetGateset', ] }, resolver_cache=_class_resolver_dictionary(), diff --git a/cirq-google/cirq_google/transformers/__init__.py b/cirq-google/cirq_google/transformers/__init__.py index 1dea73acb52..c0655d7b717 100644 --- a/cirq-google/cirq_google/transformers/__init__.py +++ b/cirq-google/cirq_google/transformers/__init__.py @@ -19,4 +19,4 @@ two_qubit_matrix_to_sycamore_operations, ) -from cirq_google.transformers.target_gatesets import SycamoreTargetGateset +from cirq_google.transformers.target_gatesets import GoogleCZTargetGateset, SycamoreTargetGateset diff --git a/cirq-google/cirq_google/transformers/target_gatesets/__init__.py b/cirq-google/cirq_google/transformers/target_gatesets/__init__.py index 98de96915fb..be82a9c9c7a 100644 --- a/cirq-google/cirq_google/transformers/target_gatesets/__init__.py +++ b/cirq-google/cirq_google/transformers/target_gatesets/__init__.py @@ -14,4 +14,6 @@ """`cirq.CompilationTargetGateset` implementations for cirq_google gatesets and devices.""" +from cirq_google.transformers.target_gatesets.google_cz_gateset import GoogleCZTargetGateset + from cirq_google.transformers.target_gatesets.sycamore_gateset import SycamoreTargetGateset diff --git a/cirq-google/cirq_google/transformers/target_gatesets/google_cz_gateset.py b/cirq-google/cirq_google/transformers/target_gatesets/google_cz_gateset.py new file mode 100644 index 00000000000..dacefe8c851 --- /dev/null +++ b/cirq-google/cirq_google/transformers/target_gatesets/google_cz_gateset.py @@ -0,0 +1,102 @@ +# 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, List, Sequence, Type, Union +import cirq + + +class GoogleCZTargetGateset(cirq.CZTargetGateset): + """`cirq.CZTargetGateset` implementation tailored to Google devices. + + In addition to features available from `cirq.CZTargetGateset`, `GoogleCZTargetGateset` contains + a flag, `eject_paulis`, to enable the postprocess transformers `cirq.eject_phased_paulis` and + `cirq.eject_z`, which will push X, Y, Z, PhasedX, and certain PhasedXZ gates to the end of the + circuit. + """ + + def __init__( + self, + atol: float = 1e-8, + eject_paulis: bool = False, + additional_gates: Sequence[Union[Type[cirq.Gate], cirq.Gate, cirq.GateFamily]] = (), + ): + """Initializes GoogleCZTargetGateset. + + Args: + atol: A limit on the amount of absolute error introduced by the transformation. + eject_paulis: Whether to enable postprocess transformers `cirq.eject_z` and + `cirq.eject_phased_paulis`. If enabled, these transformers will remove tags (e.g. + `cirq_google.PhysicalZTag`) from single-qubit Pauli operations. Defaults to False. + additional_gates: Sequence of additional gates / gate families which should also + be "accepted" by this gateset. This is empty by default. + """ + super().__init__(atol=atol, allow_partial_czs=False, additional_gates=additional_gates) + self.eject_paulis = eject_paulis + self._additional_gates_repr_str = ", ".join( + [cirq.ops.gateset._gate_str(g, repr) for g in additional_gates] + ) + + @property + def postprocess_transformers(self) -> List[cirq.TRANSFORMER]: + """List of transformers which should be run after decomposing individual operations. + + If `eject_paulis` is enabled in the constructor, adds `cirq.eject_phased_paulis` and + `cirq.eject_z` in addition to postprocess_transformers already available in + `cirq.CompilationTargetGateset`. + """ + transformers: List[cirq.TRANSFORMER] = [ + cirq.create_transformer_with_kwargs( + cirq.merge_single_qubit_moments_to_phxz, atol=self.atol + ), + cirq.create_transformer_with_kwargs(cirq.drop_negligible_operations, atol=self.atol), + cirq.drop_empty_moments, + ] + + if self.eject_paulis: + return ( + transformers[:1] + + [ + cirq.create_transformer_with_kwargs(cirq.eject_phased_paulis, atol=self.atol), + cirq.create_transformer_with_kwargs(cirq.eject_z, atol=self.atol), + ] + + transformers[1:] + ) + return transformers + + def __repr__(self) -> str: + return ( + 'cirq_google.GoogleCZTargetGateset(' + f'atol={self.atol}, ' + f'eject_paulis={self.eject_paulis}, ' + f'additional_gates=[{self._additional_gates_repr_str}]' + ')' + ) + + def _value_equality_values_(self) -> Any: + return self.atol, self.eject_paulis, frozenset(self.additional_gates) + + @classmethod + def _json_namespace_(cls) -> str: + return 'cirq.google' + + def _json_dict_(self) -> Dict[str, Any]: + return { + 'atol': self.atol, + 'eject_paulis': self.eject_paulis, + 'additional_gates': list(self.additional_gates), + } + + @classmethod + def _from_json_dict_(cls, atol, eject_paulis, additional_gates, **kwargs): + return cls(atol=atol, eject_paulis=eject_paulis, additional_gates=additional_gates) diff --git a/cirq-google/cirq_google/transformers/target_gatesets/google_cz_gateset_test.py b/cirq-google/cirq_google/transformers/target_gatesets/google_cz_gateset_test.py new file mode 100644 index 00000000000..5e060a5a944 --- /dev/null +++ b/cirq-google/cirq_google/transformers/target_gatesets/google_cz_gateset_test.py @@ -0,0 +1,101 @@ +# 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_google + + +_qa, _qb = cirq.NamedQubit('a'), cirq.NamedQubit('b') + + +@pytest.mark.parametrize( + 'before, gate_family', + [ + ( + cirq.Circuit(cirq.Z(_qa) ** 0.5, cirq.CZ(_qa, _qb)), + cirq.GateFamily(cirq.ZPowGate, tags_to_ignore=[cirq_google.PhysicalZTag()]), + ), + ( + cirq.Circuit( + (cirq.Z**0.5)(_qa).with_tags(cirq_google.PhysicalZTag()), cirq.CZ(_qa, _qb) + ), + cirq.GateFamily(cirq.ZPowGate, tags_to_accept=[cirq_google.PhysicalZTag()]), + ), + ( + cirq.Circuit(cirq.PhasedXPowGate(phase_exponent=0.125).on(_qa), cirq.CZ(_qa, _qb)), + cirq.GateFamily(cirq.PhasedXPowGate), + ), + ], +) +def test_eject_paulis_disabled(before, gate_family): + after = cirq.optimize_for_target_gateset( + before, + gateset=cirq_google.GoogleCZTargetGateset(additional_gates=[gate_family]), + ignore_failures=False, + ) + cirq.testing.assert_same_circuits(after, before) + + +@pytest.mark.parametrize( + 'before, expected, gate_family', + [ + ( + cirq.Circuit(cirq.Z(_qa) ** 0.75, cirq.CZ(_qa, _qb)), + cirq.Circuit(cirq.CZ(_qa, _qb), cirq.Z(_qa) ** 0.75), + cirq.GateFamily(cirq.ZPowGate, tags_to_ignore=[cirq_google.PhysicalZTag()]), + ), + ( + # PhysicalZ tag is erased + cirq.Circuit( + (cirq.Z**0.75)(_qa).with_tags(cirq_google.PhysicalZTag()), cirq.CZ(_qa, _qb) + ), + cirq.Circuit(cirq.CZ(_qa, _qb), cirq.Z(_qa) ** 0.75), + cirq.GateFamily(cirq.ZPowGate, tags_to_accept=[cirq_google.PhysicalZTag()]), + ), + ( + cirq.Circuit(cirq.PhasedXPowGate(phase_exponent=0.125).on(_qa), cirq.CZ(_qa, _qb)), + cirq.Circuit( + (cirq.CZ**-1)(_qa, _qb), + cirq.PhasedXPowGate(phase_exponent=0.125).on(_qa), + cirq.Z(_qb), + ), + cirq.PhasedXPowGate, + ), + ], +) +def test_eject_paulis_enabled(before, expected, gate_family): + after = cirq.optimize_for_target_gateset( + before, + gateset=cirq_google.GoogleCZTargetGateset( + eject_paulis=True, additional_gates=[gate_family] + ), + ignore_failures=False, + ) + cirq.testing.assert_same_circuits(after, expected) + + +@pytest.mark.parametrize( + 'gateset', + [ + cirq_google.GoogleCZTargetGateset(), + cirq_google.GoogleCZTargetGateset( + atol=1e-6, eject_paulis=True, additional_gates=[cirq.SQRT_ISWAP, cirq.XPowGate] + ), + cirq_google.GoogleCZTargetGateset(additional_gates=()), + ], +) +def test_repr(gateset): + cirq.testing.assert_equivalent_repr(gateset, setup_code='import cirq\nimport cirq_google')