Repository: PennyLaneAI/pennylane
Pull request #7936 (2025-07-21 17:56:40)

update `.git-blame-ignore-revs` to ignore refactoring PR #7860
**Context:**

https://github.com/PennyLaneAI/pennylane/pull/7860 did a refactor which touched a lot of files - to clean up git blame history we can add it to this file.

[sc-95335]



Patch:
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 660e2bcde..01d8950c9 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -3,3 +3,6 @@
 
 # Update EOL character for many files from CRLF to LF (https://github.com/PennyLaneAI/pennylane/pull/7502)
 c73e73b19c8d38e63b41ca4c8039d9f430c55da1
+
+# Use `ruff` and `pyupgrade` to support `3.11`+ Python language features (https://github.com/PennyLaneAI/pennylane/pull/7860)
+a565cd69ad025796923f0354d084f9ca7267ab18
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index f4f705ddf..5bf772966 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -39,27 +39,6 @@ Pending deprecations
   - Deprecated in v0.43
   - Will be removed in v0.44
 
-* ``qml.operation.Observable`` and the accompanying ``Observable.compare`` methods are deprecated. At this point, ``Observable`` only
-  provides a default value of ``is_hermitian=True`` and prevents the object from being processed into a tape. Instead of inheriting from
-  ``Observable``, operator developers should manually set ``is_hermitian = True`` and update the ``queue`` function to stop it from being
-  processed into the circuit.
-
-  .. code-block:: python
-
-      class MyObs(Operator):
-      
-          is_hermitian = True
-
-          def queue(self, context=qml.QueuingManager):
-              return self
-
-  To check if an operator is likely to be hermitian, the ``op.is_hermitian`` property can be checked.
-
-  ``qml.equal`` and ``op1 == op2`` should be used to compare instances instead of ``op1.compare(op2)``.
-
-  - 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``,
@@ -137,6 +116,27 @@ for details on how to port your legacy code to the new system. The following fun
 Completed deprecation cycles
 ----------------------------
 
+* ``qml.operation.Observable`` and the accompanying ``Observable.compare`` methods are deprecated. At this point, ``Observable`` only
+  provides a default value of ``is_hermitian=True`` and prevents the object from being processed into a tape. Instead of inheriting from
+  ``Observable``, operator developers should manually set ``is_hermitian = True`` and update the ``queue`` function to stop it from being
+  processed into the circuit.
+
+  .. code-block:: python
+
+      class MyObs(Operator):
+      
+          is_hermitian = True
+
+          def queue(self, context=qml.QueuingManager):
+              return self
+
+  To check if an operator is likely to be hermitian, the ``op.is_hermitian`` property can be checked.
+
+  ``qml.equal`` and ``op1 == op2`` should be used to compare instances instead of ``op1.compare(op2)``.
+
+  - Deprecated in v0.42
+  - Removed in v0.43
+
 * ``qml.operation.WiresEnum``, ``qml.operation.AllWires``, and ``qml.operation.AnyWires`` are deprecated. If an operation 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 overridden unless the operator developer wants to validate that the correct number of wires is passed.
diff --git a/doc/introduction/interfaces.rst b/doc/introduction/interfaces.rst
index 257149bd5..fe92907f3 100644
--- a/doc/introduction/interfaces.rst
+++ b/doc/introduction/interfaces.rst
@@ -195,6 +195,7 @@ PennyLane offers a built-in Jax-based implementation compatible with ``jax.jit``
     :nosignatures:
 
     ~pennylane.QNGOptimizerQJIT
+    ~pennylane.MomentumQNGOptimizerQJIT
 
 :html:`</div>`
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 3c034a3b4..f76a5e648 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -4,17 +4,59 @@
 
 <h3>New features since last release</h3>
 
+* Leveraging quantum just-in-time compilation to optimize parameterized hybrid workflows with the momentum
+  quantum natural gradient optimizer is now possible with the new :class:`~.MomentumQNGOptimizerQJIT` optimizer.
+  [(#7606)](https://github.com/PennyLaneAI/pennylane/pull/7606)
+
+  Similar to the :class:`~.QNGOptimizerQJIT` optimizer, :class:`~.MomentumQNGOptimizerQJIT` offers a
+  `qml.qjit`-compatible analogue to the existing :class:`~.MomentumQNGOptimizer` with an Optax-like interface:
+
+  ```python
+  import pennylane as qml
+  import jax.numpy as jnp
+
+  dev = qml.device("lightning.qubit", wires=2)
+
+  @qml.qnode(dev)
+  def circuit(params):
+      qml.RX(params[0], wires=0)
+      qml.RY(params[1], wires=1)
+      return qml.expval(qml.Z(0) + qml.X(1))
+
+  opt = qml.MomentumQNGOptimizerQJIT(stepsize=0.1, momentum=0.2)
+
+  @qml.qjit
+  def update_step_qjit(i, args):
+      params, state = args
+      return opt.step(circuit, params, state)
+
+  @qml.qjit
+  def optimization_qjit(params, iters):
+      state = opt.init(params)
+      args = (params, state)
+      params, state = qml.for_loop(iters)(update_step_qjit)(args)
+      return params
+  ```
+
+  ```pycon
+  >>> params = jnp.array([0.1, 0.2])
+  >>> iters = 1000
+  >>> optimization_qjit(params=params, iters=iters)
+  Array([ 3.14159265, -1.57079633], dtype=float64)
+  ```
+
 <h3>Improvements 🛠</h3>
 
 <h4>OpenQASM-PennyLane interoperability</h4>
 
 * The :func:`qml.from_qasm3` function can now convert OpenQASM 3.0 circuits that contain
-  subroutines, constants, all remaining stdlib gates, and built-in mathematical functions.
+  subroutines, constants, all remaining stdlib gates, qubit registers, and built-in mathematical functions.
   [(#7651)](https://github.com/PennyLaneAI/pennylane/pull/7651)
   [(#7653)](https://github.com/PennyLaneAI/pennylane/pull/7653)
   [(#7676)](https://github.com/PennyLaneAI/pennylane/pull/7676)
   [(#7679)](https://github.com/PennyLaneAI/pennylane/pull/7679)
   [(#7677)](https://github.com/PennyLaneAI/pennylane/pull/7677)
+  [(#7767)](https://github.com/PennyLaneAI/pennylane/pull/7767)
   [(#7690)](https://github.com/PennyLaneAI/pennylane/pull/7690)
 
 <h4>Other improvements</h4>
@@ -85,6 +127,11 @@
 
 <h3>Breaking changes 💔</h3>
 
+* `qml.operation.Observable` and the corresponding `Observable.compare` have been removed, as
+  PennyLane now depends on the more general `Operator` interface instead. The
+  `Operator.is_hermitian` property can instead be used to check whether or not it is highly likely
+  that the operator instance is Hermitian.
+
 * `qml.operation.WiresEnum`, `qml.operation.AllWires`, and `qml.operation.AnyWires` have been removed. Setting `Operator.num_wires = None` (the default)
   should instead indicate that the `Operator` does not need wire validation.
   [(#7911)](https://github.com/PennyLaneAI/pennylane/pull/7911)
@@ -194,6 +241,7 @@ Utkarsh Azad,
 Joey Carter,
 Yushao Chen,
 Marcus Edwards,
+Simone Gasperini,
 David Ittah,
 Erick Ochoa,
 Mudit Pandey,
diff --git a/pennylane/_deprecated_observable.py b/pennylane/_deprecated_observable.py
deleted file mode 100644
index a95e212fa..000000000
--- a/pennylane/_deprecated_observable.py
+++ /dev/null
@@ -1,98 +0,0 @@
-# Copyright 2025 Xanadu Quantum Technologies Inc.
-# 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
-
-#     http://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.
-"""
-This module contains the deprecated Observable class to allow for deprecation warnings on access to the class.
-"""
-import warnings
-from typing import Union
-
-import pennylane as qml
-
-from .operation import Operation, Operator
-
-
-class Observable(Operator):
-    """Base class representing observables.
-
-    .. warning::
-
-        ``qml.operation.Observable`` is now deprecated. A generic operator can be used anywhere an ``Observable``
-        can, and is less restrictive.  To preserve prior ``Observable`` default behavior, an operator can override
-        ``Operator.queue()`` with empty behavior, and set ``is_hermitian = True`` manually:
-
-        .. code-block:: python
-
-            class MyObs(Operator):
-
-                is_hermitian = True
-
-                def queue(self, context=qml.QueuingManager):
-                    return self
-
-
-
-    Args:
-        params (tuple[tensor_like]): trainable parameters
-        wires (Iterable[Any] or Any): Wi're label(s) that the operator acts on.
-            If not given, args[-1] is interpreted as wires.
-        id (str): custom label given to an operator instance,
-            can be useful for some applications where the instance has to be identified
-    """
-
-    @property
-    def _queue_category(self):
-        return "_ops" if isinstance(self, Operation) else None
-
-    @property
-    def is_hermitian(self) -> bool:
-        """All observables must be hermitian"""
-        return True
-
-    def compare(self, other: Union["Observable", "qml.ops.LinearCombination"]) -> bool:
-        r"""Compares with another :class:`~Observable`, to determine if they are equivalent.
-
-        .. warning::
-
-            This method is deprecated. ``qml.equal`` or ``op1 == op2`` should be used instead.
-
-        Observables are equivalent if they represent the same operator
-        (their matrix representations are equal), and they are defined on the same wires.
-
-        .. Warning::
-
-            The compare method does **not** check if the matrix representation
-            of a :class:`~.Hermitian` observable is equal to an equivalent
-            observable expressed in terms of Pauli matrices.
-            To do so would require the matrix form to be calculated, which would
-            drastically increase runtime.
-
-        Returns:
-            (bool): True if equivalent.
-
-        **Examples**
-
-        >>> ob1 = qml.X(0) @ qml.Identity(1)
-        >>> ob2 = qml.Hamiltonian([1], [qml.X(0)])
-        >>> ob1.compare(ob2)
-        True
-        >>> ob1 = qml.X(0)
-        >>> ob2 = qml.Hermitian(np.array([[0, 1], [1, 0]]), 0)
-        >>> ob1.compare(ob2)
-        False
-        """
-        warnings.warn(
-            "The compare method is deprecated and will be removed in v0.43."
-            " op1 == op2 or qml.equal should be used instead.",
-            qml.exceptions.PennyLaneDeprecationWarning,
-        )
-        return qml.equal(self, other)
diff --git a/pennylane/io/io.py b/pennylane/io/io.py
index bce9b538c..fc6d615e9 100644
--- a/pennylane/io/io.py
+++ b/pennylane/io/io.py
@@ -979,8 +979,8 @@ def from_qasm3(quantum_circuit: str, wire_map: dict = None):
     Converts an OpenQASM 3.0 circuit into a quantum function that can be used within a QNode.
 
     .. note::
-        The standard library gates, built-in mathematical functions and constants, subroutines, variables,
-        control flow, measurements, inputs, outputs, custom gates and ``end`` statements are all supported.
+        The standard library gates, qubit registers, built-in mathematical functions and constants, subroutines,
+        variables, control flow, measurements, inputs, outputs, custom gates and ``end`` statements are all supported.
         Pulses are not yet supported.
 
         In order to use this function, ``openqasm3`` and ``'openqasm3[parser]'`` must be installed in the user's
diff --git a/pennylane/io/qasm_interpreter.py b/pennylane/io/qasm_interpreter.py
index d1e987395..6a5a27894 100644
--- a/pennylane/io/qasm_interpreter.py
+++ b/pennylane/io/qasm_interpreter.py
@@ -277,6 +277,8 @@ class Context:
             context["aliases"] = {}
         if "wires" not in context:
             context["wires"] = []
+        if "registers" not in context:
+            context["registers"] = {}
         if "scopes" not in context:
             context["scopes"] = {"subroutines": {}, "custom_gates": {}}
         if "wire_map" not in context or context["wire_map"] is None:
@@ -300,6 +302,7 @@ class Context:
                 "params": [_resolve_name(param) for param in node.arguments]
                 + [_resolve_name(qubit) for qubit in node.qubits],
                 "wires": copy.deepcopy(self.wires),
+                "registers": copy.deepcopy(self.registers),
                 "name": node.name.name,
                 # we want subroutines declared in the global scope to be available
                 "scopes": {
@@ -323,6 +326,7 @@ class Context:
                 "vars": {k: v for k, v in self.vars.items() if v.constant},  # same namespace
                 "wire_map": {},
                 "wires": copy.deepcopy(self.wires),
+                "registers": copy.deepcopy(self.registers),
                 "name": node.name.name,
                 # we want subroutines declared in the global scope to be available
                 "scopes": {
@@ -354,6 +358,8 @@ class Context:
             if res.val is not None:
                 return res
             raise ValueError(f"Attempt to reference uninitialized parameter {name}!")
+        if name in self.registers:
+            return self.registers[name]
         if name in self.wires:
             return name
         if name in self.aliases:
@@ -393,7 +399,7 @@ class Context:
         Raises:
             NameError: If the context is missing a wire.
         """
-        missing_wires = set(wires) - set(self.wires)
+        missing_wires = set(wires) - (set(self.wires + list(self.registers)))
         if len(missing_wires) > 0:
             raise NameError(
                 f"Attempt to reference wire(s): {missing_wires} that have not been declared in {self.name}"
@@ -596,8 +602,11 @@ class QasmInterpreter:
             node (QuantumMeasurementStatement): the quantum measurement statement to register.
             context (Context): the current context.
         """
-        wire = self.visit(node.measure.qubit, context)
-        res = measure(context.wire_map.get(wire, wire))
+        wires = self.visit(node.measure.qubit, context)
+        if isinstance(wires, list):
+            res = [measure(context.wire_map.get(wire, wire)) for wire in wires]
+        else:
+            res = measure(context.wire_map.get(wires, wires))
         if node.target is not None:
             name = _resolve_name(node.target)  # str or Identifier
             context.vars[name].val = res
@@ -829,8 +838,33 @@ class QasmInterpreter:
             node (QASMNode): the quantum reset node.
             context (dict): the current context.
         """
-        wire = self.visit(node.qubits, context)
-        measure(context.wire_map.get(wire, wire), reset=True)
+        wires = self.visit(node.qubits, context)
+        if isinstance(wires, list):
+            for wire in wires:
+                measure(context.wire_map.get(wire, wire), reset=True)
+        else:
+            measure(context.wire_map.get(wires, wires), reset=True)
+
+    @staticmethod
+    def _bind_quantum_parameter(param, evald_arg, inner_context, context):
+        """
+        Binds a quantum parameter in a subroutine or custom gate context.
+
+        Args:
+            param (str): the name of the quantum parameter to bind.
+            evald_arg (str): the name of the qubit.
+            inner_context (Context): the custom gate or subroutine context.
+            context (Context): the outer context.
+        """
+        reg = False
+        if evald_arg in context.registers:
+            reg = True
+            del inner_context.wires[inner_context.wires.index(param)]
+            inner_context.registers[param] = context.registers[evald_arg]
+        elif evald_arg in context.wire_map:
+            evald_arg = context.wire_map[evald_arg]
+        if param != evald_arg and not reg:
+            inner_context.wire_map[param] = evald_arg
 
     def execute_custom_gate(self, node: ast.QuantumGate, context: Context):
         """
@@ -853,10 +887,7 @@ class QasmInterpreter:
                     evald_arg.__class__.__name__, evald_arg, None, node.span.start_line, False
                 )
             else:
-                if evald_arg in context.wire_map:
-                    evald_arg = context.wire_map[evald_arg]
-                if param != evald_arg:
-                    gate_context.wire_map[param] = evald_arg
+                self._bind_quantum_parameter(param, evald_arg, gate_context, context)
 
         # execute the subroutine
         self.visit(gate_context.body, gate_context)
@@ -885,13 +916,20 @@ class QasmInterpreter:
         func_context.context["return"] = None
 
         # bind subroutine arguments
-        evald_args = [self.visit(raw_arg, context) for raw_arg in node.arguments]
+        evald_args = [
+            # visit will resolve a qubit name to itself, but a register name to a list of its member qubits,
+            # we don't want to dereference yet... to behave consistently with execute_custom_gate
+            (
+                _resolve_name(raw_arg)
+                if isinstance(raw_arg, (str, ast.Identifier))
+                and _resolve_name(raw_arg) in context.registers
+                else self.visit(raw_arg, context)
+            )
+            for raw_arg in node.arguments
+        ]
         for evald_arg, param in list(zip(evald_args, func_context.params)):
             if isinstance(evald_arg, str):  # this would indicate a quantum parameter
-                if evald_arg in context.wire_map:
-                    evald_arg = context.wire_map[evald_arg]
-                if evald_arg != param:
-                    func_context.wire_map[param] = evald_arg
+                self._bind_quantum_parameter(param, evald_arg, func_context, context)
             else:
                 func_context.vars[param] = Variable(
                     evald_arg.__class__.__name__,
@@ -1043,11 +1081,13 @@ class QasmInterpreter:
         Raises:
             TypeError: if it is a qubit register declaration.
         """
-        if node.size is not None:
-            raise TypeError(
-                "Qubit registers are not yet supported, please declare each qubit individually."
-            )
-        context.wires.append(node.qubit.name)
+        if node.size is not None and isinstance(self.visit(node.size, context), int):
+            context.registers[node.qubit.name] = []
+            for i in range(self.visit(node.size, context)):
+                context.wires.append(f"{node.qubit.name}[{i}]")
+                context.registers[node.qubit.name].append(f"{node.qubit.name}[{i}]")
+        else:
+            context.wires.append(node.qubit.name)
 
     @visit.register(ast.ClassicalAssignment)
     def visit_classical_assignment(self, node: ast.ClassicalAssignment, context: Context):
@@ -1281,15 +1321,43 @@ class QasmInterpreter:
         gate = gates_dict[node.name.name.upper()]
 
         # setup wires
-        wires = [_resolve_name(node.qubits[q]) for q in range(len(node.qubits))]
-
-        context.require_wires(wires)
+        wires = []
+        require_wires = []
+        for qubit in node.qubits:
+            if (not hasattr(qubit, "indices")) or qubit.indices is None:
+                # we are dealing with a wire label directly
+                wire = _resolve_name(qubit)
+                # require the qubit to have been declared
+                require_wires.append(wire)
+                # resolve any wire relabelling (mapping pennylane wires to qasm wires, or renaming between func contexts)
+                wires.append(context.wire_map[wire] if wire in context.wire_map else wire)
+            elif len(qubit.indices) == 1 and len(qubit.indices[0]) == 1:
+                # we are dealing with an index into a register
+                register = _resolve_name(qubit)
+                # required the register to have been declared
+                require_wires.append(register)
+                # evaluate the register to a list of the qubits that compose it
+                reg_var = context.retrieve_variable(register)
+                # evaluate the index into the register to a literal
+                index = self.visit(qubit.indices[0][0], context)
+                # check that the index is not out of bounds
+                if index < len(reg_var):
+                    # index into the register and return the wire acted on
+                    wires.append(reg_var[index])
+                else:
+                    raise IndexError(
+                        f"Index {index} into register {register} of length {len(reg_var)} out of bounds on line {node.span.start_line}."
+                    )
+            else:
+                raise (
+                    NotImplementedError(
+                        "Only a single Expression or Index is supported for indexing into registers."
+                    )
+                )
 
-        resolved_wires = list(
-            map(lambda wire: context.wire_map[wire] if wire in context.wire_map else wire, wires)
-        )
+        context.require_wires(require_wires)
 
-        return gate, args, resolved_wires
+        return gate, args, wires
 
     def apply_modifier(
         self, mod: ast.QuantumGate, previous: Operator, context: Context, wires: list
diff --git a/pennylane/operation.py b/pennylane/operation.py
index bd626e843..80d843c84 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -110,7 +110,6 @@ Operator Types
 
     ~Operator
     ~Operation
-    ~Observable
     ~CV
     ~CVObservable
     ~CVOperation
@@ -2575,17 +2574,6 @@ def gen_is_multi_term_hamiltonian(obj):
 
 def __getattr__(name):
     """To facilitate StatePrep rename"""
-    if name == "Observable":
-        from ._deprecated_observable import Observable  # pylint: disable=import-outside-toplevel
-
-        warnings.warn(
-            "Observable is deprecated and will be removed in v0.43. "
-            "A generic Operator class should be used instead. "
-            "If defining an Operator, set the is_hermitian property to True. "
-            "If checking if an Operator is Hermitian, check the is_hermitian property. ",
-            PennyLaneDeprecationWarning,
-        )
-        return Observable
     if name == "StatePrep":
         return StatePrepBase
     raise AttributeError(f"module 'pennylane.operation' has no attribute '{name}'")
diff --git a/pennylane/ops/op_math/adjoint.py b/pennylane/ops/op_math/adjoint.py
index 565bb5b4d..df78d1c80 100644
--- a/pennylane/ops/op_math/adjoint.py
+++ b/pennylane/ops/op_math/adjoint.py
@@ -19,7 +19,6 @@ from functools import lru_cache, partial
 from typing import overload
 
 import pennylane as qml
-from pennylane._deprecated_observable import Observable
 from pennylane.capture.autograph import wraps
 from pennylane.compiler import compiler
 from pennylane.math import conj, moveaxis, transpose
@@ -312,17 +311,6 @@ class Adjoint(SymbolicOp):
         >>> op.grad_method
         'A'
 
-        If the base class is an ``Observable`` instead, the ``Adjoint`` will be an ``Observable`` as
-        well.
-
-        >>> op = Adjoint(1.0 * qml.X(0))
-        >>> isinstance(op, qml.operation.Observable)
-        True
-        >>> isinstance(op, qml.operation.Operation)
-        False
-        >>> Adjoint(qml.X(0)) @ qml.Y(1)
-        (Adjoint(X(0))) @ Y(1)
-
     """
 
     resource_keys = {"base_class", "base_params"}
@@ -340,21 +328,13 @@ class Adjoint(SymbolicOp):
         """Returns an uninitialized type with the necessary mixins.
 
         If the ``base`` is an ``Operation``, this will return an instance of ``AdjointOperation``.
-        If ``Observable`` but not ``Operation``, it will be ``AdjointObs``.
-        And if both, it will be an instance of ``AdjointOpObs``.
 
         """
 
         if isinstance(base, Operation):
-            if isinstance(base, Observable):
-                return object.__new__(AdjointOpObs)
-
             # not an observable
             return object.__new__(AdjointOperation)
 
-        if isinstance(base, Observable):
-            return object.__new__(AdjointObs)
-
         return object.__new__(Adjoint)
 
     def __init__(self, base=None, id=None):
@@ -491,22 +471,4 @@ class AdjointOperation(Adjoint, Operation):
         return -1 * self.base.generator()
 
 
-class AdjointObs(Adjoint, Observable):
-    """A child of :class:`~.Adjoint` that also inherits from :class:`~.Observable`."""
-
-    def __new__(cls, *_, **__):
-        return object.__new__(cls)
-
-
-# TODO: Remove when PL supports pylint==3.3.6 (it is considered a useless-suppression) [sc-91362]
-# pylint: disable=too-many-ancestors
-class AdjointOpObs(AdjointOperation, Observable):
-    """A child of :class:`~.AdjointOperation` that also inherits from :class:`~.Observable."""
-
-    def __new__(cls, *_, **__):
-        return object.__new__(cls)
-
-
 AdjointOperation._primitive = Adjoint._primitive  # pylint: disable=protected-access
-AdjointObs._primitive = Adjoint._primitive  # pylint: disable=protected-access
-AdjointOpObs._primitive = Adjoint._primitive  # pylint: disable=protected-access
diff --git a/pennylane/ops/op_math/linear_combination.py b/pennylane/ops/op_math/linear_combination.py
index 0dacf9adc..bee71b4c0 100644
--- a/pennylane/ops/op_math/linear_combination.py
+++ b/pennylane/ops/op_math/linear_combination.py
@@ -16,7 +16,6 @@ LinearCombination class
 """
 import itertools
 import numbers
-import warnings
 
 # pylint: disable=too-many-arguments,protected-access
 from copy import copy
@@ -313,58 +312,6 @@ class LinearCombination(Sum):
         coeffs, ops, pr = self._simplify_coeffs_ops(self.coeffs, self.ops, self.pauli_rep, cutoff)
         return LinearCombination(coeffs, ops, _pauli_rep=pr)
 
-    def compare(self, other):
-        r"""Determines mathematical equivalence between operators
-
-        ``LinearCombination`` and other operators are equivalent if they mathematically represent the same operator
-        (their matrix representations are equal), acting on the same wires.
-
-        .. Warning::
-
-            This method does not compute explicit matrices but uses the underlyding operators and coefficients for comparisons. When both operators
-            consist purely of Pauli operators, and therefore have a valid ``op.pauli_rep``, the comparison is cheap.
-            When that is not the case (e.g. one of the operators contains a ``Hadamard`` gate), it can be more expensive as it involves mathematical simplification of both operators.
-
-        Returns:
-            (bool): True if equivalent.
-
-        **Examples**
-
-        >>> H = qml.ops.LinearCombination(
-        ...     [0.5, 0.5],
-        ...     [qml.PauliZ(0) @ qml.PauliY(1), qml.PauliY(1) @ qml.PauliZ(0) @ qml.Identity("a")]
-        ... )
-        >>> obs = qml.PauliZ(0) @ qml.PauliY(1)
-        >>> print(H.compare(obs))
-        True
-
-        >>> H1 = qml.ops.LinearCombination([1, 1], [qml.PauliX(0), qml.PauliZ(1)])
-        >>> H2 = qml.ops.LinearCombination([1, 1], [qml.PauliZ(0), qml.PauliX(1)])
-        >>> H1.compare(H2)
-        False
-
-        >>> ob1 = qml.ops.LinearCombination([1], [qml.PauliX(0)])
-        >>> ob2 = qml.Hermitian(np.array([[0, 1], [1, 0]]), 0)
-        >>> ob1.compare(ob2)
-        False
-        """
-        warnings.warn(
-            "The compare method is deprecated and will be removed in v0.43."
-            " op1 == op2 or qml.equal should be used instead.",
-            qml.exceptions.PennyLaneDeprecationWarning,
-        )
-        if isinstance(other, (Operator)):
-            if (pr1 := self.pauli_rep) is not None and (pr2 := other.pauli_rep) is not None:
-                pr1.simplify()
-                pr2.simplify()
-                return pr1 == pr2
-
-            op1 = self.simplify()
-            op2 = other.simplify()
-            return qml.equal(op1, op2)
-
-        raise ValueError("Can only compare a LinearCombination and an Operator.")
-
     def __matmul__(self, other: Operator) -> Operator:
         """The product operation between Operator objects."""
         if isinstance(other, LinearCombination):
diff --git a/pennylane/ops/op_math/pow.py b/pennylane/ops/op_math/pow.py
index 5f8c1fd0d..1828b2de3 100644
--- a/pennylane/ops/op_math/pow.py
+++ b/pennylane/ops/op_math/pow.py
@@ -21,7 +21,6 @@ from scipy.linalg import fractional_matrix_power
 
 import pennylane as qml
 from pennylane import math as qmlmath
-from pennylane._deprecated_observable import Observable
 from pennylane.operation import (
     AdjointUndefinedError,
     DecompositionUndefinedError,
@@ -163,15 +162,10 @@ class Pow(ScalarSymbolicOp):
         """
 
         if isinstance(base, Operation):
-            if isinstance(base, Observable):
-                return object.__new__(PowOpObs)
 
             # not an observable
             return object.__new__(PowOperation)
 
-        if isinstance(base, Observable):
-            return object.__new__(PowObs)
-
         return object.__new__(Pow)
 
     def __init__(self, base=None, z=1, id=None):
@@ -430,20 +424,3 @@ class PowOperation(Pow, Operation):
     @property
     def control_wires(self):
         return self.base.control_wires
-
-
-class PowObs(Pow, Observable):
-    """A child class of ``Pow`` that also inherits from ``Observable``."""
-
-    def __new__(cls, *_, **__):
-        return object.__new__(cls)
-
-
-# pylint: disable=too-many-ancestors
-class PowOpObs(PowOperation, Observable):
-    """A child class of ``Pow`` that inherits from both
-    ``Observable`` and ``Operation``.
-    """
-
-    def __new__(cls, *_, **__):
-        return object.__new__(cls)
diff --git a/pennylane/ops/qubit/non_parametric_ops.py b/pennylane/ops/qubit/non_parametric_ops.py
index f38055ea9..234b5b880 100644
--- a/pennylane/ops/qubit/non_parametric_ops.py
+++ b/pennylane/ops/qubit/non_parametric_ops.py
@@ -25,7 +25,6 @@ from scipy import sparse
 
 import pennylane as qml
 from pennylane import math
-from pennylane._deprecated_observable import Observable
 from pennylane.decomposition import (
     add_decomps,
     adjoint_resource_rep,
@@ -47,7 +46,7 @@ from pennylane.wires import Wires, WiresLike
 INV_SQRT2 = 1 / qml.math.sqrt(2)
 
 
-class Hadamard(Observable, Operation):
+class Hadamard(Operation):
     r"""Hadamard(wires)
     The Hadamard operator
 
@@ -65,6 +64,7 @@ class Hadamard(Observable, Operation):
     """
 
     is_hermitian = True
+    _queue_category = "_ops"
 
     num_wires = 1
     """int: Number of wires that the operator acts on."""
@@ -305,7 +305,7 @@ def _controlled_hadamard(wires, control_wires, work_wires, work_wire_type, **__)
 add_decomps("C(Hadamard)", flip_zero_control(_controlled_hadamard))
 
 
-class PauliX(Observable, Operation):
+class PauliX(Operation):
     r"""
     The Pauli X operator
 
@@ -337,6 +337,7 @@ class PauliX(Observable, Operation):
     batch_size = None
 
     _queue_category = "_ops"
+    is_hermitian = True
 
     @property
     def pauli_rep(self):
@@ -588,7 +589,7 @@ def _controlled_x_decomp(
 add_decomps("C(PauliX)", _controlled_x_decomp)
 
 
-class PauliY(Observable, Operation):
+class PauliY(Operation):
     r"""
     The Pauli Y operator
 
@@ -845,7 +846,7 @@ def _controlled_y_decomp(*_, wires, control_wires, work_wires, work_wire_type, *
 add_decomps("C(PauliY)", flip_zero_control(_controlled_y_decomp))
 
 
-class PauliZ(Observable, Operation):
+class PauliZ(Operation):
     r"""
     The Pauli Z operator
 
@@ -863,6 +864,7 @@ class PauliZ(Observable, Operation):
     """
 
     is_hermitian = True
+    _queue_category = "_ops"
     num_wires = 1
     num_params = 0
     """int: Number of trainable parameters that the operator depends on."""
diff --git a/pennylane/ops/qubit/observables.py b/pennylane/ops/qubit/observables.py
index 6a78d8702..acaa50aff 100644
--- a/pennylane/ops/qubit/observables.py
+++ b/pennylane/ops/qubit/observables.py
@@ -24,15 +24,14 @@ import numpy as np
 from scipy.sparse import csr_matrix, spmatrix
 
 import pennylane as qml
-from pennylane._deprecated_observable import Observable
-from pennylane.operation import Operation
+from pennylane.operation import Operation, Operator
 from pennylane.typing import TensorLike
 from pennylane.wires import Wires, WiresLike
 
 from .matrix_ops import QubitUnitary
 
 
-class Hermitian(Observable):
+class Hermitian(Operator):
     r"""
     An arbitrary Hermitian observable.
 
@@ -262,7 +261,7 @@ class Hermitian(Observable):
         return self.compute_diagonalizing_gates(self.eigendecomposition["eigvec"], self.wires)
 
 
-class SparseHamiltonian(Observable):
+class SparseHamiltonian(Operator):
     r"""
     A Hamiltonian represented directly as a sparse matrix in Compressed Sparse Row (CSR) format.
 
@@ -405,7 +404,7 @@ class SparseHamiltonian(Observable):
         return H
 
 
-class Projector(Observable):
+class Projector(Operator):
     r"""Projector(state, wires, id=None)
     Observable corresponding to the state projector :math:`P=\ket{\phi}\bra{\phi}`.
 
@@ -453,6 +452,7 @@ class Projector(Observable):
     is_hermitian = True
     name = "Projector"
     num_params = 1
+    _queue_category = None
     """int: Number of trainable parameters that the operator depends on."""
 
     ndim_params = (1,)
@@ -505,6 +505,7 @@ class BasisStateProjector(Projector, Operation):
     :math:`\phi` denotes a basis state."""
 
     grad_method = None
+    _queue_category = "_ops"
 
     # The call signature should be the same as Projector.__new__ for the positional
     # arguments, but with free key word arguments.
diff --git a/pennylane/ops/qutrit/observables.py b/pennylane/ops/qutrit/observables.py
index d69f2cbc5..5b3c455d6 100644
--- a/pennylane/ops/qutrit/observables.py
+++ b/pennylane/ops/qutrit/observables.py
@@ -17,7 +17,7 @@ This submodule contains the qutrit quantum observables.
 import numpy as np
 
 import pennylane as qml
-from pennylane._deprecated_observable import Observable
+from pennylane.operation import Operator
 from pennylane.ops.qubit import Hermitian
 from pennylane.ops.qutrit import QutritUnitary
 
@@ -150,7 +150,7 @@ class THermitian(Hermitian):
         return [QutritUnitary(eigenvectors.conj().T, wires=wires)]
 
 
-class GellMann(Observable):
+class GellMann(Operator):
     r"""
     The Gell-Mann observables for qutrits
 
diff --git a/pennylane/optimize/__init__.py b/pennylane/optimize/__init__.py
index 8ffe29ced..b549b3c5a 100644
--- a/pennylane/optimize/__init__.py
+++ b/pennylane/optimize/__init__.py
@@ -33,6 +33,7 @@ from .rotosolve import RotosolveOptimizer
 from .shot_adaptive import ShotAdaptiveOptimizer
 from .spsa import SPSAOptimizer
 from .qng_qjit import QNGOptimizerQJIT
+from .momentum_qng_qjit import MomentumQNGOptimizerQJIT
 
 # Optimizers to display in the docs
 __all__ = [
@@ -52,4 +53,5 @@ __all__ = [
     "ShotAdaptiveOptimizer",
     "SPSAOptimizer",
     "QNGOptimizerQJIT",
+    "MomentumQNGOptimizerQJIT",
 ]
diff --git a/pennylane/optimize/momentum_qng.py b/pennylane/optimize/momentum_qng.py
index caaf7b2ed..8744a3e3a 100644
--- a/pennylane/optimize/momentum_qng.py
+++ b/pennylane/optimize/momentum_qng.py
@@ -81,6 +81,8 @@ class MomentumQNGOptimizer(QNGOptimizer):
         - `QAOA <https://github.com/borbysh/Momentum-QNG/blob/main/QAOA_depth4.ipynb>`__
         - `VQE <https://github.com/borbysh/Momentum-QNG/blob/main/portfolio_optimization.ipynb>`__
 
+        See :class:`~.MomentumQNGOptimizerQJIT` for an Optax-like and ``jax.jit``/``qml.qjit``-compatible implementation.
+
     Keyword Args:
         stepsize=0.01 (float): the user-defined hyperparameter :math:`\eta`
         momentum=0.9 (float): the user-defined hyperparameter :math:`\rho`
diff --git a/pennylane/optimize/momentum_qng_qjit.py b/pennylane/optimize/momentum_qng_qjit.py
new file mode 100644
index 000000000..0280e244a
--- /dev/null
+++ b/pennylane/optimize/momentum_qng_qjit.py
@@ -0,0 +1,124 @@
+# Copyright 2018-2025 Xanadu Quantum Technologies Inc.
+
+# 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
+
+#     http://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.
+"""Quantum natural gradient optimizer with momentum for Jax/Catalyst interface"""
+
+from pennylane import math
+
+from .qng_qjit import QNGOptimizerQJIT
+
+
+class MomentumQNGOptimizerQJIT(QNGOptimizerQJIT):
+    r"""Optax-like and ``jax.jit``/``qml.qjit``-compatible implementation of the :class:`~.MomentumQNGOptimizer`,
+    a generalized Quantum Natural Gradient (QNG) optimizer considering a discrete-time Langevin equation
+    with QNG force.
+
+    For more theoretical details, see the :class:`~.MomentumQNGOptimizer` documentation.
+
+    .. note::
+
+        Please be aware of the following:
+
+        - As with ``MomentumQNGOptimizer``, ``MomentumQNGOptimizerQJIT`` supports a single QNode to encode the objective function.
+
+        - ``MomentumQNGOptimizerQJIT`` does not support any QNode with multiple arguments. A potential workaround
+          would be to combine all parameters into a single objective function argument.
+
+        - ``MomentumQNGOptimizerQJIT`` does not work correctly if there is any classical processing in the QNode circuit
+          (e.g., ``2 * theta`` as a gate parameter).
+
+    Parameters:
+        stepsize (float): the stepsize hyperparameter (default value: 0.01).
+        momentum (float): the momentum coefficient hyperparameter (default value: 0.9).
+        approx (str): approximation method for the metric tensor (default value: "block-diag").
+
+            - If ``None``, the full metric tensor is computed
+
+            - If ``"block-diag"``, the block-diagonal approximation is computed, reducing
+              the number of evaluated circuits significantly
+
+            - If ``"diag"``, the diagonal approximation is computed, slightly
+              reducing the classical overhead but not the quantum resources
+              (compared to ``"block-diag"``)
+
+        lam (float): metric tensor regularization to be applied at each optimization step (default value: 0).
+
+    **Example:**
+
+    Consider a hybrid workflow to optimize an objective function defined by a quantum circuit.
+    To make the entire workflow faster, the update step and the whole optimization
+    can be just-in-time compiled using the :func:`~.qjit` decorator:
+
+    .. code-block:: python
+
+        import pennylane as qml
+        import jax.numpy as jnp
+
+        dev = qml.device("lightning.qubit", wires=2)
+
+        @qml.qnode(dev)
+        def circuit(params):
+            qml.RX(params[0], wires=0)
+            qml.RY(params[1], wires=1)
+            return qml.expval(qml.Z(0) + qml.X(1))
+
+        opt = qml.MomentumQNGOptimizerQJIT(stepsize=0.1, momentum=0.2)
+
+        @qml.qjit
+        def update_step_qjit(i, args):
+            params, state = args
+            return opt.step(circuit, params, state)
+
+        @qml.qjit
+        def optimization_qjit(params, iters):
+            state = opt.init(params)
+            args = (params, state)
+            params, state = qml.for_loop(iters)(update_step_qjit)(args)
+            return params
+
+    >>> params = jnp.array([0.1, 0.2])
+    >>> iters = 1000
+    >>> optimization_qjit(params=params, iters=iters)
+    Array([ 3.14159265, -1.57079633], dtype=float64)
+
+    Make sure you are using the ``lightning.qubit`` device along with ``qml.qjit``.
+    """
+
+    def __init__(self, stepsize=0.01, momentum=0.9, approx="block-diag", lam=0):
+        super().__init__(stepsize, approx, lam)
+        self.momentum = momentum
+
+    def init(self, params):
+        """Return the initial state of the optimizer. This state is always initialized as an
+        array of zeros with the same shape and type of the given array of parameters.
+
+        Args:
+            params (array): QNode parameters
+
+        Returns:
+            array: initial state of the optimizer
+        """
+        # pylint:disable=no-self-use
+        return math.zeros_like(params)
+
+    def _apply_grad(self, mt, grad, params, state):
+        """Update the optimizer's state and the array of parameters for a single optimization
+        step according to the Quantum Natural Gradient algorithm with momentum.
+        """
+        shape = math.shape(grad)
+        grad_flat = math.flatten(grad)
+        update_flat = math.linalg.pinv(mt) @ grad_flat
+        update = math.reshape(update_flat, shape)
+        state = self.momentum * state + self.stepsize * update
+        new_params = params - state
+        return new_params, state
diff --git a/pennylane/optimize/qng_qjit.py b/pennylane/optimize/qng_qjit.py
index 7bd04744a..f49109fba 100644
--- a/pennylane/optimize/qng_qjit.py
+++ b/pennylane/optimize/qng_qjit.py
@@ -78,12 +78,13 @@ class QNGOptimizerQJIT:
     Array([ 3.14159265, -1.57079633], dtype=float64)
 
     Make sure you are using the ``lightning.qubit`` device along with ``qml.qjit`` with ``autograph`` enabled.
+    Using ``qml.qjit`` on the whole workflow with ``autograph`` not enabled may lead to a substantial increase
+    in compilation time and no runtime benefits.
 
-    Using the ``jax.jit`` decorator for the entire workflow is not recommended since it
-    may lead to a significative compilation time and no runtime benefits.
-    However, ``jax.jit`` can be used with the ``default.qubit`` device to just-in-time
-    compile the ``step`` (or ``step_and_cost``) method of the optimizer.
-    For example:
+    The ``jax.jit`` decorator should not be used on the entire workflow.
+    However, it can be used with the ``default.qubit`` device to just-in-time
+    compile the ``step`` (or ``step_and_cost``) method of the optimizer, leading
+    to a significative increase in runtime performance:
 
     .. code-block:: python
 
@@ -163,13 +164,6 @@ class QNGOptimizerQJIT:
 
         Returns:
             tuple: (new parameters values, new optimizer's state)
-
-        .. note::
-
-            Since the Quantum Natural Gradient (QNG) algorithm doesn't actually require any particular state,
-            the ``state`` object is never really updated in this case. However, it is carried over the
-            optimization to match the ``optax``-like interface for all Jax-based quantum-specific optimizers.
-
         """
         mt = self._get_metric_tensor(qnode, params, **kwargs)
         grad = self._get_grad(qnode, params, **kwargs)
@@ -188,13 +182,6 @@ class QNGOptimizerQJIT:
 
         Returns:
             tuple: (new parameters values, new optimizer's state, objective function value)
-
-        .. note::
-
-            Since the Quantum Natural Gradient (QNG) algorithm doesn't actually require any particular state,
-            the ``state`` object is never really updated in this case. However, it is carried over the
-            optimization to match the ``optax``-like interface for all Jax-based quantum-specific optimizers.
-
         """
         mt = self._get_metric_tensor(qnode, params, **kwargs)
         cost, grad = self._get_value_and_grad(qnode, params, **kwargs)


Test patch:
diff --git a/tests/io/qasm_interpreter/complex_subroutines.qasm b/tests/io/qasm_interpreter/complex_subroutines.qasm
index 9cb654b76..3a3137690 100644
--- a/tests/io/qasm_interpreter/complex_subroutines.qasm
+++ b/tests/io/qasm_interpreter/complex_subroutines.qasm
@@ -1,5 +1,6 @@
 qubit q0;
 qubit anc;
+qubit[2] p;
 
 def second(qubit q) {
     h q;
@@ -20,4 +21,10 @@ def third(qubit q, float a) {
     rx(a) q;
 }
 
+def fourth(qubit[2] p) {
+    x p[0];
+    y p[1];
+}
+
 third(q0, 0.1);
+fourth(p);
diff --git a/tests/io/qasm_interpreter/custom_gates.qasm b/tests/io/qasm_interpreter/custom_gates.qasm
index 2e890958f..efc0070c7 100644
--- a/tests/io/qasm_interpreter/custom_gates.qasm
+++ b/tests/io/qasm_interpreter/custom_gates.qasm
@@ -1,5 +1,6 @@
 qubit q0;
 qubit q1;
+qubit[2] p;
 
 const float pi = 3.14159;
 
@@ -17,4 +18,11 @@ gate custom_two(θ) c, d
     x d;
 }
 
+gate custom_three f
+{
+    x f[0];
+    y f[1];
+}
+
 custom_two(pi) q1, q0;
+custom_three p;
diff --git a/tests/io/qasm_interpreter/nested_renaming.qasm b/tests/io/qasm_interpreter/nested_renaming.qasm
index 1e339ec29..491949d15 100644
--- a/tests/io/qasm_interpreter/nested_renaming.qasm
+++ b/tests/io/qasm_interpreter/nested_renaming.qasm
@@ -1,4 +1,4 @@
-qubit q0;
+qubit[1] q;
 
 def f3(qubit q2) {
     h q2;
@@ -14,4 +14,22 @@ def f1(qubit q1) {
     f2(q1);
 }
 
-f1(q0);
+f1(q[0]);
+
+qubit[2] p;
+
+def l3(qubit[2] p2) {
+    h p2[1];
+}
+
+def l2(qubit[2] p2) {
+    x p2[0];
+    l3(p2);
+}
+
+def l1(qubit[2] p1) {
+    y p1[1];
+    l2(p1);
+}
+
+l1(p);
diff --git a/tests/io/qasm_interpreter/resets.qasm b/tests/io/qasm_interpreter/resets.qasm
index 1fb2863bf..ea98a0448 100644
--- a/tests/io/qasm_interpreter/resets.qasm
+++ b/tests/io/qasm_interpreter/resets.qasm
@@ -1,10 +1,9 @@
-qubit q;
-reset q;
+qubit[10] q;
 
-def reset_and_measure(qubit p) -> bit {
-  bit b = "0";
-  reset p;
-  measure p -> b;
+def reset_and_measure(qubit[10] q) -> bit {
+  bit b = "0000000000";
+  reset q;
+  measure q -> b;
   return b;
 }
 
diff --git a/tests/io/qasm_interpreter/test_interpreter.py b/tests/io/qasm_interpreter/test_interpreter.py
index 1b1acdef2..e8d0fd5fc 100644
--- a/tests/io/qasm_interpreter/test_interpreter.py
+++ b/tests/io/qasm_interpreter/test_interpreter.py
@@ -42,7 +42,7 @@ from pennylane import (
 )
 from pennylane.measurements import MeasurementValue, MidMeasureMP
 from pennylane.ops import Adjoint, Controlled, ControlledPhaseShift, MultiControlledX
-from pennylane.ops.op_math.pow import PowOperation, PowOpObs
+from pennylane.ops.op_math.pow import PowOperation
 from pennylane.wires import Wires
 
 try:
@@ -310,14 +310,13 @@ class TestMeasurementReset:
         ast = parse(open("tests/io/qasm_interpreter/resets.qasm", mode="r").read(), permissive=True)
 
         with queuing.AnnotatedQueue() as q:
-            context = QasmInterpreter().interpret(
-                ast, context={"name": "post_processing", "wire_map": None}
-            )
+            context = QasmInterpreter().interpret(ast, context={"name": "resets", "wire_map": None})
 
-        assert isinstance(q.queue[0], MidMeasureMP)
-        assert q.queue[0].wires == Wires(["q"])
-        assert q.queue[0].reset
-        assert context.vars["a"].val.wires == Wires(["q"])
+        for i in range(10):
+            assert isinstance(q.queue[i], MidMeasureMP)
+            assert q.queue[i].wires == Wires([f"q[{i}]"])
+            assert q.queue[i].reset
+            assert context.vars["a"].val[i].wires == Wires([f"q[{i}]"])
 
     def test_post_processing_measurement(self, mocker):
         import pennylane
@@ -567,7 +566,14 @@ class TestSubroutine:
                 ast, context={"name": "nested-subroutines", "wire_map": None}
             )
 
-        assert q.queue == [PauliY("q0"), PauliX("q0"), Hadamard("q0")]
+        assert q.queue == [
+            PauliY("q[0]"),
+            PauliX("q[0]"),
+            Hadamard("q[0]"),
+            PauliY("p[1]"),
+            PauliX("p[0]"),
+            Hadamard("p[1]"),
+        ]
 
     def test_repeated_calls(self):
         # parse the QASM
@@ -636,7 +642,14 @@ class TestSubroutine:
                 ast, context={"name": "complex-subroutines", "wire_map": None}
             )
 
-        assert q.queue == [Hadamard("q0"), PauliY("q0"), Hadamard("q0"), RX(0.1, "q0")]
+        assert q.queue == [
+            Hadamard("q0"),
+            PauliY("q0"),
+            Hadamard("q0"),
+            RX(0.1, "q0"),
+            PauliX("p[0]"),
+            PauliY("p[1]"),
+        ]
         assert context.vars["c"].val == 0
 
     def test_subroutines(self):
@@ -816,6 +829,81 @@ class TestExpressions:
             assert isinstance(preprocess_operands(operand), float)
 
 
+@pytest.mark.external
+class TestRegisters:
+
+    def test_index_out_of_bounds(self):
+        # parse the QASM program
+        ast = parse(
+            """
+            qubit[3] q;
+            id q[4];
+            """
+        )
+
+        with pytest.raises(
+            IndexError, match="Index 4 into register q of length 3 out of bounds on line 3"
+        ):
+            QasmInterpreter().interpret(ast, context={"wire_map": None, "name": "out-of-bounds"})
+
+    def test_unsupported_register_index(self):
+        # parse the QASM program
+        ast = parse(
+            """
+            qubit[3] q;
+            id q[1, 2];
+            """
+        )
+
+        with pytest.raises(NotImplementedError, match="Only a single Expression"):
+            QasmInterpreter().interpret(
+                ast, context={"wire_map": None, "name": "qubit-registers-index"}
+            )
+
+    def test_qubit_registers(self):
+        # parse the QASM program
+        ast = parse(
+            """
+            int offset = 1;
+            qubit[3] q;
+            id q[0 + offset];
+            h q[2];
+            x q[1 + offset];
+            y q[2];
+            z q[0];
+            s q[2];
+            sdg q[2];
+            t q[1];
+            tdg q[1];
+            sx q[0];
+            ctrl @ id q[0], q[1];
+            inv @ h q[2 - offset];
+            pow(2) @ t q[1];
+            """,
+            permissive=True,
+        )
+
+        # execute the callable
+        with queuing.AnnotatedQueue() as q:
+            QasmInterpreter().interpret(ast, context={"wire_map": None, "name": "qubit-registers"})
+
+        assert q.queue == [
+            Identity("q[1]"),
+            Hadamard("q[2]"),
+            PauliX("q[2]"),
+            PauliY("q[2]"),
+            PauliZ("q[0]"),
+            S("q[2]"),
+            Adjoint(S("q[2]")),
+            T("q[1]"),
+            Adjoint(T("q[1]")),
+            SX("q[0]"),
+            Controlled(Identity("q[1]"), control_wires=["q[0]"]),
+            Adjoint(Hadamard("q[1]")),
+            T("q[1]") ** 2,
+        ]
+
+
 @pytest.mark.external
 class TestVariables:
 
@@ -946,22 +1034,6 @@ class TestVariables:
         ):
             QasmInterpreter().interpret(ast, context={"wire_map": None, "name": "mutate-error"})
 
-    def test_declare_register(self):
-        # parse the QASM
-        ast = parse(
-            """
-            qubit[2] q;
-            """,
-            permissive=True,
-        )
-
-        with pytest.raises(
-            TypeError,
-            match="Qubit registers are not yet supported, "
-            "please declare each qubit individually.",
-        ):
-            QasmInterpreter().interpret(ast, context={"wire_map": None, "name": "qubit-register"})
-
     def test_retrieve_wire(self):
         # parse the QASM
         ast = parse(
@@ -1153,6 +1225,8 @@ class TestGates:
             CNOT(wires=["q0", "q1"]),
             RX(0.7853975, wires=["q1"]),
             PauliX("q0"),
+            PauliX("p[0]"),
+            PauliY("p[1]"),
         ]
 
     def test_nested_modifiers(self):
@@ -1401,7 +1475,7 @@ class TestGates:
             RX(0.5, wires=["q0"]),
             RY(0.2, wires=["q0"]),
             Adjoint(RX(0.5, wires=["q0"])),
-            PowOpObs(PauliX(wires=["q0"]), 2),
+            PowOperation(PauliX(wires=["q0"]), 2),
             CNOT(wires=["q1", "q0"]),
         ]
 
diff --git a/tests/ops/functions/conftest.py b/tests/ops/functions/conftest.py
index c969b6592..602e0b926 100644
--- a/tests/ops/functions/conftest.py
+++ b/tests/ops/functions/conftest.py
@@ -22,11 +22,10 @@ import numpy as np
 import pytest
 
 import pennylane as qml
-from pennylane._deprecated_observable import Observable
 from pennylane.exceptions import DeviceError
 from pennylane.operation import Channel, Operation, Operator, StatePrepBase
-from pennylane.ops.op_math.adjoint import Adjoint, AdjointObs, AdjointOperation, AdjointOpObs
-from pennylane.ops.op_math.pow import PowObs, PowOperation, PowOpObs
+from pennylane.ops.op_math.adjoint import Adjoint, AdjointOperation
+from pennylane.ops.op_math.pow import PowOperation
 from pennylane.templates.subroutines.trotter import TrotterizedQfunc
 
 
@@ -171,12 +170,9 @@ These operators need to break PL conventions, and each one's reason is specified
 
 _ABSTRACT_OR_META_TYPES = {
     Adjoint,
-    AdjointOpObs,
     AdjointOperation,
-    AdjointObs,
     Operator,
     Operation,
-    Observable,
     Channel,
     qml.ops.Projector,
     qml.ops.SymbolicOp,
@@ -190,9 +186,7 @@ _ABSTRACT_OR_META_TYPES = {
     StatePrepBase,
     qml.resource.ResourcesOperation,
     qml.resource.ErrorOperation,
-    PowOpObs,
     PowOperation,
-    PowObs,
     qml.StatePrep,
     qml.FromBloq,
     qml.allocation.Allocate,  # no integer wires
diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py
index 029c81843..095a92b33 100644
--- a/tests/ops/functions/test_equal.py
+++ b/tests/ops/functions/test_equal.py
@@ -1885,19 +1885,6 @@ class TestSymbolicOpComparison:
         assert qml.equal(op1, op2, check_interface=False, check_trainability=False)
         assert not qml.equal(op1, op2, check_interface=False, check_trainability=True)
 
-    def test_adjoint_observable_with_non_adjoint(self):
-        """Test that comparing an adjoint observable with a non-adjoint operation returns False"""
-
-        base = qml.X(0)
-        adj_op = qml.adjoint(base)
-
-        # if we get rid of Observable or stop having adjoint(Observable) be an Observable,
-        # this test is no longer relevant. Ref: https://github.com/PennyLaneAI/pennylane/pull/7107/
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert isinstance(adj_op, qml.operation.Observable)
-
-        assert not qml.equal(adj_op, qml.exp(base, 2))
-
     @pytest.mark.parametrize(("wire1", "wire2", "res"), WIRES)
     def test_conditional_base_operator_wire_comparison(self, wire1, wire2, res):
         """Test that equal compares operator wires for Conditional operators"""
diff --git a/tests/ops/op_math/test_adjoint.py b/tests/ops/op_math/test_adjoint.py
index 0676ad33b..b8412c8ed 100644
--- a/tests/ops/op_math/test_adjoint.py
+++ b/tests/ops/op_math/test_adjoint.py
@@ -48,8 +48,6 @@ class TestInheritanceMixins:
         assert isinstance(op, Adjoint)
         assert isinstance(op, qml.operation.Operator)
         assert not isinstance(op, qml.operation.Operation)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not isinstance(op, qml.operation.Observable)
         assert not isinstance(op, AdjointOperation)
 
         # checking we can call `dir` without problems
@@ -70,41 +68,12 @@ class TestInheritanceMixins:
         assert isinstance(op, Adjoint)
         assert isinstance(op, qml.operation.Operator)
         assert isinstance(op, qml.operation.Operation)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not isinstance(op, qml.operation.Observable)
         assert isinstance(op, AdjointOperation)
 
         # check operation-specific properties made it into the mapping
         assert "grad_recipe" in dir(op)
         assert "control_wires" in dir(op)
 
-    def test_observable(self):
-        """Test that when the base is an Observable, Adjoint will also inherit from Observable."""
-
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            # pylint: disable=too-few-public-methods
-            class CustomObs(qml.operation.Observable):
-                num_wires = 1
-                num_params = 0
-
-        base = CustomObs(wires=0)
-        ob = Adjoint(base)
-
-        assert isinstance(ob, Adjoint)
-        assert isinstance(ob, qml.operation.Operator)
-        assert not isinstance(ob, qml.operation.Operation)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert isinstance(ob, qml.operation.Observable)
-        assert not isinstance(ob, AdjointOperation)
-
-        # Check some basic observable functionality
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert ob.compare(ob)
-        assert isinstance(1.0 * ob @ ob, qml.ops.Prod)
-
-        # check the dir
-        assert "grad_recipe" not in dir(ob)
-
     @pytest.mark.parametrize(
         "op",
         (
@@ -1003,19 +972,6 @@ class TestAdjointConstructorOutsideofQueuing:
         assert isinstance(out, qml.RX)
         assert out.data == (-x,)
 
-    def test_observable(self):
-        """Test providing a preconstructed Observable outside of a queuing context."""
-
-        base = 1.0 * qml.PauliX(0)
-        obs = adjoint(base)
-
-        assert isinstance(obs, Adjoint)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert isinstance(base, qml.operation.Observable) == isinstance(
-                obs, qml.operation.Observable
-            )
-        assert obs.base is base
-
     def test_single_op_function(self):
         """Test the transform on a single op as a callable outside of a queuing context."""
         x = 1.234
diff --git a/tests/ops/op_math/test_linear_combination.py b/tests/ops/op_math/test_linear_combination.py
index 1d815d078..180afb432 100644
--- a/tests/ops/op_math/test_linear_combination.py
+++ b/tests/ops/op_math/test_linear_combination.py
@@ -708,54 +708,6 @@ class TestLinearCombination:
         (qml.ops.LinearCombination([1.0], [X(0) @ X(1)]), X(0) @ X(1)),
     )
 
-    @pytest.mark.parametrize("H, op", COMPARE_WITH_OPS)
-    def test_compare_to_simple_ops(self, H, op):
-        with pytest.raises(qml.exceptions.PennyLaneDeprecationWarning):
-            assert H.compare(op)
-
-    def test_compare_raises_error(self):
-        op = qml.ops.LinearCombination([], [])
-        with pytest.raises(qml.exceptions.PennyLaneDeprecationWarning):
-            with pytest.raises(ValueError, match="Can only compare a LinearCombination"):
-                op.compare(0)
-
-    @pytest.mark.xfail
-    def test_compare_gell_mann(self):
-        """Tests that the compare method returns the correct result for LinearCombinations
-        with qml.GellMann present."""
-        H1 = qml.ops.LinearCombination([1], [qml.GellMann(wires=2, index=2)])
-        H2 = qml.ops.LinearCombination(
-            [1], [qml.GellMann(wires=2, index=1) @ qml.GellMann(wires=1, index=2)]
-        )
-        H3 = qml.ops.LinearCombination([1], [qml.GellMann(wires=2, index=1)])
-        H4 = qml.ops.LinearCombination(
-            [1], [qml.GellMann(wires=2, index=1) @ qml.GellMann(wires=1, index=3)]
-        )
-
-        assert H1.compare(qml.GellMann(wires=2, index=2))
-        assert H1.compare(qml.GellMann(wires=2, index=1))
-        assert H1.compare(H3)
-        assert H2.compare(qml.GellMann(wires=2, index=1) @ qml.GellMann(wires=1, index=2))
-        assert not H2.compare(qml.GellMann(wires=2, index=2) @ qml.GellMann(wires=1, index=2))
-        assert not H2.compare(H4)
-
-    def test_LinearCombination_equal_error(self):
-        """Tests that the correct error is raised when compare() is called on invalid type"""
-
-        H = qml.ops.LinearCombination([1], [Z(0)])
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            with pytest.raises(
-                ValueError,
-                match=r"Can only compare a LinearCombination and an Operator.",
-            ):
-                _ = H.compare([[1, 0], [0, -1]])
-
-    @pytest.mark.parametrize(("H1", "H2", "res"), equal_LinearCombinations)
-    def test_LinearCombination_equal(self, H1, H2, res):
-        """Tests that equality can be checked between LinearCombinations"""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert H1.compare(H2) == res
-
     @pytest.mark.parametrize(("H1", "H2", "H"), add_LinearCombinations)
     def test_LinearCombination_add(self, H1, H2, H):
         """Tests that LinearCombinations are added correctly"""
diff --git a/tests/ops/op_math/test_pow_op.py b/tests/ops/op_math/test_pow_op.py
index 449ee9ab1..de1a4de2b 100644
--- a/tests/ops/op_math/test_pow_op.py
+++ b/tests/ops/op_math/test_pow_op.py
@@ -122,8 +122,6 @@ class TestInheritanceMixins:
         assert isinstance(op, Pow)
         assert isinstance(op, qml.operation.Operator)
         assert not isinstance(op, qml.operation.Operation)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not isinstance(op, qml.operation.Observable)
         assert not isinstance(op, PowOperation)
 
         # checking we can call `dir` without problems
@@ -143,38 +141,12 @@ class TestInheritanceMixins:
         assert isinstance(op, Pow)
         assert isinstance(op, qml.operation.Operator)
         assert isinstance(op, qml.operation.Operation)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not isinstance(op, qml.operation.Observable)
         assert isinstance(op, PowOperation)
 
         # check operation-specific properties made it into the mapping
         assert "grad_recipe" in dir(op)
         assert "control_wires" in dir(op)
 
-    def test_observable(self, power_method):
-        """Test that when the base is an Observable, Pow will also inherit from Observable."""
-
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-
-            class CustomObs(qml.operation.Observable):
-                num_wires = 1
-                num_params = 0
-
-        base = CustomObs(wires=0)
-        ob: Pow = power_method(base=base, z=-1.2)
-
-        assert isinstance(ob, Pow)
-        assert isinstance(ob, qml.operation.Operator)
-        assert not isinstance(ob, qml.operation.Operation)
-        assert not isinstance(ob, PowOperation)
-
-        # Check some basic observable functionality
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert ob.compare(ob)
-
-        # check the dir
-        assert "grad_recipe" not in dir(ob)
-
 
 @pytest.mark.parametrize("power_method", [Pow, pow_using_dunder_method, qml.pow])
 class TestInitialization:
diff --git a/tests/optimize/test_momentum_qng_qjit.py b/tests/optimize/test_momentum_qng_qjit.py
new file mode 100644
index 000000000..0643de8af
--- /dev/null
+++ b/tests/optimize/test_momentum_qng_qjit.py
@@ -0,0 +1,240 @@
+# Copyright 2018-2025 Xanadu Quantum Technologies Inc.
+
+# 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
+
+#     http://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.
+"""Test Jax-based Catalyst-compatible Momentum-QNG optimizer"""
+
+from functools import partial
+
+import numpy as np
+import pytest
+
+import pennylane as qml
+
+dev_names = (
+    "default.qubit",
+    "lightning.qubit",
+)
+
+
+def circuit(params):
+    """Simple circuit to use for testing."""
+    qml.RX(params[0], wires=0)
+    qml.RY(params[1], wires=1)
+    return qml.expval(qml.Z(wires=0))
+
+
+class TestBasics:
+    """Test basic properties of the MomentumQNGOptimizerQJIT."""
+
+    def test_initialization_default(self):
+        """Test that initializing MomentumQNGOptimizerQJIT with default values works."""
+        opt = qml.MomentumQNGOptimizerQJIT()
+        assert opt.stepsize == 0.01
+        assert opt.momentum == 0.9
+        assert opt.approx == "block-diag"
+        assert opt.lam == 0
+
+    def test_initialization_custom(self):
+        """Test that initializing MomentumQNGOptimizerQJIT with custom values works."""
+        opt = qml.MomentumQNGOptimizerQJIT(stepsize=0.05, momentum=0.8, approx="diag", lam=1e-9)
+        assert opt.stepsize == 0.05
+        assert opt.momentum == 0.8
+        assert opt.approx == "diag"
+        assert opt.lam == 1e-9
+
+    def test_init_zero_state(self):
+        """Test that the MomentumQNGOptimizerQJIT state is initialized to an array of zeros."""
+        opt = qml.MomentumQNGOptimizerQJIT()
+        params = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]])
+        state = opt.init(params)
+        assert np.all(state == np.zeros_like(params))
+
+
+class TestOptimize:
+    """Test basic optimization integration."""
+
+    @pytest.mark.jax
+    @pytest.mark.parametrize("dev_name", dev_names)
+    def test_step_and_cost(self, dev_name):
+        """Test that the step and step_and_cost methods are returning the correct result."""
+        import jax.numpy as jnp
+
+        @qml.qnode(qml.device(dev_name))
+        def circ(params):
+            qml.RX(params[0], wires=0)
+            qml.RY(params[1], wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        opt = qml.MomentumQNGOptimizerQJIT(stepsize=0.1, momentum=0.4)
+        params = jnp.array([0.11, 0.412])
+        state = jnp.array([[-0.31, 0.842]])
+
+        new_params1, state1 = opt.step(circ, params, state)
+        new_params2, state2, cost = opt.step_and_cost(circ, params, state)
+
+        exp_params = qml.numpy.array(params)
+        exp_state = np.array([-0.31, 0.842])
+        exp_cost = circ(exp_params)
+        exp_mt = np.array([0.25, (np.cos(exp_params[0]) ** 2) / 4])
+        exp_state = opt.momentum * exp_state + opt.stepsize * qml.grad(circ)(exp_params) / exp_mt
+        exp_params -= exp_state
+
+        assert np.allclose(new_params1, exp_params)
+        assert np.allclose(new_params2, exp_params)
+        assert np.allclose(state1, exp_state)
+        assert np.allclose(state2, exp_state)
+        assert np.allclose(cost, exp_cost)
+
+    @pytest.mark.jax
+    @pytest.mark.parametrize("dev_name", dev_names)
+    def test_step_and_cost_with_gen_hamiltonian(self, dev_name):
+        """Test that the step and step_and_cost methods are returning the correct result
+        when the generator of an operator is a Hamiltonian."""
+        import jax.numpy as jnp
+
+        @qml.qnode(qml.device(dev_name, wires=4))
+        def circ(params):
+            qml.DoubleExcitation(params[0], wires=[0, 1, 2, 3])
+            qml.RY(params[1], wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        opt = qml.MomentumQNGOptimizerQJIT(stepsize=0.1, momentum=0.4)
+        params = jnp.array([0.11, 0.412])
+        state = jnp.array([[-0.31, 0.842]])
+
+        new_params1, state1 = opt.step(circ, params, state)
+        new_params2, state2, cost = opt.step_and_cost(circ, params, state)
+
+        exp_params = qml.numpy.array(params)
+        exp_state = np.array([-0.31, 0.842])
+        exp_cost = circ(exp_params)
+        exp_mt = np.array([1 / 16, 1 / 4])
+        exp_state = opt.momentum * exp_state + opt.stepsize * qml.grad(circ)(exp_params) / exp_mt
+        exp_params -= exp_state
+
+        assert np.allclose(new_params1, exp_params)
+        assert np.allclose(new_params2, exp_params)
+        assert np.allclose(state1, exp_state)
+        assert np.allclose(state2, exp_state)
+        assert np.allclose(cost, exp_cost)
+
+    @pytest.mark.jax
+    @pytest.mark.parametrize("dev_name", dev_names)
+    def test_qubit_rotations_circuit(self, tol, dev_name):
+        """Test that a simple qubit rotations circuit gets optimized correctly, checking params and cost at each step."""
+        import jax.numpy as jnp
+
+        @qml.qnode(qml.device(dev_name))
+        def circ(params):
+            qml.RX(params[0], wires=0)
+            qml.RY(params[1], wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        def grad(params):
+            """Returns the gradient of the above circuit."""
+            da = -np.sin(params[0]) * np.cos(params[1])
+            db = -np.cos(params[0]) * np.sin(params[1])
+            return np.array([da, db])
+
+        opt = qml.MomentumQNGOptimizerQJIT(stepsize=0.2, momentum=0.3)
+        params = jnp.array([0.011, 0.012])
+        state = opt.init(params)
+
+        exp_params = np.array([0.011, 0.012])
+        exp_state = np.zeros_like(exp_params)
+
+        num_steps = 30
+        for _ in range(num_steps):
+            params, state, cost = opt.step_and_cost(circ, params, state)
+
+            exp_cost = circ(exp_params)
+            exp_mt = np.array([0.25, (np.cos(exp_params[0]) ** 2) / 4])
+            exp_state = opt.momentum * exp_state + opt.stepsize * grad(exp_params) / exp_mt
+            exp_params -= exp_state
+
+            assert np.allclose(cost, exp_cost, atol=tol, rtol=0)
+            assert np.allclose(state, exp_state, atol=tol, rtol=0)
+            assert np.allclose(params, exp_params, atol=tol, rtol=0)
+
+        assert np.allclose(circ(params), -1)
+
+    @pytest.mark.jax
+    def test_jit(self):
+        """Test optimizer compatibility with jax.jit compilation."""
+        import jax
+        import jax.numpy as jnp
+
+        device = qml.device("default.qubit", wires=2)
+        qnode = qml.QNode(circuit, device=device)
+
+        opt = qml.MomentumQNGOptimizerQJIT()
+        params = jnp.array([0.1, 0.2])
+        state = opt.init(params)
+
+        new_params1, state1 = opt.step(qnode, params, state)
+        new_params2, state2, cost = opt.step_and_cost(qnode, params, state)
+
+        step = jax.jit(partial(opt.step, qnode))
+        step_and_cost = jax.jit(partial(opt.step_and_cost, qnode))
+        new_params1_jit, state1_jit = step(params, state)
+        new_params2_jit, state2_jit, cost_jit = step_and_cost(params, state)
+
+        # check params have been updated
+        assert not np.allclose(params, new_params1)
+        assert not np.allclose(state, state1)
+        assert not np.allclose(params, new_params2)
+        assert not np.allclose(state, state2)
+
+        # check jitted results match
+        assert np.allclose(new_params1, new_params1_jit)
+        assert np.allclose(state1, state1_jit)
+        assert np.allclose(new_params2, new_params2_jit)
+        assert np.allclose(state2, state2_jit)
+        assert np.allclose(cost, cost_jit)
+
+    @pytest.mark.jax
+    @pytest.mark.catalyst
+    @pytest.mark.external
+    def test_qjit(self):
+        """Test optimizer compatibility with qml.qjit compilation."""
+        import jax.numpy as jnp
+
+        pytest.importorskip("catalyst")
+
+        device = qml.device("lightning.qubit", wires=2)
+        qnode = qml.QNode(circuit, device=device)
+
+        opt = qml.MomentumQNGOptimizerQJIT()
+        params = jnp.array([0.1, 0.2])
+        state = opt.init(params)
+
+        new_params1, state1 = opt.step(qnode, params, state)
+        new_params2, state2, cost = opt.step_and_cost(qnode, params, state)
+
+        step = qml.qjit(partial(opt.step, qnode))
+        step_and_cost = qml.qjit(partial(opt.step_and_cost, qnode))
+        new_params1_qjit, state1_qjit = step(params, state)
+        new_params2_qjit, state2_qjit, cost_qjit = step_and_cost(params, state)
+
+        # check params and state have been updated
+        assert not np.allclose(params, new_params1)
+        assert not np.allclose(state, state1)
+        assert not np.allclose(params, new_params2)
+        assert not np.allclose(state, state2)
+
+        # check qjitted results match
+        assert np.allclose(new_params1, new_params1_qjit)
+        assert np.allclose(state1, state1_qjit)
+        assert np.allclose(new_params2, new_params2_qjit)
+        assert np.allclose(state2, state2_qjit)
+        assert np.allclose(cost, cost_qjit)
diff --git a/tests/test_operation.py b/tests/test_operation.py
index d4b2f665a..790b160d2 100644
--- a/tests/test_operation.py
+++ b/tests/test_operation.py
@@ -938,42 +938,17 @@ class TestObservableConstruction:
     def test_construction_with_wires_pos_arg(self):
         """Test that the wires can be given as a positional argument"""
 
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
+        class DummyObserv(qml.operation.Operator):
+            r"""Dummy custom observable"""
 
-            class DummyObserv(qml.operation.Observable):
-                r"""Dummy custom observable"""
+            _queue_category = None
 
-                num_wires = 1
-                grad_method = None
+            num_wires = 1
+            grad_method = None
 
         ob = DummyObserv([1])
         assert ob.wires == qml.wires.Wires(1)
 
-    def test_observable_is_not_operation_but_operator(self):
-        """Check that the Observable class inherits from an Operator, not from an Operation"""
-
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert issubclass(qml.operation.Observable, qml.operation.Operator)
-
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not issubclass(qml.operation.Observable, qml.operation.Operation)
-
-    def test_observable_is_operation_as_well(self):
-        """Check that the Observable class inherits from an Operator class as well"""
-
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-
-            class DummyObserv(qml.operation.Observable, qml.operation.Operation):
-                r"""Dummy custom observable"""
-
-                num_wires = 1
-                grad_method = None
-
-        assert issubclass(DummyObserv, qml.operation.Operator)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert issubclass(DummyObserv, qml.operation.Observable)
-        assert issubclass(DummyObserv, qml.operation.Operation)
-
     def test_tensor_n_multiple_modes(self):
         """Checks that the TensorN operator was constructed correctly when
         multiple modes were specified."""
@@ -1027,13 +1002,11 @@ class TestObservableConstruction:
     def test_id(self):
         """Test that the id attribute of an observable can be set."""
 
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-
-            class DummyObserv(qml.operation.Observable):
-                r"""Dummy custom observable"""
+        class DummyObserv(qml.operation.Operator):
+            r"""Dummy custom observable"""
 
-                num_wires = 1
-                grad_method = None
+            num_wires = 1
+            grad_method = None
 
         op = DummyObserv(1.0, wires=0, id="test")
         assert op.id == "test"
@@ -1041,10 +1014,8 @@ class TestObservableConstruction:
     def test_raises_if_no_wire_is_given(self):
         """Test that an error is raised if no wire is passed at initialization."""
 
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-
-            class DummyObservable(qml.operation.Observable):
-                num_wires = 1
+        class DummyObservable(qml.operation.Operator):
+            num_wires = 1
 
         with pytest.raises(Exception, match="Must specify the wires *"):
             DummyObservable()
@@ -1052,16 +1023,14 @@ class TestObservableConstruction:
     def test_is_hermitian(self):
         """Test that the id attribute of an observable can be set."""
 
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-
-            class DummyObserv(qml.operation.Observable):
-                r"""Dummy custom observable"""
+        class DummyObserv(qml.operation.Operator):
+            r"""Dummy custom observable"""
 
-                num_wires = 1
-                grad_method = None
+            num_wires = 1
+            grad_method = None
 
         op = DummyObserv(wires=0)
-        assert op.is_hermitian is True
+        assert op.is_hermitian is False
 
 
 class TestOperatorIntegration: