Repository: PennyLaneAI/pennylane
Pull request #6806 (2025-01-10 03:02:36)

Daily rc sync to master

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6806?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.60%. Comparing base [(`5efeffb`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/5efeffbcbbd89e9ea90b3e4cf4c3a043e73cf053?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`190bbbe`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/190bbbe95825f089d0b215b720513ac77876d957?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> Report is 1 commits behind head on master.

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


```diff
@@            Coverage Diff             @@
##           master    #6806      +/-   ##
==========================================
- Coverage   99.60%   99.60%   -0.01%     
==========================================
  Files         476      476              
  Lines       45232    45222      -10     
==========================================
- Hits        45055    45045      -10     
  Misses        177      177              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/6806?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).


Patch:
diff --git a/doc/code/qml_fermi.rst b/doc/code/qml_fermi.rst
index 2c528c1f2..b6c9d1b6a 100644
--- a/doc/code/qml_fermi.rst
+++ b/doc/code/qml_fermi.rst
@@ -78,15 +78,15 @@ the orbital it acts on. The values of the dictionary are one of ``'+'`` or ``'-'
 denote creation and annihilation operators, respectively. The operator
 :math:`a^{\dagger}_0 a_3 a^{\dagger}_1` can then be constructed with
 
->>> qml.fermi.FermiWord({(0, 0): '+', (1, 3): '-', (2, 1): '+'})
+>>> qml.FermiWord({(0, 0): '+', (1, 3): '-', (2, 1): '+'})
 a⁺(0) a(3) a⁺(1)
 
 A Fermi sentence can be constructed directly by passing a dictionary of Fermi words and their
 corresponding coefficients to the :class:`~pennylane.fermi.FermiSentence` class. For instance, the
 Fermi sentence :math:`1.2 a^{\dagger}_0 a_0  + 2.3 a^{\dagger}_3 a_3` can be constructed as
 
->>> fw1 = qml.fermi.FermiWord({(0, 0): '+', (1, 0): '-'})
->>> fw2 = qml.fermi.FermiWord({(0, 3): '+', (1, 3): '-'})
->>> qml.fermi.FermiSentence({fw1: 1.2, fw2: 2.3})
+>>> fw1 = qml.FermiWord({(0, 0): '+', (1, 0): '-'})
+>>> fw2 = qml.FermiWord({(0, 3): '+', (1, 3): '-'})
+>>> qml.FermiSentence({fw1: 1.2, fw2: 2.3})
 1.2 * a⁺(0) a(0)
 + 2.3 * a⁺(3) a(3)
diff --git a/doc/releases/changelog-0.40.0.md b/doc/releases/changelog-0.40.0.md
index e37693266..7d9707882 100644
--- a/doc/releases/changelog-0.40.0.md
+++ b/doc/releases/changelog-0.40.0.md
@@ -111,9 +111,6 @@
 * Added support to build a vibrational Hamiltonian in Taylor form.
   [(#6523)](https://github.com/PennyLaneAI/pennylane/pull/6523)
 
-* Added support to build a vibrational Hamiltonian in the Christiansen form.
-  [(#6560)](https://github.com/PennyLaneAI/pennylane/pull/6560)
-
 <h3>Improvements 🛠</h3>
 
 <h4>QChem improvements</h4>
@@ -389,6 +386,7 @@
 
 * `qml.BasisRotation` template is now JIT compatible.
   [(#6019)](https://github.com/PennyLaneAI/pennylane/pull/6019)
+  [(#6779)](https://github.com/PennyLaneAI/pennylane/pull/6779)
 
 * The Jaxpr primitives for `for_loop`, `while_loop` and `cond` now store slices instead of
   numbers of args.
@@ -493,6 +491,12 @@ such as `shots`, `rng` and `prng_key`.
   for the horizontal Cartan subalgebra instead of `$\mathfrak{h}$`.
   [(#6747)](https://github.com/PennyLaneAI/pennylane/pull/6747)
 
+<h4>Construct vibrational Hamiltonians 🫨</h4>
+
+* Added support to build a vibrational Hamiltonian in the Christiansen form.
+  [(#6560)](https://github.com/PennyLaneAI/pennylane/pull/6560)
+  [(#6792)](https://github.com/PennyLaneAI/pennylane/pull/6792)
+
 <h3>Breaking changes 💔</h3>
 
 * The default graph coloring method of `qml.dot`, `qml.sum`, and `qml.pauli.optimize_measurements` for grouping observables was changed 
diff --git a/pennylane/__init__.py b/pennylane/__init__.py
index 3d1c0f1b9..6e8a8fe90 100644
--- a/pennylane/__init__.py
+++ b/pennylane/__init__.py
@@ -35,6 +35,8 @@ import pennylane.qchem
 from pennylane.fermi import (
     FermiC,
     FermiA,
+    FermiWord,
+    FermiSentence,
     jordan_wigner,
     parity_transform,
     bravyi_kitaev,
diff --git a/pennylane/bose/bosonic.py b/pennylane/bose/bosonic.py
index 82da7b39f..cb09a1a11 100644
--- a/pennylane/bose/bosonic.py
+++ b/pennylane/bose/bosonic.py
@@ -30,7 +30,7 @@ class BoseWord(dict):
     symbols that denote creation and annihilation operators, respectively. The operator
     :math:`b^{\dagger}_0 b_1` can then be constructed as
 
-    >>> w = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'})
+    >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'})
     >>> print(w)
     b⁺(0) b(1)
     """
@@ -112,7 +112,7 @@ class BoseWord(dict):
         represented by the number of the wire it operates on, and a `+` or `-` to indicate either
         a creation or annihilation operator.
 
-        >>> w = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'})
+        >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'})
         >>> w.to_string()
         'b⁺(0) b(1)'
         """
@@ -209,7 +209,7 @@ class BoseWord(dict):
     def __mul__(self, other):
         r"""Multiply a BoseWord with another BoseWord, a BoseSentence, or a constant.
 
-        >>> w = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'})
+        >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'})
         >>> print(w * w)
         b⁺(0) b(1) b⁺(0) b(1)
         """
@@ -263,7 +263,7 @@ class BoseWord(dict):
     def __pow__(self, value):
         r"""Exponentiate a Bose word to an integer power.
 
-        >>> w = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'})
+        >>> w = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'})
         >>> print(w**3)
         b⁺(0) b(1) b⁺(0) b(1) b⁺(0) b(1)
         """
@@ -280,7 +280,7 @@ class BoseWord(dict):
     def normal_order(self):
         r"""Convert a BoseWord to its normal-ordered form.
 
-        >>> bw = qml.bose.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"})
+        >>> bw = qml.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"})
         >>> print(bw.normal_order())
         4.0 * b⁺(0) b(0)
         + 2.0 * I
@@ -420,9 +420,9 @@ class BoseSentence(dict):
     r"""Dictionary used to represent a Bose sentence, a linear combination of Bose words,
     with the keys as BoseWord instances and the values correspond to coefficients.
 
-    >>> w1 = qml.bose.BoseWord({(0, 0) : '+', (1, 1) : '-'})
-    >>> w2 = qml.bose.BoseWord({(0, 1) : '+', (1, 2) : '-'})
-    >>> s = BoseSentence({w1 : 1.2, w2: 3.1})
+    >>> w1 = qml.BoseWord({(0, 0) : '+', (1, 1) : '-'})
+    >>> w2 = qml.BoseWord({(0, 1) : '+', (1, 2) : '-'})
+    >>> s = qml.BoseSentence({w1 : 1.2, w2: 3.1})
     >>> print(s)
     1.2 * b⁺(0) b(1)
     + 3.1 * b⁺(1) b(2)
@@ -608,8 +608,8 @@ class BoseSentence(dict):
     def normal_order(self):
         r"""Convert a BoseSentence to its normal-ordered form.
 
-        >>> bw = qml.bose.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"})
-        >>> bs = qml.bose.BoseSentence({bw: 1})
+        >>> bw = qml.BoseWord({(0, 0): "-", (1, 0): "-", (2, 0): "+", (3, 0): "+"})
+        >>> bs = qml.BoseSentence({bw: 1})
         >>> print(bw.normal_order())
         4.0 * b⁺(0) b(0)
         + 2.0 * I
diff --git a/pennylane/bose/bosonic_mapping.py b/pennylane/bose/bosonic_mapping.py
index 5c5eb5534..87112648b 100644
--- a/pennylane/bose/bosonic_mapping.py
+++ b/pennylane/bose/bosonic_mapping.py
@@ -65,7 +65,7 @@ def binary_mapping(
 
     **Example**
 
-    >>> w = qml.bose.BoseWord({(0, 0): "+"})
+    >>> w = qml.BoseWord({(0, 0): "+"})
     >>> qml.binary_mapping(w, n_states=4)
     (
         0.6830127018922193 * X(0)
@@ -95,7 +95,7 @@ def binary_mapping(
 @singledispatch
 def _binary_mapping_dispatch(bose_operator, n_states, tol):
     """Dispatches to appropriate function if bose_operator is a BoseWord or BoseSentence."""
-    raise ValueError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}")
+    raise TypeError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}")
 
 
 @_binary_mapping_dispatch.register
@@ -190,7 +190,7 @@ def unary_mapping(
 
     **Example**
 
-    >>> w = qml.bose.BoseWord({(0, 0): "+"})
+    >>> w = qml.BoseWord({(0, 0): "+"})
     >>> qml.unary_mapping(w, n_states=4)
     (
         0.25 * X(0) @ X(1)
@@ -224,7 +224,7 @@ def unary_mapping(
 @singledispatch
 def _unary_mapping_dispatch(bose_operator, n_states, ps=False, wires_map=None, tol=None):
     """Dispatches to appropriate function if bose_operator is a BoseWord or BoseSentence."""
-    raise ValueError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}")
+    raise TypeError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}")
 
 
 @_unary_mapping_dispatch.register
@@ -367,7 +367,7 @@ def christiansen_mapping(
 @singledispatch
 def _christiansen_mapping_dispatch(bose_operator, tol):
     """Dispatches to appropriate function if bose_operator is a BoseWord or BoseSentence."""
-    raise ValueError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}")
+    raise TypeError(f"bose_operator must be a BoseWord or BoseSentence, got: {bose_operator}")
 
 
 @_christiansen_mapping_dispatch.register
diff --git a/pennylane/devices/_legacy_device.py b/pennylane/devices/_legacy_device.py
index 93342f9d2..33400c376 100644
--- a/pennylane/devices/_legacy_device.py
+++ b/pennylane/devices/_legacy_device.py
@@ -813,7 +813,10 @@ class Device(abc.ABC, metaclass=_LegacyMeta):
                 # Some measurements are not observable based.
                 continue
 
-            if mp.obs.name == "LinearCombination" and not self.supports_observable("Hamiltonian"):
+            if mp.obs.name == "LinearCombination" and not (
+                self.supports_observable("Hamiltonian")
+                or self.supports_observable("LinearCombination")
+            ):
                 return False
 
             if mp.obs.name in (
diff --git a/pennylane/fermi/fermionic.py b/pennylane/fermi/fermionic.py
index 6aacbdabe..f41627941 100644
--- a/pennylane/fermi/fermionic.py
+++ b/pennylane/fermi/fermionic.py
@@ -30,7 +30,7 @@ class FermiWord(dict):
     :math:`a^{\dagger}_0 a_1` can then be constructed as
 
     >>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'})
-    >>> w
+    >>> print(w)
     a⁺(0) a(1)
     """
 
@@ -114,7 +114,7 @@ class FermiWord(dict):
 
         >>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'})
         >>> w.to_string()
-        a⁺(0) a(1)
+        'a⁺(0) a(1)'
         """
         if len(self) == 0:
             return "I"
@@ -211,7 +211,7 @@ class FermiWord(dict):
         r"""Multiply a FermiWord with another FermiWord, a FermiSentence, or a constant.
 
         >>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'})
-        >>> w * w
+        >>> print(w * w)
         a⁺(0) a(1) a⁺(0) a(1)
         """
 
@@ -265,7 +265,7 @@ class FermiWord(dict):
         r"""Exponentiate a Fermi word to an integer power.
 
         >>> w = FermiWord({(0, 0) : '+', (1, 1) : '-'})
-        >>> w**3
+        >>> print(w**3)
         a⁺(0) a(1) a⁺(0) a(1) a⁺(0) a(1)
         """
 
@@ -348,7 +348,7 @@ class FermiWord(dict):
 
         **Example**
 
-        >>> w = qml.fermi.FermiWord({(0, 0): '+', (1, 1): '-'})
+        >>> w = qml.FermiWord({(0, 0): '+', (1, 1): '-'})
         >>> w.shift_operator(0, 1)
         -1 * a(1) a⁺(0)
         """
@@ -427,7 +427,7 @@ class FermiSentence(dict):
     >>> w1 = FermiWord({(0, 0) : '+', (1, 1) : '-'})
     >>> w2 = FermiWord({(0, 1) : '+', (1, 2) : '-'})
     >>> s = FermiSentence({w1 : 1.2, w2: 3.1})
-    >>> s
+    >>> print(s)
     1.2 * a⁺(0) a(1)
     + 3.1 * a⁺(1) a(2)
     """
@@ -754,13 +754,15 @@ class FermiC(FermiWord):
 
     To construct the operator :math:`a^{\dagger}_0`:
 
-    >>> FermiC(0)
+    >>> w = FermiC(0)
+    >>> print(w)
     a⁺(0)
 
     This can be combined with the annihilation operator :class:`~pennylane.FermiA`. For example,
     :math:`a^{\dagger}_0 a_1 a^{\dagger}_2 a_3` can be constructed as:
 
-    >>> qml.FermiC(0) * qml.FermiA(1) * qml.FermiC(2) * qml.FermiA(3)
+    >>> w = qml.FermiC(0) * qml.FermiA(1) * qml.FermiC(2) * qml.FermiA(3)
+    >>> print(w)
     a⁺(0) a(1) a⁺(2) a(3)
     """
 
@@ -796,13 +798,15 @@ class FermiA(FermiWord):
 
     To construct the operator :math:`a_0`:
 
-    >>> FermiA(0)
+    >>> w = FermiA(0)
+    >>> print(w)
     a(0)
 
     This can be combined with the creation operator :class:`~pennylane.FermiC`. For example,
     :math:`a^{\dagger}_0 a_1 a^{\dagger}_2 a_3` can be constructed as:
 
-    >>> qml.FermiC(0) * qml.FermiA(1) * qml.FermiC(2) * qml.FermiA(3)
+    >>> w = qml.FermiC(0) * qml.FermiA(1) * qml.FermiC(2) * qml.FermiA(3)
+    >>> print(w)
     a⁺(0) a(1) a⁺(2) a(3)
     """
 
diff --git a/pennylane/labs/vibrational/christiansen_utils.py b/pennylane/labs/vibrational/christiansen_utils.py
index 93ef5c7e2..33a6f26ed 100644
--- a/pennylane/labs/vibrational/christiansen_utils.py
+++ b/pennylane/labs/vibrational/christiansen_utils.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 """Utility functions related to the construction of the taylor form Hamiltonian."""
 import itertools
-import subprocess
+from pathlib import Path
 
 import h5py
 import numpy as np
@@ -516,7 +516,7 @@ def _load_cform_onemode(num_proc, nmodes, quad_order):
         for rank in range(num_proc):
             f = h5py.File("cform_H1data" + f"_{rank}" + ".hdf5", "r+")
             local_ham_cform_onebody = f["H1"][()]
-            chunk = np.array_split(local_ham_cform_onebody, nmode_combos)[mode_combo]  #
+            chunk = np.array_split(local_ham_cform_onebody, nmode_combos)[mode_combo]
             l1 += len(chunk)
             local_chunk[l0:l1] = chunk
             l0 += len(chunk)
@@ -767,17 +767,16 @@ def christiansen_integrals(pes, n_states=16, cubic=False):
     local_ham_cform_onebody = _cform_onemode(pes, n_states)
     comm.Barrier()
 
-    f = h5py.File("cform_H1data" + f"_{rank}" + ".hdf5", "w")
-    f.create_dataset("H1", data=local_ham_cform_onebody)
-    f.close()
+    file_path = Path(f"cform_H1data_{rank}.hdf5")
+    with h5py.File(file_path, "w") as f:
+        f.create_dataset("H1", data=local_ham_cform_onebody)
     comm.Barrier()
 
     ham_cform_onebody = None
     if rank == 0:
         ham_cform_onebody = _load_cform_onemode(size, len(pes.freqs), n_states)
-        process = subprocess.Popen("rm " + "cform_H1data*", stdout=subprocess.PIPE, shell=True)
-        _, _ = process.communicate()
-
+        for path in Path.cwd().glob("cform_H1data*"):
+            path.unlink()
     comm.Barrier()
     ham_cform_onebody = comm.bcast(ham_cform_onebody, root=0)
 
@@ -786,34 +785,32 @@ def christiansen_integrals(pes, n_states=16, cubic=False):
         local_ham_cform_twobody += _cform_twomode_kinetic(pes, n_states)
     comm.Barrier()
 
-    f = h5py.File("cform_H2data" + f"_{rank}" + ".hdf5", "w")
-    f.create_dataset("H2", data=local_ham_cform_twobody)
-    f.close()
+    file_path = Path(f"cform_H2data_{rank}.hdf5")
+    with h5py.File(file_path, "w") as f:
+        f.create_dataset("H2", data=local_ham_cform_twobody)
     comm.Barrier()
 
     ham_cform_twobody = None
     if rank == 0:
         ham_cform_twobody = _load_cform_twomode(size, len(pes.freqs), n_states)
-        process = subprocess.Popen("rm " + "cform_H2data*", stdout=subprocess.PIPE, shell=True)
-        _, _ = process.communicate()
-
+        for path in Path.cwd().glob("cform_H2data*"):
+            path.unlink()
     comm.Barrier()
     ham_cform_twobody = comm.bcast(ham_cform_twobody, root=0)
 
     if cubic:
         local_ham_cform_threebody = _cform_threemode(pes, n_states)
 
-        f = h5py.File("cform_H3data" + f"_{rank}" + ".hdf5", "w")
-        f.create_dataset("H3", data=local_ham_cform_threebody)
-        f.close()
+        file_path = Path(f"cform_H3data_{rank}.hdf5")
+        with h5py.File(file_path, "w") as f:
+            f.create_dataset("H3", data=local_ham_cform_threebody)
         comm.Barrier()
 
         ham_cform_threebody = None
         if rank == 0:
             ham_cform_threebody = _load_cform_threemode(size, len(pes.freqs), n_states)
-            process = subprocess.Popen("rm " + "cform_H3data*", stdout=subprocess.PIPE, shell=True)
-            _, _ = process.communicate()
-
+            for path in Path.cwd().glob("cform_H3data*"):
+                path.unlink()
         comm.Barrier()
         ham_cform_threebody = comm.bcast(ham_cform_threebody, root=0)
 
@@ -838,17 +835,16 @@ def christiansen_integrals_dipole(pes, n_states=16):
     local_dipole_cform_onebody = _cform_onemode_dipole(pes, n_states)
     comm.Barrier()
 
-    f = h5py.File("cform_D1data" + f"_{rank}" + ".hdf5", "w")
-    f.create_dataset("D1", data=local_dipole_cform_onebody)
-    f.close()
+    file_path = Path(f"cform_D1data_{rank}.hdf5")
+    with h5py.File(file_path, "w") as f:
+        f.create_dataset("D1", data=local_dipole_cform_onebody)
     comm.Barrier()
 
     dipole_cform_onebody = None
     if rank == 0:
         dipole_cform_onebody = _load_cform_onemode_dipole(size, len(pes.freqs), n_states)
-        process = subprocess.Popen("rm " + "cform_D1data*", stdout=subprocess.PIPE, shell=True)
-        _, _ = process.communicate()
-
+        for path in Path.cwd().glob("cform_D1data*"):
+            path.unlink()
     comm.Barrier()
     dipole_cform_onebody = comm.bcast(dipole_cform_onebody, root=0)
 
@@ -856,16 +852,16 @@ def christiansen_integrals_dipole(pes, n_states=16):
         local_dipole_cform_twobody = _cform_twomode_dipole(pes, n_states)
         comm.Barrier()
 
-        f = h5py.File("cform_D2data" + f"_{rank}" + ".hdf5", "w")
-        f.create_dataset("D2", data=local_dipole_cform_twobody)
-        f.close()
+        file_path = Path(f"cform_D2data_{rank}.hdf5")
+        with h5py.File(file_path, "w") as f:
+            f.create_dataset("D2", data=local_dipole_cform_twobody)
         comm.Barrier()
 
         dipole_cform_twobody = None
         if rank == 0:
             dipole_cform_twobody = _load_cform_twomode_dipole(size, len(pes.freqs), n_states)
-            process = subprocess.Popen("rm " + "cform_D2data*", stdout=subprocess.PIPE, shell=True)
-            _, _ = process.communicate()
+            for path in Path.cwd().glob("cform_D2data*"):
+                path.unlink()
         comm.Barrier()
         dipole_cform_twobody = comm.bcast(dipole_cform_twobody, root=0)
 
@@ -873,17 +869,17 @@ def christiansen_integrals_dipole(pes, n_states=16):
         local_dipole_cform_threebody = _cform_threemode_dipole(pes, n_states)
         comm.Barrier()
 
-        f = h5py.File("cform_D3data" + f"_{rank}" + ".hdf5", "w")
-        f.create_dataset("D3", data=local_dipole_cform_threebody)
-        f.close()
+        file_path = Path(f"cform_D3data_{rank}.hdf5")
+        with h5py.File(file_path, "w") as f:
+            f.create_dataset("D3", data=local_dipole_cform_threebody)
+        comm.Barrier()
 
         dipole_cform_threebody = None
         if rank == 0:
             dipole_cform_threebody = _load_cform_threemode_dipole(size, len(pes.freqs), n_states)
-            process = subprocess.Popen("rm " + "cform_D3data*", stdout=subprocess.PIPE, shell=True)
-            _, _ = process.communicate()
+            for path in Path.cwd().glob("cform_D3data*"):
+                path.unlink()
         comm.Barrier()
-
         dipole_cform_threebody = comm.bcast(dipole_cform_threebody, root=0)
 
         D_arr = [dipole_cform_onebody, dipole_cform_twobody, dipole_cform_threebody]
diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py
index c7afc235b..f40769a7d 100644
--- a/pennylane/ops/functions/equal.py
+++ b/pennylane/ops/functions/equal.py
@@ -754,39 +754,6 @@ def _equal_counts(op1: CountsMP, op2: CountsMP, **kwargs):
     return _equal_measurements(op1, op2, **kwargs) and op1.all_outcomes == op2.all_outcomes
 
 
-@_equal_dispatch.register
-# pylint: disable=unused-argument
-def _equal_basis_rotation(
-    op1: qml.BasisRotation,
-    op2: qml.BasisRotation,
-    check_interface=True,
-    check_trainability=True,
-    rtol=1e-5,
-    atol=1e-9,
-):
-    if not qml.math.allclose(
-        op1.hyperparameters["unitary_matrix"],
-        op2.hyperparameters["unitary_matrix"],
-        atol=atol,
-        rtol=rtol,
-    ):
-        return (
-            "The hyperparameter unitary_matrix is not equal for op1 and op2.\n"
-            f"Got {op1.hyperparameters['unitary_matrix']}\n and {op2.hyperparameters['unitary_matrix']}."
-        )
-    if op1.wires != op2.wires:
-        return f"op1 and op2 have different wires. Got {op1.wires} and {op2.wires}."
-    if check_interface:
-        interface1 = qml.math.get_interface(op1.hyperparameters["unitary_matrix"])
-        interface2 = qml.math.get_interface(op2.hyperparameters["unitary_matrix"])
-        if interface1 != interface2:
-            return (
-                "The hyperparameter unitary_matrix has different interfaces for op1 and op2."
-                f" Got {interface1} and {interface2}."
-            )
-    return True
-
-
 @_equal_dispatch.register
 def _equal_hilbert_schmidt(
     op1: qml.HilbertSchmidt,
diff --git a/pennylane/qchem/convert_openfermion.py b/pennylane/qchem/convert_openfermion.py
index e00c479b4..f0c616554 100644
--- a/pennylane/qchem/convert_openfermion.py
+++ b/pennylane/qchem/convert_openfermion.py
@@ -56,7 +56,7 @@ def from_openfermion(openfermion_op, wires=None, tol=1e-16):
         tol (float): Tolerance for discarding negligible coefficients.
 
     Returns:
-        Union[FermiWord, FermiSentence, LinearCombination]: PennyLane operator.
+        Union[~.FermiWord, ~.FermiSentence, LinearCombination]: PennyLane operator.
 
     **Example**
 
@@ -113,7 +113,7 @@ def to_openfermion(
     `FermionOperator <https://quantumai.google/reference/python/openfermion/ops/FermionOperator>`__.
 
     Args:
-        pennylane_op (~ops.op_math.Sum, ~ops.op_math.LinearCombination, FermiWord, FermiSentence):
+        pennylane_op (~ops.op_math.Sum, ~ops.op_math.LinearCombination, ~.FermiWord, ~.FermiSentence):
             PennyLane operator
         wires (dict): Custom wire mapping used to convert a PennyLane qubit operator
             to the external operator.
@@ -126,9 +126,9 @@ def to_openfermion(
     **Example**
 
     >>> import pennylane as qml
-    >>> w1 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'})
-    >>> w2 = qml.fermi.FermiWord({(0, 1) : '+', (1, 2) : '-'})
-    >>> fermi_s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1})
+    >>> w1 = qml.FermiWord({(0, 0) : '+', (1, 1) : '-'})
+    >>> w2 = qml.FermiWord({(0, 1) : '+', (1, 2) : '-'})
+    >>> fermi_s = qml.FermiSentence({w1 : 1.2, w2: 3.1})
     >>> of_fermi_op = qml.to_openfermion(fermi_s)
     >>> of_fermi_op
     1.2 [0^ 1] +
diff --git a/pennylane/qchem/observable_hf.py b/pennylane/qchem/observable_hf.py
index 4ef1fe1f9..5002168ff 100644
--- a/pennylane/qchem/observable_hf.py
+++ b/pennylane/qchem/observable_hf.py
@@ -32,7 +32,7 @@ def fermionic_observable(constant, one=None, two=None, cutoff=1.0e-12):
         cutoff (float): cutoff value for discarding the negligible integrals
 
     Returns:
-        FermiSentence: fermionic observable
+        ~.FermiSentence: fermionic observable
 
     **Example**
 
@@ -98,7 +98,7 @@ def qubit_observable(o_ferm, cutoff=1.0e-12, mapping="jordan_wigner"):
     r"""Convert a fermionic observable to a PennyLane qubit observable.
 
     Args:
-        o_ferm (Union[FermiWord, FermiSentence]): fermionic operator
+        o_ferm (Union[~.FermiWord, ~.FermiSentence]): fermionic operator
         cutoff (float): cutoff value for discarding the negligible terms
         mapping (str): Specifies the fermion-to-qubit mapping. Input values can
             be ``'jordan_wigner'``, ``'parity'`` or ``'bravyi_kitaev'``.
@@ -107,9 +107,9 @@ def qubit_observable(o_ferm, cutoff=1.0e-12, mapping="jordan_wigner"):
 
     **Example**
 
-    >>> w1 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'})
-    >>> w2 = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'})
-    >>> s = qml.fermi.FermiSentence({w1 : 1.2, w2: 3.1})
+    >>> w1 = qml.FermiWord({(0, 0) : '+', (1, 1) : '-'})
+    >>> w2 = qml.FermiWord({(0, 0) : '+', (1, 1) : '-'})
+    >>> s = qml.FermiSentence({w1 : 1.2, w2: 3.1})
     >>> print(qubit_observable(s))
     -0.775j * (Y(0) @ X(1)) + 0.775 * (Y(0) @ Y(1)) + 0.775 * (X(0) @ X(1)) + 0.775j * (X(0) @ Y(1))
     """
diff --git a/pennylane/qchem/structure.py b/pennylane/qchem/structure.py
index b8504af8d..02d84c959 100644
--- a/pennylane/qchem/structure.py
+++ b/pennylane/qchem/structure.py
@@ -292,9 +292,9 @@ def excitations(electrons, orbitals, delta_sz=0, fermionic=False):
     if not fermionic:
         return singles, doubles
 
-    fermionic_singles = [qml.fermi.FermiWord({(0, x[0]): "+", (1, x[1]): "-"}) for x in singles]
+    fermionic_singles = [qml.FermiWord({(0, x[0]): "+", (1, x[1]): "-"}) for x in singles]
     fermionic_doubles = [
-        qml.fermi.FermiWord({(0, x[0]): "+", (1, x[1]): "+", (2, x[2]): "-", (3, x[3]): "-"})
+        qml.FermiWord({(0, x[0]): "+", (1, x[1]): "+", (2, x[2]): "-", (3, x[3]): "-"})
         for x in doubles
     ]
 
diff --git a/pennylane/templates/subroutines/basis_rotation.py b/pennylane/templates/subroutines/basis_rotation.py
index 832a06847..f522dcb8b 100644
--- a/pennylane/templates/subroutines/basis_rotation.py
+++ b/pennylane/templates/subroutines/basis_rotation.py
@@ -107,6 +107,10 @@ class BasisRotation(Operation):
 
         return cls._primitive.bind(*wires, unitary_matrix, check=check, id=id)
 
+    @classmethod
+    def _unflatten(cls, data, metadata):
+        return cls(wires=metadata[0], unitary_matrix=data[0])
+
     def __init__(self, wires, unitary_matrix, check=False, id=None):
         M, N = qml.math.shape(unitary_matrix)
 
@@ -124,19 +128,15 @@ class BasisRotation(Operation):
         if len(wires) < 2:
             raise ValueError(f"This template requires at least two wires, got {len(wires)}")
 
-        self._hyperparameters = {
-            "unitary_matrix": unitary_matrix,
-        }
-
-        super().__init__(wires=wires, id=id)
+        super().__init__(unitary_matrix, wires=wires, id=id)
 
     @property
     def num_params(self):
-        return 0
+        return 1
 
     @staticmethod
     def compute_decomposition(
-        wires, unitary_matrix, check=False
+        unitary_matrix, wires, check=False
     ):  # pylint: disable=arguments-differ
         r"""Representation of the operator as a product of other operators.
 


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e0bfa8d1f..63c8074e5 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -9,6 +9,8 @@ on:
       - reopened
       - synchronize
       - ready_for_review
+    branches-ignore:
+      - 'v[0-9]+.[0-9]+.[0-9]+-docs'
   # Scheduled trigger on Monday at 2:47am UTC
   schedule:
     - cron: "47 2 * * 1"
diff --git a/tests/bose/test_binary_mapping.py b/tests/bose/test_binary_mapping.py
index 4f8dff4d5..d9cdb384c 100644
--- a/tests/bose/test_binary_mapping.py
+++ b/tests/bose/test_binary_mapping.py
@@ -548,7 +548,7 @@ def test_binary_mapping_wiremap(bose_op, wire_map, result):
 def test_error_is_raised_for_incompatible_type():
     """Test that an error is raised if the input is not a BoseWord or BoseSentence"""
 
-    with pytest.raises(ValueError, match="bose_operator must be a BoseWord or BoseSentence"):
+    with pytest.raises(TypeError, match="bose_operator must be a BoseWord or BoseSentence"):
         binary_mapping(X(0))
 
 
diff --git a/tests/bose/test_christiansen_mapping.py b/tests/bose/test_christiansen_mapping.py
index 121d679bf..085c45d3a 100644
--- a/tests/bose/test_christiansen_mapping.py
+++ b/tests/bose/test_christiansen_mapping.py
@@ -388,5 +388,5 @@ def test_christiansen_mapping_tolerance(bose_op, qubit_op_data, tol):
 def test_error_is_raised_for_incompatible_type():
     """Test that an error is raised if the input is not a BoseWord or BoseSentence"""
 
-    with pytest.raises(ValueError, match="bose_operator must be a BoseWord or BoseSentence"):
+    with pytest.raises(TypeError, match="bose_operator must be a BoseWord or BoseSentence"):
         christiansen_mapping(X(0))
diff --git a/tests/bose/test_unary_mapping.py b/tests/bose/test_unary_mapping.py
index edebef362..51c064fd0 100644
--- a/tests/bose/test_unary_mapping.py
+++ b/tests/bose/test_unary_mapping.py
@@ -517,7 +517,7 @@ def test_n_states_error_unary():
 def test_error_is_raised_for_incompatible_type():
     """Test that an error is raised if the input is not a BoseWord or BoseSentence"""
 
-    with pytest.raises(ValueError, match="bose_operator must be a BoseWord or BoseSentence"):
+    with pytest.raises(TypeError, match="bose_operator must be a BoseWord or BoseSentence"):
         unary_mapping(X(0))
 
 
diff --git a/tests/devices/test_legacy_device.py b/tests/devices/test_legacy_device.py
index 671425692..bd8d2e7b2 100644
--- a/tests/devices/test_legacy_device.py
+++ b/tests/devices/test_legacy_device.py
@@ -278,6 +278,20 @@ class TestDeviceSupportedLogic:
         ):
             dev.supports_observable(operation)
 
+    @pytest.mark.parametrize("supported_multi_term_obs", ["Hamiltonian", "LinearCombination"])
+    @pytest.mark.parametrize("obs_type", [qml.ops.LinearCombination, qml.Hamiltonian])
+    def test_all_multi_term_obs_supported_linear_combination(
+        self, mock_device, supported_multi_term_obs, obs_type
+    ):
+        """Test that LinearCombination is supported when the device supports either
+        LinearCombination or Hamiltonian."""
+        dev = mock_device()
+        dev.observables = dev.observables + [supported_multi_term_obs]
+
+        obs = obs_type([1.0, 2.0], [qml.Z(0), qml.Z(0)])
+        circuit = qml.tape.QuantumScript([], [qml.expval(obs)])
+        assert dev._all_multi_term_obs_supported(circuit)  # pylint: disable=protected-access
+
 
 class TestInternalFunctions:  # pylint:disable=too-many-public-methods
     """Test the internal functions of the abstract Device class"""
diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py
index 2a6f52706..3ef0e9be2 100644
--- a/tests/ops/functions/test_equal.py
+++ b/tests/ops/functions/test_equal.py
@@ -2598,7 +2598,7 @@ class TestBasisRotation:
         assert_equal(op, other_op, atol=1e-5)
         assert qml.equal(op, other_op, rtol=0, atol=1e-9) is False
 
-        with pytest.raises(AssertionError, match="The hyperparameter unitary_matrix is not equal"):
+        with pytest.raises(AssertionError, match="op1 and op2 have different data"):
             assert_equal(op, other_op, rtol=0, atol=1e-9)
 
     @pytest.mark.parametrize("op, other_op", [(op1, op2)])
@@ -2629,7 +2629,7 @@ class TestBasisRotation:
         assert_equal(op, other_op, check_interface=False)
         assert qml.equal(op, other_op) is False
 
-        with pytest.raises(AssertionError, match=r"has different interfaces for op1 and op2"):
+        with pytest.raises(AssertionError, match=r"have different interfaces"):
             assert_equal(op, other_op)
 
 
diff --git a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py
index cb2cc1902..fa3e1cf7b 100644
--- a/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py
+++ b/tests/qchem/openfermion_pyscf_tests/test_convert_openfermion.py
@@ -183,7 +183,7 @@ class TestFromOpenFermion:
         of_op = openfermion.FermionOperator("2^ 3")
         converted_op = qml.qchem.from_openfermion(of_op)
 
-        assert isinstance(converted_op, qml.fermi.FermiWord)
+        assert isinstance(converted_op, qml.FermiWord)
 
     def test_convert_fermionic_type_fs(self):
         r"""Test that FermiSentence object is returned when there are multiple
@@ -192,7 +192,7 @@ class TestFromOpenFermion:
         of_op = openfermion.FermionOperator("2^ 3") + openfermion.FermionOperator("1^ 2")
         converted_op = qml.qchem.from_openfermion(of_op)
 
-        assert isinstance(converted_op, qml.fermi.FermiSentence)
+        assert isinstance(converted_op, qml.FermiSentence)
 
     def test_tol_fermionic(self):
         r"""Test that terms with coefficients larger than tolerance are discarded"""
@@ -226,19 +226,17 @@ class TestFromOpenFermion:
 class TestToOpenFermion:
 
     FERMI_AND_OF_OPS = (
-        ((qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"})), (openfermion.FermionOperator("0^ 1"))),
+        ((qml.FermiWord({(0, 0): "+", (1, 1): "-"})), (openfermion.FermionOperator("0^ 1"))),
         (
-            (qml.fermi.FermiWord({(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"})),
+            (qml.FermiWord({(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"})),
             (openfermion.FermionOperator("1 0^ 3^ 2")),
         ),
         (
             (
-                qml.fermi.FermiSentence(
+                qml.FermiSentence(
                     {
-                        qml.fermi.FermiWord(
-                            {(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"}
-                        ): 0.5,
-                        qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"}): 0.3,
+                        qml.FermiWord({(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"}): 0.5,
+                        qml.FermiWord({(0, 0): "+", (1, 1): "-"}): 0.3,
                     }
                 )
             ),
@@ -272,12 +270,10 @@ class TestToOpenFermion:
 
     COMPLEX_OPS = (
         (
-            qml.fermi.FermiSentence(
+            qml.FermiSentence(
                 {
-                    qml.fermi.FermiWord(
-                        {(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"}
-                    ): 1e-08j,
-                    qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"}): 0.3,
+                    qml.FermiWord({(1, 0): "+", (0, 1): "-", (2, 3): "+", (3, 2): "-"}): 1e-08j,
+                    qml.FermiWord({(0, 0): "+", (1, 1): "-"}): 0.3,
                 }
             )
         ),
@@ -339,9 +335,9 @@ class TestToOpenFermion:
             )
 
     OPS_FERMI_WIRE = (
-        ((qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"})), ({0: "a", 1: 2})),
+        ((qml.FermiWord({(0, 0): "+", (1, 1): "-"})), ({0: "a", 1: 2})),
         (
-            (qml.fermi.FermiSentence({qml.fermi.FermiWord({(0, 0): "+", (1, 1): "-"}): 1.2})),
+            (qml.FermiSentence({qml.FermiWord({(0, 0): "+", (1, 1): "-"}): 1.2})),
             ({0: "a", 1: 2}),
         ),
     )
diff --git a/tests/templates/test_subroutines/test_basis_rotation.py b/tests/templates/test_subroutines/test_basis_rotation.py
index a4328ca52..0f9f6412a 100644
--- a/tests/templates/test_subroutines/test_basis_rotation.py
+++ b/tests/templates/test_subroutines/test_basis_rotation.py
@@ -21,7 +21,6 @@ import pytest
 import pennylane as qml
 
 
-@pytest.mark.xfail  # to be fixed by [sc-51603]
 def test_standard_validity():
     """Run standard tests of operation validity."""
     weights = np.array(
@@ -402,8 +401,9 @@ class TestInterfaces:
 
         assert np.allclose(grads, np.zeros_like(unitary_matrix, dtype=complex), atol=tol, rtol=0)
 
+    @pytest.mark.parametrize("device_name", ("default.qubit", "reference.qubit"))
     @pytest.mark.jax
-    def test_jax_jit(self, tol):
+    def test_jax_jit(self, device_name, tol):
         """Test the jax interface."""
 
         import jax
@@ -417,7 +417,7 @@ class TestInterfaces:
             ]
         )
 
-        dev = qml.device("default.qubit", wires=3)
+        dev = qml.device(device_name, wires=3)
 
         circuit = jax.jit(qml.QNode(circuit_template, dev), static_argnames="check")
         circuit2 = qml.QNode(circuit_template, dev)