diff --git a/cirq-core/cirq/ops/gate_operation_test.py b/cirq-core/cirq/ops/gate_operation_test.py index 7f4c4b72351..3352694c045 100644 --- a/cirq-core/cirq/ops/gate_operation_test.py +++ b/cirq-core/cirq/ops/gate_operation_test.py @@ -11,11 +11,15 @@ # 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 collections.abc +import pathlib + import numpy as np import pytest import sympy import cirq +import cirq.testing def test_gate_operation_init(): @@ -465,3 +469,73 @@ def qubit_index_to_equivalence_group_key(self, index: int) -> int: assert gate(qubits[0], qubits[1], qubits[2], qubits[3]) == gate( qubits[3], qubits[1], qubits[2], qubits[0] ) + + +def test_gate_to_operation_to_gate_round_trips(): + def all_subclasses(cls): + return set(cls.__subclasses__()).union( + [s for c in cls.__subclasses__() for s in all_subclasses(c)] + ) + + # Only test gate subclasses in cirq-core. + gate_subclasses = { + g + for g in all_subclasses(cirq.Gate) + if "cirq." in g.__module__ and "contrib" not in g.__module__ and "test" not in g.__module__ + } + + test_module_spec = cirq.testing.json.spec_for("cirq.protocols") + + skip_classes = { + # Abstract or private parent classes. + cirq.BaseDensePauliString, + cirq.EigenGate, + cirq.Pauli, + # Private gates. + cirq.transformers.analytical_decompositions.two_qubit_to_fsim._BGate, + cirq.ops.raw_types._InverseCompositeGate, + cirq.circuits.qasm_output.QasmTwoQubitGate, + cirq.circuits.quil_output.QuilTwoQubitGate, + cirq.circuits.quil_output.QuilOneQubitGate, + cirq.ion.ion_gates.MSGate, + # Gate features. + cirq.SingleQubitGate, + # Interop gates + cirq.interop.quirk.QuirkQubitPermutationGate, + cirq.interop.quirk.QuirkArithmeticGate, + # No reason given for missing json. + # TODO(#5353): Serialize these gates. + cirq.PauliInteractionGate, + cirq.ArithmeticGate, + } + + # Gates that do not satisfy the contract. + # TODO(#5167): Fix this case. + exceptions = {cirq.PauliStringPhasorGate} + + skipped = set() + for gate_cls in gate_subclasses - exceptions: + filename = test_module_spec.test_data_path.joinpath(f"{gate_cls.__name__}.json") + if pathlib.Path(filename).is_file(): + gates = cirq.read_json(filename) + else: + if gate_cls in skip_classes: + skipped.add(gate_cls) + continue + # coverage:ignore + raise AssertionError( + f"{gate_cls} has no json file, please add a json file or add to the list of " + "classes to be skipped if there is a reason this gate should not round trip " + "to a gate via creating an operation." + ) + + if not isinstance(gates, collections.abc.Iterable): + gates = [gates] + for gate in gates: + if gate.num_qubits(): + qudits = [cirq.LineQid(i, d) for i, d in enumerate(cirq.qid_shape(gate))] + assert gate.on(*qudits).gate == gate + + assert ( + skipped == skip_classes + ), "A gate that was supposed to be skipped was not, please update the list of skipped gates."