Repository: PennyLaneAI/pennylane
Pull request #5618 (2024-05-01 18:42:15)

Validate `LinearCombination` and `qml.dot` inputs
**Context:**

Now, we technically allow something like `LinearCombination([1,2], qml.X(0)@qml.Y(1))`, but that's probabably doesn't do what the user was actually expecting, and this syntax could lead to some very difficult to diagnose bugs.

**Description of the Change:**

Explicitly forbid the above syntax.

**Benefits:**

Preventing people from doing weird things accidentally.

**Possible Drawbacks:**

More validation.  Maybe someone actually does want the strange behaviour.

**Related GitHub Issues:**
 
[sc-62162]

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5618?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:
> :exclamation: No coverage uploaded for pull request base (`v0.36.0-rc0@8ec2720`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#section-missing-base-commit).


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


```diff
@@              Coverage Diff               @@
##             v0.36.0-rc0    #5618   +/-   ##
==============================================
  Coverage               ?   99.69%           
==============================================
  Files                  ?      412           
  Lines                  ?    38307           
  Branches               ?        0           
==============================================
  Hits                   ?    38189           
  Misses                 ?      118           
  Partials               ?        0           
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5618?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/pennylane/ops/functions/dot.py b/pennylane/ops/functions/dot.py
index 743d868ff..d2c8fa04e 100644
--- a/pennylane/ops/functions/dot.py
+++ b/pennylane/ops/functions/dot.py
@@ -39,7 +39,8 @@ def dot(
 
     Args:
         coeffs (Sequence[float, Callable]): sequence containing the coefficients of the linear combination
-        ops (Sequence[Operator]): sequence containing the operators of the linear combination
+        ops (Sequence[Operator, PauliWord, PauliSentence]): sequence containing the operators of the linear combination.
+           Can also be ``PauliWord`` or ``PauliSentence`` instances.
         pauli (bool, optional): If ``True``, a :class:`~.PauliSentence`
             operator is used to represent the linear combination. If False, a :class:`Sum` operator
             is returned. Defaults to ``False``. Note that when ``ops`` consists solely of ``PauliWord``
@@ -136,6 +137,12 @@ def dot(
     if len(coeffs) == 0 and len(ops) == 0:
         raise ValueError("Cannot compute the dot product of an empty sequence.")
 
+    for t in (Operator, PauliWord, PauliSentence):
+        if isinstance(ops, t):
+            raise ValueError(
+                f"ops must be an Iterable of {t.__name__}'s, not a {t.__name__} itself."
+            )
+
     if any(callable(c) for c in coeffs):
         return ParametrizedHamiltonian(coeffs, ops)
 
diff --git a/pennylane/ops/op_math/linear_combination.py b/pennylane/ops/op_math/linear_combination.py
index e666ee121..39d65cc72 100644
--- a/pennylane/ops/op_math/linear_combination.py
+++ b/pennylane/ops/op_math/linear_combination.py
@@ -117,6 +117,10 @@ class LinearCombination(Sum):
         _pauli_rep=None,
         id=None,
     ):
+        if isinstance(observables, Operator):
+            raise ValueError(
+                "observables must be an Iterable of Operator's, and not an Operator itself."
+            )
         if qml.math.shape(coeffs)[0] != len(observables):
             raise ValueError(
                 "Could not create valid LinearCombination; "


Test patch:
diff --git a/tests/ops/functions/test_dot.py b/tests/ops/functions/test_dot.py
index 61ea6ec46..73a87e152 100644
--- a/tests/ops/functions/test_dot.py
+++ b/tests/ops/functions/test_dot.py
@@ -50,6 +50,11 @@ H3 = 1.0 * X0 + 2.0 * Y0 + 3.0 * Z0
 class TestDotSum:
     """Unittests for the dot function when ``pauli=False``."""
 
+    def test_error_if_ops_operator(self):
+        """Test that dot raises an error if ops is an operator itself."""
+        with pytest.raises(ValueError, match=r"ops must be an Iterable of Operator's"):
+            qml.dot([1, 1], qml.X(0) @ qml.Y(1))
+
     def test_dot_returns_sum(self):
         """Test that the dot function returns a Sum operator when ``pauli=False``."""
         c = [1.0, 2.0, 3.0]
@@ -280,6 +285,20 @@ ops0 = [
 class TestDotPauliSentence:
     """Unittest for the dot function when ``pauli=True``"""
 
+    def test_error_if_ops_PauliWord(self):
+        """Test that dot raises an error if ops is a PauliWord itself."""
+        _pw = qml.pauli.PauliWord({0: "X", 1: "Y"})
+        with pytest.raises(ValueError, match=r"ops must be an Iterable of PauliWord's"):
+            qml.dot([1, 2], _pw)
+
+    def test_error_if_ops_PauliSentence(self):
+        """Test that dot raises an error if ops is a PauliSentence itself."""
+        _pw1 = qml.pauli.PauliWord({0: "X", 1: "Y"})
+        _pw2 = qml.pauli.PauliWord({2: "Z"})
+        ps = 2 * _pw1 + 3 * _pw2
+        with pytest.raises(ValueError, match=r"ops must be an Iterable of PauliSentence's"):
+            qml.dot([1, 2], ps)
+
     def test_dot_returns_pauli_sentence(self):
         """Test that the dot function returns a PauliSentence class."""
         ps = qml.dot(coeffs0, ops0, pauli=True)
diff --git a/tests/ops/op_math/test_linear_combination.py b/tests/ops/op_math/test_linear_combination.py
index a71714b50..4e2521327 100644
--- a/tests/ops/op_math/test_linear_combination.py
+++ b/tests/ops/op_math/test_linear_combination.py
@@ -564,6 +564,12 @@ dev = qml.device("default.qubit", wires=2)
 class TestLinearCombination:
     """Test the LinearCombination class"""
 
+    def test_error_if_observables_operator(self):
+        """Test thatt an error is raised if an operator is provided to observables."""
+
+        with pytest.raises(ValueError, match=r"observables must be an Iterable of Operator's"):
+            qml.ops.LinearCombination([1, 1], qml.X(0) @ qml.Y(1))
+
     PAULI_REPS = (
         ([], [], PauliSentence({})),
         (