def _deserialize_gate_op(
        self,
        operation_proto: v2.program_pb2.Operation,
        *,
        constants: list[v2.program_pb2.Constant] | None = None,
        deserialized_constants: list[Any] | None = None,
    ) -> cirq.Operation:
        """Deserialize an Operation from a cirq_google.api.v2.Operation.

        Args:
            operation_proto: A dictionary representing a
                cirq.google.api.v2.Operation proto.
            constants: The list of Constant protos referenced by constant
                table indices in `proto`.
            deserialized_constants: The deserialized contents of `constants`.
                cirq_google.api.v2.Operation proto.

        Returns:
            The deserialized Operation.

        Raises:
            ValueError: If the operation cannot be deserialized.
        """
        if deserialized_constants is not None:
            qubits = [deserialized_constants[q] for q in operation_proto.qubit_constant_index]
        else:
            qubits = []
        for q in operation_proto.qubits:
            # Preserve previous functionality in case
            # constants table was not used
            qubits.append(v2.qubit_from_proto_id(q.id))

        which_gate_type = operation_proto.WhichOneof('gate_value')

        if which_gate_type == 'xpowgate':
            op = cirq.XPowGate(
                exponent=arg_func_langs.float_arg_from_proto(
                    operation_proto.xpowgate.exponent, required_arg_name=None
                )
                or 0.0
            )(*qubits)
        elif which_gate_type == 'ypowgate':
            op = cirq.YPowGate(
                exponent=arg_func_langs.float_arg_from_proto(
                    operation_proto.ypowgate.exponent, required_arg_name=None
                )
                or 0.0
            )(*qubits)
        elif which_gate_type == 'zpowgate':
            op = cirq.ZPowGate(
                exponent=arg_func_langs.float_arg_from_proto(
                    operation_proto.zpowgate.exponent, required_arg_name=None
                )
                or 0.0
            )(*qubits)
            if operation_proto.zpowgate.is_physical_z:
                op = op.with_tags(PhysicalZTag())
        elif which_gate_type == 'hpowgate':
            op = cirq.HPowGate(
                exponent=arg_func_langs.float_arg_from_proto(
                    operation_proto.hpowgate.exponent, required_arg_name=None
                )
                or 0.0
            )(*qubits)
        elif which_gate_type == 'identitygate':
            op = cirq.IdentityGate(qid_shape=tuple(operation_proto.identitygate.qid_shape))(*qubits)
        elif which_gate_type == 'singlequbitcliffordgate':
            tableau = arg_func_langs.clifford_tableau_from_proto(
                operation_proto.singlequbitcliffordgate.tableau
            )
            op = cirq.ops.SingleQubitCliffordGate.from_clifford_tableau(tableau)(*qubits)
        elif which_gate_type == 'phasedxpowgate':
            exponent = (
                arg_func_langs.float_arg_from_proto(
                    operation_proto.phasedxpowgate.exponent, required_arg_name=None
                )
                or 0.0
            )
            phase_exponent = (
                arg_func_langs.float_arg_from_proto(
                    operation_proto.phasedxpowgate.phase_exponent, required_arg_name=None
                )
                or 0.0
            )
            op = cirq.PhasedXPowGate(exponent=exponent, phase_exponent=phase_exponent)(*qubits)
        elif which_gate_type == 'phasedxzgate':
            x_exponent = (
                arg_func_langs.float_arg_from_proto(
                    operation_proto.phasedxzgate.x_exponent, required_arg_name=None
                )
                or 0.0
            )
            z_exponent = (
                arg_func_langs.float_arg_from_proto(
                    operation_proto.phasedxzgate.z_exponent, required_arg_name=None
                )
                or 0.0
            )
            axis_phase_exponent = (
                arg_func_langs.float_arg_from_proto(
                    operation_proto.phasedxzgate.axis_phase_exponent, required_arg_name=None
                )
                or 0.0
            )
            op = cirq.PhasedXZGate(
                x_exponent=x_exponent,
                z_exponent=z_exponent,
                axis_phase_exponent=axis_phase_exponent,
            )(*qubits)
        elif which_gate_type == 'czpowgate':
            op = cirq.CZPowGate(
                exponent=arg_func_langs.float_arg_from_proto(
                    operation_proto.czpowgate.exponent, required_arg_name=None
                )
                or 0.0
            )(*qubits)
        elif which_gate_type == 'iswappowgate':
            op = cirq.ISwapPowGate(
                exponent=arg_func_langs.float_arg_from_proto(
                    operation_proto.iswappowgate.exponent, required_arg_name=None
                )
                or 0.0
            )(*qubits)
        elif which_gate_type == 'iswaplikegate':
            if (
                operation_proto.iswaplikegate.original_gate
                == v2.program_pb2.ISwapLikeGate.OriginalCirqGate.WILLOW
            ):
                op = WillowGate()(*qubits)
            else:
                op = SycamoreGate()(*qubits)
        elif which_gate_type == 'fsimgate':
            theta = arg_func_langs.float_arg_from_proto(
                operation_proto.fsimgate.theta, required_arg_name=None
            )
            phi = arg_func_langs.float_arg_from_proto(
                operation_proto.fsimgate.phi, required_arg_name=None
            )
            if isinstance(theta, (int, float, sympy.Basic)) and isinstance(
                phi, (int, float, sympy.Basic)
            ):
                op = cirq.FSimGate(theta=theta, phi=phi)(*qubits)
            else:
                raise ValueError('theta and phi must be specified for FSimGate')
            if operation_proto.fsimgate.translate_via_model:
                op = op.with_tags(FSimViaModelTag())
        elif which_gate_type == 'measurementgate':
            key = arg_func_langs.arg_from_proto(
                operation_proto.measurementgate.key, required_arg_name=None
            )
            parsed_invert_mask = arg_func_langs.arg_from_proto(
                operation_proto.measurementgate.invert_mask, required_arg_name=None
            )
            if (
                isinstance(parsed_invert_mask, (list, tuple)) or parsed_invert_mask is None
            ) and isinstance(key, str):
                invert_mask: tuple[bool, ...] = ()
                if parsed_invert_mask is not None:
                    invert_mask = tuple(bool(x) for x in parsed_invert_mask)
                op = cirq.MeasurementGate(num_qubits=len(qubits), key=key, invert_mask=invert_mask)(
                    *qubits
                )
            else:
                raise ValueError(f'Incorrect types for measurement gate {parsed_invert_mask} {key}')

        elif which_gate_type == 'waitgate':
            total_nanos = arg_func_langs.float_arg_from_proto(
                operation_proto.waitgate.duration_nanos, required_arg_name=None
            )
            op = cirq.WaitGate(
                duration=cirq.Duration(nanos=total_nanos or 0.0), num_qubits=len(qubits)
            )(*qubits)
        elif which_gate_type == 'resetgate':
            dimensions_proto = operation_proto.resetgate.arguments.get('dimension', None)
            if dimensions_proto is not None:
                dimensions = arg_func_langs.arg_from_proto(dimensions_proto)
            else:
                dimensions = 2
            if not isinstance(dimensions, int):
                # This should always be int, if serialized from cirq.
                raise ValueError(f"dimensions {dimensions} for ResetChannel must be an integer!")
            op = cirq.ResetChannel(dimension=dimensions)(*qubits)
        elif which_gate_type == 'internalgate':
            msg = operation_proto.internalgate
            op = arg_func_langs.internal_gate_from_proto(msg)(*qubits)
        elif which_gate_type == 'couplerpulsegate':
            gate = CouplerPulse(
                hold_time=cirq.Duration(
                    picos=arg_func_langs.float_arg_from_proto(
                        operation_proto.couplerpulsegate.hold_time_ps, required_arg_name=None
                    )
                    or 0.0
                ),
                rise_time=cirq.Duration(
                    picos=arg_func_langs.float_arg_from_proto(
                        operation_proto.couplerpulsegate.rise_time_ps, required_arg_name=None
                    )
                    or 0.0
                ),
                padding_time=cirq.Duration(
                    picos=arg_func_langs.float_arg_from_proto(
                        operation_proto.couplerpulsegate.padding_time_ps, required_arg_name=None
                    )
                    or 0.0
                ),
                coupling_mhz=arg_func_langs.float_arg_from_proto(
                    operation_proto.couplerpulsegate.coupling_mhz, required_arg_name=None
                )
                or 0.0,
                q0_detune_mhz=arg_func_langs.float_arg_from_proto(
                    operation_proto.couplerpulsegate.q0_detune_mhz, required_arg_name=None
                )
                or 0.0,
                q1_detune_mhz=arg_func_langs.float_arg_from_proto(
                    operation_proto.couplerpulsegate.q1_detune_mhz, required_arg_name=None
                )
                or 0.0,
            )
            op = gate(*qubits)
        else:
            raise ValueError(
                f'Unsupported serialized gate with type "{which_gate_type}".'
                f'\n\noperation_proto:\n{operation_proto}'
            )

        which = operation_proto.WhichOneof('token')
        if which == 'token_constant_index':
            if not constants:
                raise ValueError(
                    'Proto has references to constants table '
                    'but none was passed in, value ='
                    f'{operation_proto}'
                )
            op = op.with_tags(
                CalibrationTag(constants[operation_proto.token_constant_index].string_value)
            )
        elif which == 'token_value':
            op = op.with_tags(CalibrationTag(operation_proto.token_value))

        # Add conditions to op
        if operation_proto.conditioned_on:
            conditions = []
            for condition in operation_proto.conditioned_on:
                conditions.append(arg_func_langs.condition_from_proto(condition))
                op = op.with_classical_controls(*conditions)

        # Add tags to op
        if operation_proto.tag_indices and deserialized_constants is not None:
            tags = [
                deserialized_constants[tag_index]
                for tag_index in operation_proto.tag_indices
                if deserialized_constants[tag_index] not in op.tags
                and deserialized_constants[tag_index] is not None
            ]
        else:
            tags = []
            for tag in operation_proto.tags:
                if tag not in op.tags:
                    if self.tag_deserializer and self.tag_deserializer.can_deserialize_proto(tag):
                        tags.append(
                            self.tag_deserializer.from_proto(
                                tag,
                                constants=constants or [],
                                deserialized_constants=deserialized_constants or [],
                            )
                        )
                    elif (new_tag := self._deserialize_tag(tag)) is not None:
                        tags.append(new_tag)

        return op.with_tags(*tags)
Deserialize an Operation from a cirq_google.api.v2.Operation.

        Args:
            operation_proto: A dictionary representing a
                cirq.google.api.v2.Operation proto.
            constants: The list of Constant protos referenced by constant
                table indices in `proto`.
            deserialized_constants: The deserialized contents of `constants`.
                cirq_google.api.v2.Operation proto.

        Returns:
            The deserialized Operation.

        Raises:
            ValueError: If the operation cannot be deserialized.
        
cirq-google/cirq_google/serialization/circuit_serializer.py