Repository: PennyLaneAI/pennylane
Pull request #7319 (2025-04-22 13:33:22)

deprecate the operation.py boolean fns
**Context:**

The `operation.py` module currently serves to provide both the core interface for the operator abstraction, and a bunch of minor utility functions.

Some of those minor utility functions have dependencies on downstream components, like `MeasurementProcess` and `QuantumScript`.  Others are examples where the interface is more complicated than the code they hide.  They just end up complicating the interface and don't really even simplify anything.

**Description of the Change:**

Deprecate all the `BooleanFn`'s present in `operation.py` in favour of people just using the relevant code.

**Benefits:**

`operation.py` can be more focused on setting up the abstractions and interfaces for operators. `operation.py` looses dependencies on downstream structures. The interface is less cluttered with unnecessary helpers.

**Possible Drawbacks:**

As it is still a deprecation, someone out there might still be depending on these things.

**Related GitHub Issues:**

[sc-89158]

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7319?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
Attention: Patch coverage is `94.87179%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 99.64%. Comparing base [(`067543f`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/067543f08d38950b5db5e7817c4a9c9931a75435?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`b11d091`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/b11d09158856c8a65093c08755ad2d46a3cd77e4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

| [Files with missing lines](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7319?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Patch % | Lines |
|---|---|---|
| [pennylane/operation.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7319?src=pr&el=tree&filepath=pennylane%2Foperation.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wZXJhdGlvbi5weQ==) | 81.81% | [2 Missing :warning: ](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #7319      +/-   ##
==========================================
- Coverage   99.65%   99.64%   -0.01%     
==========================================
  Files         526      526              
  Lines       50291    50318      +27     
==========================================
+ Hits        50115    50137      +22     
- Misses        176      181       +5     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7319?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index 4bc2cbf3f..066f33364 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -16,6 +16,50 @@ Pending deprecations
   - Deprecated in v0.42
   - Will be removed in v0.43
 
+* The boolean functions provided by ``pennylane.operation`` are deprecated. See below for alternate code to
+  use instead.
+  These include ``not_tape``, ``has_gen``, ``has_grad_method``,  ``has_multipar``, ``has_nopar``, ``has_unitary_gen``,
+  ``is_measurement``, ``defines_diagonalizing_gates``, and ``gen_is_multi_term_hamiltonian``.
+
+  - Deprecated in v0.42
+  - Will be removed in v0.43
+
+.. code-block:: python
+
+    def not_tape(obj):
+        return not isinstance(obj, qml.tape.QuantumScript)
+
+    def has_gen(obj):
+        return obj.has_generator
+
+    def has_grad_method(obj):
+        return obj.grad_method is not None
+
+    def has_multipar(obj):
+        return obj.num_params > 1
+
+    def has_nopar(obj):
+        return obj.num_params == 0
+
+    def has_unitary_gen(obj):
+        return obj in qml.ops.qubit.attributes.has_unitary_generator
+
+    def is_measurement(obj):
+        return isinstance(obj, qml.measurements.MeasurementProcess)
+
+    def defines_diagonalizing_gates(obj):
+        return obj.has_diagonalizing_gates
+
+    def gen_is_multi_term_hamiltonian(obj):
+        if not isinstance(obj, Operator) or not obj.has_generator:
+            return False
+        try:
+            generator = obj.generator()
+            _, ops = generator.terms() 
+            return len(ops) > 1
+        except TermsUndefinedError:
+            return False
+
 * The :func:`qml.QNode.get_gradient_fn` method is now deprecated. Instead, use :func:`~.workflow.get_best_diff_method` to obtain the differentiation method.
 
   - Deprecated in v0.42
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index a7bef5ad0..be132e449 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -201,6 +201,11 @@
 
 <h3>Deprecations 👋</h3>
 
+* The boolean functions provided in `pennylane.operation` are deprecated. See the :doc:`deprecations page </development/deprecations>` 
+  for equivalent code to use instead. These include `not_tape`, `has_gen`, `has_grad_method`, `has_multipar`,
+  `has_nopar`, `has_unitary_gen`, `is_measurement`, `defines_diagonalizing_gates`, and `gen_is_multi_term_hamiltonian`.
+  [(#7319)](https://github.com/PennyLaneAI/pennylane/pull/7319)
+
 * `qml.operation.WiresEnum`, `qml.operation.AllWires`, and `qml.operation.AnyWires` are deprecated. To indicate that
   an operator can act on any number of wires, `Operator.num_wires = None` should be used instead. This is the default
   and does not need to be overwritten unless the operator developer wants to add wire number validation.
diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py
index a941db7a3..5a223341f 100644
--- a/pennylane/devices/default_qubit.py
+++ b/pennylane/devices/default_qubit.py
@@ -25,6 +25,7 @@ from typing import Optional, Sequence, Union
 import numpy as np
 
 import pennylane as qml
+from pennylane import math
 from pennylane.logging import debug_logger, debug_logger_init
 from pennylane.measurements import ClassicalShadowMP, ShadowExpvalMP
 from pennylane.measurements.mid_measure import MidMeasureMP
@@ -63,7 +64,7 @@ def stopping_condition(op: qml.operation.Operator) -> bool:
         return False
     if op.name == "Snapshot":
         return True
-    if op.__class__.__name__[:3] == "Pow" and qml.operation.is_trainable(op):
+    if op.__class__.__name__[:3] == "Pow" and any(math.requires_grad(d) for d in op.data):
         return False
     if op.name == "FromBloq" and len(op.wires) > 3:
         return False
@@ -220,7 +221,7 @@ def adjoint_ops(op: qml.operation.Operator) -> bool:
     """Specify whether or not an Operator is supported by adjoint differentiation."""
     return not isinstance(op, (Conditional, MidMeasureMP)) and (
         op.num_params == 0
-        or not qml.operation.is_trainable(op)
+        or not any(math.requires_grad(d) for d in op.data)
         or (op.num_params == 1 and op.has_generator)
     )
 
diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py
index 9b0a7231b..76273405a 100644
--- a/pennylane/devices/legacy_facade.py
+++ b/pennylane/devices/legacy_facade.py
@@ -22,7 +22,7 @@ from copy import copy, deepcopy
 from dataclasses import replace
 
 import pennylane as qml
-from pennylane.math import get_canonical_interface_name
+from pennylane.math import get_canonical_interface_name, requires_grad
 from pennylane.measurements import MidMeasureMP, Shots
 from pennylane.transforms.core.transform_program import TransformProgram
 
@@ -101,7 +101,7 @@ def legacy_device_batch_transform(tape, device):
 
 def adjoint_ops(op: qml.operation.Operator) -> bool:
     """Specify whether or not an Operator is supported by adjoint differentiation."""
-    if isinstance(op, qml.QubitUnitary) and not qml.operation.is_trainable(op):
+    if isinstance(op, qml.QubitUnitary) and not any(requires_grad(d) for d in op.data):
         return True
     return not isinstance(op, MidMeasureMP) and (
         op.num_params == 0 or (op.num_params == 1 and op.has_generator)
diff --git a/pennylane/devices/preprocess.py b/pennylane/devices/preprocess.py
index 8f4e93dcf..d709b6355 100644
--- a/pennylane/devices/preprocess.py
+++ b/pennylane/devices/preprocess.py
@@ -24,6 +24,7 @@ from typing import Optional, Type
 
 import pennylane as qml
 from pennylane import Snapshot, transform
+from pennylane.math import requires_grad
 from pennylane.measurements import SampleMeasurement, StateMeasurement
 from pennylane.operation import StatePrepBase
 from pennylane.tape import QuantumScript, QuantumScriptBatch
@@ -278,13 +279,13 @@ def validate_adjoint_trainable_params(
     """
 
     for op in tape.operations[: tape.num_preps]:
-        if qml.operation.is_trainable(op):
+        if any(requires_grad(d) for d in op.data):
             raise qml.QuantumFunctionError(
                 "Differentiating with respect to the input parameters of state-prep operations "
                 "is not supported with the adjoint differentiation method."
             )
     for m in tape.measurements:
-        if m.obs and qml.operation.is_trainable(m.obs):
+        if m.obs and any(requires_grad(d) for d in m.obs.data):
             warnings.warn(
                 f"Differentiating with respect to the input parameters of {m.obs.name} "
                 "is not supported with the adjoint differentiation method. Gradients are computed "
diff --git a/pennylane/operation.py b/pennylane/operation.py
index 1449346d8..a2028ad37 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -156,6 +156,11 @@ Boolean Functions
 :class:`~.BooleanFn`'s are functions of a single object that return ``True`` or ``False``.
 The ``operation`` module provides the following:
 
+.. warning::
+
+    All of the boolean functions in this module are currently deprecated. See the individual functions
+    for alternative code.
+
 .. currentmodule:: pennylane.operation
 
 .. autosummary::
@@ -2404,13 +2409,30 @@ def operation_derivative(operation: Operation) -> TensorLike:
 
 @qml.BooleanFn
 def not_tape(obj):
-    """Returns ``True`` if the object is not a quantum tape"""
-    return isinstance(obj, qml.tape.QuantumScript)
+    """Returns ``True`` if the object is not a quantum tape.
+
+    .. warning::
+
+        **Deprecated**. Use ``not isinstance(obj, qml.tape.QuantumScript)`` instead.
+    """
+    warnings.warn(
+        "not_tape is deprecated. Use ``not isinstance(obj, qml.tape.QuantumScript)`` instead.",
+        PennyLaneDeprecationWarning,
+    )
+    return not isinstance(obj, qml.tape.QuantumScript)
 
 
 @qml.BooleanFn
 def has_gen(obj):
-    """Returns ``True`` if an operator has a generator defined."""
+    """Returns ``True`` if an operator has a generator defined.
+
+    .. warning::
+
+        **Deprecated**. Use ``obj.has_generator`` instead.
+    """
+    warnings.warn(
+        "has_gen is deprecated. Use Operator.has_generator instead.", PennyLaneDeprecationWarning
+    )
     if isinstance(obj, Operator):
         return obj.has_generator
     try:
@@ -2422,42 +2444,87 @@ def has_gen(obj):
 
 @qml.BooleanFn
 def has_grad_method(obj):
-    """Returns ``True`` if an operator has a grad_method defined."""
+    """Returns ``True`` if an operator has a grad_method defined.
+
+    .. warning::
+
+        **Deprecated**: Use ``obj.grad_method is not None`` instead.
+    """
+    warnings.warn(
+        "has_grad_method is deprecated. Use obj.grad_method is not None instead. ",
+        PennyLaneDeprecationWarning,
+    )
     return obj.grad_method is not None
 
 
 @qml.BooleanFn
 def has_multipar(obj):
     """Returns ``True`` if an operator has more than one parameter
-    according to ``num_params``."""
+    according to ``num_params``.
+
+    .. warning::
+
+        **Deprecated**: Use ``obj.num_params > 1`` instead.
+    """
+    warnings.warn(
+        "has_multipar is deprecated. Use obj.num_params > 1 instead.", PennyLaneDeprecationWarning
+    )
     return obj.num_params > 1
 
 
 @qml.BooleanFn
 def has_nopar(obj):
     """Returns ``True`` if an operator has no parameters
-    according to ``num_params``."""
+    according to ``num_params``.
+
+    .. warning::
+
+        **Deprecated**: Use ``obj.num_params == 0`` instead.
+    """
+    warnings.warn(
+        "has_nopar is deprecated. Use obj.num_params == 0 instead.", PennyLaneDeprecationWarning
+    )
     return obj.num_params == 0
 
 
 @qml.BooleanFn
 def has_unitary_gen(obj):
     """Returns ``True`` if an operator has a unitary_generator
-    according to the ``has_unitary_generator`` flag."""
+    according to the ``has_unitary_generator`` flag.
+
+    .. warning::
+
+        **Deprecated**: Use ``obj in qml.ops.qubit.attributes.has_unitary_gen`` insteaed.
+
+    """
+    warnings.warn(
+        "has_unitary_gen is deprecated. Use `obj in qml.ops.qubit.attributes.has_unitary_generator` instead.",
+        PennyLaneDeprecationWarning,
+    )
     # Linting check disabled as static analysis can misidentify qml.ops as the set instance qml.ops.qubit.ops
     return obj in qml.ops.qubit.attributes.has_unitary_generator  # pylint:disable=no-member
 
 
 @qml.BooleanFn
 def is_measurement(obj):
-    """Returns ``True`` if an operator is a ``MeasurementProcess`` instance."""
+    """Returns ``True`` if an operator is a ``MeasurementProcess`` instance.
+
+    .. warning::
+
+        **Deprecated**: Use ``isinstance(obj, qml.measurements.MeasurementProcess)`` instead.
+    """
+    warnings.warn(
+        "is_measurement is deprecated. Use isinstance(obj, qml.measurements.MeasurementProcess) instead.",
+        PennyLaneDeprecationWarning,
+    )
     return isinstance(obj, qml.measurements.MeasurementProcess)
 
 
 @qml.BooleanFn
 def is_trainable(obj):
     """Returns ``True`` if any of the parameters of an operator is trainable
-    according to ``qml.math.requires_grad``."""
+    according to ``qml.math.requires_grad``.
+    """
     return any(qml.math.requires_grad(p) for p in obj.parameters)
 
 
@@ -2465,16 +2532,57 @@ def is_trainable(obj):
 def defines_diagonalizing_gates(obj):
     """Returns ``True`` if an operator defines the diagonalizing gates.
 
-    This helper function is useful if the property is to be checked in
-    a queuing context, but the resulting gates must not be queued.
+    .. warning::
+
+        **Deprecated:** Use ``obj.has_diagonalizing_gates`` instead.
+
     """
+    warnings.warn(
+        "defines_diagonalizing_gates is deprecated. Use obj.has_diagonalizing_gates instead.",
+        PennyLaneDeprecationWarning,
+    )
     return obj.has_diagonalizing_gates
 
 
+_gen_is_multi_term_hamiltonian_code = """
+if not isinstance(obj, Operator) or not obj.has_generator:
+    return False
+try:
+    generator = obj.generator()
+    _, ops = generator.terms() 
+    return len(ops) > 1
+except TermsUndefinedError:
+    return False
+"""
+
+
 @qml.BooleanFn
 def gen_is_multi_term_hamiltonian(obj):
     """Returns ``True`` if an operator has a generator defined and it is a Hamiltonian
-    with more than one term."""
+    with more than one term.
+
+    .. warning::
+
+        **Deprecated**: Use the following code instead:
+
+        .. code-block:: python
+
+            def gen_is_multi_term_hamiltonian(obj):
+                if not isinstance(obj, Operator) or not obj.has_generator:
+                    return False
+                try:
+                    generator = obj.generator()
+                    _, ops = generator.terms()
+                    return len(ops) > 1
+                except TermsUndefinedError:
+                    return False
+
+
+    """
+    warnings.warn(
+        f"gen_is_multiterm_hamiltonian is deprecated. Use {_gen_is_multi_term_hamiltonian_code}",
+        PennyLaneDeprecationWarning,
+    )
     if not isinstance(obj, Operator) or not obj.has_generator:
         return False
     try:
diff --git a/pennylane/templates/subroutines/commuting_evolution.py b/pennylane/templates/subroutines/commuting_evolution.py
index 3bb4d49e8..0a5fcf490 100644
--- a/pennylane/templates/subroutines/commuting_evolution.py
+++ b/pennylane/templates/subroutines/commuting_evolution.py
@@ -18,6 +18,7 @@ Contains the CommutingEvolution template.
 import copy
 
 import pennylane as qml
+from pennylane import math
 from pennylane.operation import Operation
 from pennylane.wires import Wires
 
@@ -130,7 +131,7 @@ class CommutingEvolution(Operation):
                 f"hamiltonian must be a linear combination of pauli words. Got {hamiltonian}"
             )
 
-        trainable_hamiltonian = qml.operation.is_trainable(hamiltonian)
+        trainable_hamiltonian = any(math.requires_grad(d) for d in hamiltonian.data)
         if frequencies is not None and not trainable_hamiltonian:
             c, s = generate_shift_rule(frequencies, shifts).T
             recipe = qml.math.stack([c, qml.math.ones_like(c), s]).T
diff --git a/pennylane/transforms/tape_expand.py b/pennylane/transforms/tape_expand.py
index cf96fdf1b..0eb6c262c 100644
--- a/pennylane/transforms/tape_expand.py
+++ b/pennylane/transforms/tape_expand.py
@@ -18,21 +18,13 @@ import contextlib
 import warnings
 
 import pennylane as qml
-from pennylane.operation import (
-    gen_is_multi_term_hamiltonian,
-    has_gen,
-    has_grad_method,
-    has_nopar,
-    has_unitary_gen,
-    is_measurement,
-    is_trainable,
-    not_tape,
-)
+from pennylane import math
+from pennylane.measurements import MeasurementProcess
 
 
 def _update_trainable_params(tape):
     params = tape.get_parameters(trainable_only=False)
-    tape.trainable_params = qml.math.get_trainable_indices(params)
+    tape.trainable_params = math.get_trainable_indices(params)
 
 
 def create_expand_fn(depth, stop_at=None, device=None, docstring=None):
@@ -65,7 +57,8 @@ def create_expand_fn(depth, stop_at=None, device=None, docstring=None):
     steps, which can be controlled with the argument ``depth``.
     The stopping criterion is easy to write as
 
-    >>> stop_at = ~(qml.operation.has_multipar & qml.operation.is_trainable)
+    >>> def stop_at(obj):
+    ...     return not (len(op.data) > 1 and any(qml.math.requires_grad(d) for d in obj.data))
 
     Then the expansion function can be obtained via
 
@@ -97,7 +90,10 @@ def create_expand_fn(depth, stop_at=None, device=None, docstring=None):
         if stop_at is None:
             stop_at = device.stopping_condition
         else:
-            stop_at &= device.stopping_condition
+            orig_stop_at = stop_at
+
+            def stop_at(obj):
+                return orig_stop_at(obj) and device.stopping_condition(obj)
 
     def expand_fn(tape, depth=depth, **kwargs):
         with qml.QueuingManager.stop_recording():
@@ -133,9 +129,21 @@ Returns:
     .QuantumTape: the expanded tape
 """
 
+
+def _multipar_stopping_fn(obj):
+    try:
+        return (
+            isinstance(obj, MeasurementProcess)
+            or len(obj.data) == 0
+            or (obj.has_generator and len(obj.generator().terms()[0]) == 1)
+        )
+    except qml.operation.TermsUndefinedError:
+        return True
+
+
 expand_multipar = create_expand_fn(
     depth=None,
-    stop_at=not_tape | is_measurement | has_nopar | (has_gen & ~gen_is_multi_term_hamiltonian),
+    stop_at=_multipar_stopping_fn,
     docstring=_expand_multipar_doc,
 )
 
@@ -156,13 +164,14 @@ Returns:
     .QuantumTape: the expanded tape
 """
 
+
+def _trainable_multipar_stopping_fn(obj):
+    return _multipar_stopping_fn(obj) or not any(math.requires_grad(d) for d in obj.data)
+
+
 expand_trainable_multipar = create_expand_fn(
     depth=None,
-    stop_at=not_tape
-    | is_measurement
-    | has_nopar
-    | (~is_trainable)
-    | (has_gen & ~gen_is_multi_term_hamiltonian),
+    stop_at=_trainable_multipar_stopping_fn,
     docstring=_expand_trainable_multipar_doc,
 )
 
@@ -177,17 +186,12 @@ def create_expand_trainable_multipar(tape, use_tape_argnum=False):
     trainable_par_info = [tape.par_info[i] for i in tape.trainable_params]
     trainable_ops = [info["op"] for info in trainable_par_info]
 
-    @qml.BooleanFn
-    def _is_trainable(obj):
-        return obj in trainable_ops
+    def _argnum_trainable_multipar(obj):
+        return _multipar_stopping_fn(obj) or obj not in trainable_ops
 
     return create_expand_fn(
         depth=None,
-        stop_at=not_tape
-        | is_measurement
-        | has_nopar
-        | (~_is_trainable)
-        | (has_gen & ~gen_is_multi_term_hamiltonian),
+        stop_at=_argnum_trainable_multipar,
         docstring=_expand_trainable_multipar_doc,
     )
 
@@ -209,9 +213,18 @@ Returns:
     .QuantumTape: the expanded tape
 """
 
+
+def _expand_nonunitary_gen_stop_at(obj):
+    return (
+        isinstance(obj, MeasurementProcess)
+        or len(obj.data) == 0
+        or (obj.has_generator and obj in qml.ops.qubit.attributes.has_unitary_generator)
+    )
+
+
 expand_nonunitary_gen = create_expand_fn(
     depth=None,
-    stop_at=not_tape | is_measurement | has_nopar | (has_gen & has_unitary_gen),
+    stop_at=_expand_nonunitary_gen_stop_at,
     docstring=_expand_nonunitary_gen_doc,
 )
 
@@ -232,9 +245,18 @@ Returns:
     .QuantumTape: the expanded tape
 """
 
+
+def _stop_at_expand_invalid_trainable(obj):
+    return (
+        isinstance(obj, MeasurementProcess)
+        or not any(math.requires_grad(d) for d in obj.data)
+        or obj.grad_method is not None
+    )
+
+
 expand_invalid_trainable = create_expand_fn(
     depth=None,
-    stop_at=not_tape | is_measurement | (~is_trainable) | has_grad_method,
+    stop_at=_stop_at_expand_invalid_trainable,
     docstring=_expand_invalid_trainable_doc,
 )
 


Test patch:
diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py
index 6479e80c6..7d6f0d59e 100644
--- a/tests/ops/qubit/test_parametric_ops.py
+++ b/tests/ops/qubit/test_parametric_ops.py
@@ -245,7 +245,7 @@ class TestParameterFrequencies:
 
     @pytest.mark.parametrize("op", PARAMETRIZED_OPERATIONS)
     def test_parameter_frequencies_match_generator(self, op, tol):
-        if not qml.operation.has_gen(op):
+        if not op.has_generator:
             pytest.skip(f"Operation {op.name} does not have a generator defined to test against.")
 
         gen = op.generator()
diff --git a/tests/ops/qubit/test_qchem_ops.py b/tests/ops/qubit/test_qchem_ops.py
index a95dfe60a..e7bb28473 100644
--- a/tests/ops/qubit/test_qchem_ops.py
+++ b/tests/ops/qubit/test_qchem_ops.py
@@ -49,7 +49,7 @@ class TestParameterFrequencies:
 
     @pytest.mark.parametrize("op", PARAMETRIZED_QCHEM_OPERATIONS)
     def test_parameter_frequencies_match_generator(self, op, tol):
-        if not qml.operation.has_gen(op):
+        if not op.has_generator:
             pytest.skip(f"Operation {op.name} does not have a generator defined to test against.")
 
         gen = op.generator()
diff --git a/tests/ops/qutrit/test_qutrit_parametric_ops.py b/tests/ops/qutrit/test_qutrit_parametric_ops.py
index 60c962ec2..12aefcc6c 100644
--- a/tests/ops/qutrit/test_qutrit_parametric_ops.py
+++ b/tests/ops/qutrit/test_qutrit_parametric_ops.py
@@ -101,7 +101,7 @@ class TestParameterFrequencies:
     @pytest.mark.parametrize("op", PARAMETRIZED_OPERATIONS)
     def test_parameter_frequencies_match_generator(self, op, tol):
         """Check that parameter frequencies of parametrized operations are defined correctly."""
-        if not qml.operation.has_gen(op):
+        if not op.has_generator:
             pytest.skip(f"Operation {op.name} does not have a generator defined to test against.")
 
         gen = op.generator()
diff --git a/tests/test_operation.py b/tests/test_operation.py
index dc8ef6382..da6810dbf 100644
--- a/tests/test_operation.py
+++ b/tests/test_operation.py
@@ -1610,66 +1610,92 @@ class TestCriteria:
 
     def test_docstring(self):
         expected = "Returns ``True`` if an operator has a generator defined."
-        assert qml.operation.has_gen.__doc__ == expected
+        assert expected in qml.operation.has_gen.__doc__
 
     def test_has_gen(self):
         """Test has_gen criterion."""
-        assert qml.operation.has_gen(self.rx)
-        assert not qml.operation.has_gen(self.cnot)
-        assert not qml.operation.has_gen(self.rot)
-        assert not qml.operation.has_gen(self.exp)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.has_gen(self.rx)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_gen(self.cnot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_gen(self.rot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_gen(self.exp)
 
     def test_has_grad_method(self):
         """Test has_grad_method criterion."""
-        assert qml.operation.has_grad_method(self.rx)
-        assert qml.operation.has_grad_method(self.rot)
-        assert not qml.operation.has_grad_method(self.cnot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.has_grad_method(self.rx)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.has_grad_method(self.rot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_grad_method(self.cnot)
 
     def test_gen_is_multi_term_hamiltonian(self):
         """Test gen_is_multi_term_hamiltonian criterion."""
-        assert qml.operation.gen_is_multi_term_hamiltonian(self.doubleExcitation)
-        assert not qml.operation.gen_is_multi_term_hamiltonian(self.cnot)
-        assert not qml.operation.gen_is_multi_term_hamiltonian(self.rot)
-        assert not qml.operation.gen_is_multi_term_hamiltonian(self.exp)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.gen_is_multi_term_hamiltonian(self.doubleExcitation)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.gen_is_multi_term_hamiltonian(self.cnot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.gen_is_multi_term_hamiltonian(self.rot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.gen_is_multi_term_hamiltonian(self.exp)
 
         class SProdGen(Operator):
 
             def generator(self):
                 return 2.0 * (qml.X(0) + qml.Y(0))
 
-        assert qml.operation.gen_is_multi_term_hamiltonian(SProdGen(wires=0))
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.gen_is_multi_term_hamiltonian(SProdGen(wires=0))
 
         class SumGen(Operator):
 
             def generator(self):
                 return qml.X(0) + qml.Y(1)
 
-        assert qml.operation.gen_is_multi_term_hamiltonian(SumGen(wires=0))
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.gen_is_multi_term_hamiltonian(SumGen(wires=0))
 
     def test_has_multipar(self):
         """Test has_multipar criterion."""
-        assert not qml.operation.has_multipar(self.rx)
-        assert qml.operation.has_multipar(self.rot)
-        assert not qml.operation.has_multipar(self.cnot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_multipar(self.rx)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.has_multipar(self.rot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_multipar(self.cnot)
 
     def test_has_nopar(self):
         """Test has_nopar criterion."""
-        assert not qml.operation.has_nopar(self.rx)
-        assert not qml.operation.has_nopar(self.rot)
-        assert qml.operation.has_nopar(self.cnot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_nopar(self.rx)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_nopar(self.rot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.has_nopar(self.cnot)
 
     def test_has_unitary_gen(self):
         """Test has_unitary_gen criterion."""
-        assert qml.operation.has_unitary_gen(self.rx)
-        assert not qml.operation.has_unitary_gen(self.rot)
-        assert not qml.operation.has_unitary_gen(self.cnot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.has_unitary_gen(self.rx)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_unitary_gen(self.rot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.has_unitary_gen(self.cnot)
 
     def test_is_measurement(self):
         """Test is_measurement criterion."""
-        assert not qml.operation.is_measurement(self.rx)
-        assert not qml.operation.is_measurement(self.rot)
-        assert not qml.operation.is_measurement(self.cnot)
-        assert qml.operation.is_measurement(self.exp)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.is_measurement(self.rx)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.is_measurement(self.rot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.is_measurement(self.cnot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.is_measurement(self.exp)
 
     def test_is_trainable(self):
         """Test is_trainable criterion."""
@@ -1682,10 +1708,27 @@ class TestCriteria:
     def test_composed(self):
         """Test has_gen criterion."""
         both = qml.operation.has_gen & qml.operation.is_trainable
-        assert both(self.rx)
-        assert not both(self.cnot)
-        assert not both(self.rot)
-        assert not both(self.exp)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert both(self.rx)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not both(self.cnot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not both(self.rot)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not both(self.exp)
+
+    def test_not_tape(self):
+        """Test the not_tape criterion."""
+
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.not_tape(2)
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert not qml.operation.not_tape(qml.tape.QuantumScript())
+
+    def test_defines_diagonalizing_gates(self):
+        """Test the defines_diagonalizing_gates criterion."""
+        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+            assert qml.operation.defines_diagonalizing_gates(qml.X(0))
 
 
 pairs_of_ops = [
diff --git a/tests/transforms/test_tape_expand.py b/tests/transforms/test_tape_expand.py
index f390d6463..f7b33c8f1 100644
--- a/tests/transforms/test_tape_expand.py
+++ b/tests/transforms/test_tape_expand.py
@@ -25,10 +25,15 @@ import pennylane as qml
 from pennylane.wires import Wires
 
 
+def crit_0(op: qml.operation.Operator):
+    return not any(qml.math.requires_grad(d) for d in op.data) or (
+        op.has_generator and any(qml.math.requires_grad(d) for d in op.data)
+    )
+
+
 class TestCreateExpandFn:
     """Test creating expansion functions from stopping criteria."""
 
-    crit_0 = (~qml.operation.is_trainable) | (qml.operation.has_gen & qml.operation.is_trainable)
     doc_0 = "Test docstring."
     with qml.queuing.AnnotatedQueue() as q:
         qml.RX(0.2, wires=0)
@@ -41,14 +46,14 @@ class TestCreateExpandFn:
         """Test creation of expand_fn."""
         expand_fn = qml.transforms.create_expand_fn(
             depth=10,
-            stop_at=self.crit_0,
+            stop_at=crit_0,
             docstring=self.doc_0,
         )
         assert expand_fn.__doc__ == "Test docstring."
 
     def test_create_expand_fn_expansion(self):
         """Test expansion with created expand_fn."""
-        expand_fn = qml.transforms.create_expand_fn(depth=10, stop_at=self.crit_0)
+        expand_fn = qml.transforms.create_expand_fn(depth=10, stop_at=crit_0)
         new_tape = expand_fn(self.tape)
         assert new_tape.operations[0] == self.tape.operations[0]
         assert new_tape.operations[1] == self.tape.operations[1]
@@ -58,7 +63,7 @@ class TestCreateExpandFn:
 
     def test_create_expand_fn_dont_expand(self):
         """Test expansion is skipped with depth=0."""
-        expand_fn = qml.transforms.create_expand_fn(depth=0, stop_at=self.crit_0)
+        expand_fn = qml.transforms.create_expand_fn(depth=0, stop_at=crit_0)
 
         new_tape = expand_fn(self.tape)
         assert new_tape.operations == self.tape.operations
@@ -68,7 +73,7 @@ class TestCreateExpandFn:
         that all operations are expanded to match the devices default gate
         set"""
         dev = DefaultQubitLegacy(wires=1)
-        expand_fn = qml.transforms.create_expand_fn(device=dev, depth=10, stop_at=self.crit_0)
+        expand_fn = qml.transforms.create_expand_fn(device=dev, depth=10, stop_at=crit_0)
 
         with qml.queuing.AnnotatedQueue() as q:
             qml.U1(0.2, wires=0)