Repository: PennyLaneAI/pennylane
Pull request #3339 (2022-11-11 21:35:10)

Use `convert_like` in `hamiltonian_expand`
The post-processing function of `hamiltonian_expand` doesn't preserve the type of the inputted results. Use `qml.math.convert_like`.

QML demo that uses `hamiltonian_expand` explicitly: `tutorial_error_mitigation.py` passes well. Otherwise relevant docstring examples were checked (though the sweep was not entirely extensively because `hamiltonian_expand` might be used in many cases).

github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3339?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3339](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3339?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (357d42c) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/1465ec62a48e97f9f9ae3b3cfd4b11b815290bd2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (1465ec6) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master    #3339   +/-   ##
=======================================
  Coverage   99.70%   99.70%           
=======================================
  Files         299      299           
  Lines       26136    26138    +2     
=======================================
+ Hits        26059    26061    +2     
  Misses         77       77           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3339?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/transforms/mitigate.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3339/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvbWl0aWdhdGUucHk=) | `100.00% <ø> (ø)` | |
| [pennylane/transforms/hamiltonian\_expand.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3339/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvaGFtaWx0b25pYW5fZXhwYW5kLnB5) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

antalszava: > Changelog? This seems like a bugfix.

Added.

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 628a6cbbe..4532e2e78 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -103,6 +103,9 @@
 
 <h3>Improvements</h3>
 
+* Added more input validation to `hamiltonian_expand` such that Hamiltonian objects with no terms raise an error.
+  [(#3339)](https://github.com/PennyLaneAI/pennylane/pull/3339)
+
 * Continuous integration checks are now performed for Python 3.11 and Torch v1.13. Python 3.7 is dropped.
   [(#3276)](https://github.com/PennyLaneAI/pennylane/pull/3276)
 
@@ -230,6 +233,9 @@ Deprecations cycles are tracked at [doc/developement/deprecations.rst](https://d
 
 <h3>Bug fixes</h3>
 
+* Fixed a bug where `hamiltonian_expand` didn't preserve the type of the inputted results in its output.
+  [(#3339)](https://github.com/PennyLaneAI/pennylane/pull/3339)
+
 * Fixed a bug that made `gradients.param_shift` raise an error when used with unshifted terms only
   in a custom recipe, and when using any unshifted terms at all under the new return type system.
   [(#3177)](https://github.com/PennyLaneAI/pennylane/pull/3177)
diff --git a/pennylane/transforms/hamiltonian_expand.py b/pennylane/transforms/hamiltonian_expand.py
index 3ad1e36ed..213122b8a 100644
--- a/pennylane/transforms/hamiltonian_expand.py
+++ b/pennylane/transforms/hamiltonian_expand.py
@@ -118,17 +118,20 @@ def hamiltonian_expand(tape, group=True):
     2
     """
 
-    hamiltonian = tape.measurements[0].obs
-
     if (
-        not isinstance(hamiltonian, qml.Hamiltonian)
-        or len(tape.measurements) > 1
+        len(tape.measurements) != 1
+        or not isinstance(hamiltonian := tape.measurements[0].obs, qml.Hamiltonian)
         or tape.measurements[0].return_type != qml.measurements.Expectation
     ):
         raise ValueError(
             "Passed tape must end in `qml.expval(H)`, where H is of type `qml.Hamiltonian`"
         )
 
+    if qml.math.shape(hamiltonian.coeffs) == (0,) and qml.math.shape(hamiltonian.ops) == (0,):
+        raise ValueError(
+            "The Hamiltonian in the tape has no terms defined - cannot perform the Hamiltonian expansion."
+        )
+
     # note: for backward passes of some frameworks
     # it is crucial to use the hamiltonian.data attribute,
     # and not hamiltonian.coeffs when recombining the results
@@ -172,7 +175,8 @@ def hamiltonian_expand(tape, group=True):
                     qml.math.dot(r_group, c_group)
                     for c_group, r_group in zip(coeff_groupings, res_groupings)
                 ]
-            return qml.math.sum(qml.math.stack(dot_products), axis=0)
+            summed_dot_products = qml.math.sum(qml.math.stack(dot_products), axis=0)
+            return qml.math.convert_like(summed_dot_products, res_groupings[0])
 
         return tapes, processing_fn
 
@@ -188,6 +192,7 @@ def hamiltonian_expand(tape, group=True):
     # pylint: disable=function-redefined
     def processing_fn(res):
         dot_products = [qml.math.dot(qml.math.squeeze(r), c) for c, r in zip(coeffs, res)]
-        return qml.math.sum(qml.math.stack(dot_products), axis=0)
+        summed_dot_products = qml.math.sum(qml.math.stack(dot_products), axis=0)
+        return qml.math.convert_like(summed_dot_products, res[0])
 
     return tapes, processing_fn
diff --git a/pennylane/transforms/mitigate.py b/pennylane/transforms/mitigate.py
index a80d70ccc..4d3e40aa9 100644
--- a/pennylane/transforms/mitigate.py
+++ b/pennylane/transforms/mitigate.py
@@ -410,7 +410,7 @@ def mitigate_with_zne(
 
     >>> qml.grad(circuit)(w1, w2)
     (array([-0.89319941,  0.37949841]),
-    array([[[-7.04121596e-01,  3.00073104e-01]],
+     array([[[-7.04121596e-01,  3.00073104e-01]],
             [[-6.41155176e-01,  8.32667268e-17]]]))
 
     .. details::


Test patch:
diff --git a/tests/returntypes/test_autograd_new.py b/tests/returntypes/test_autograd_new.py
index 30f61314e..7c6aec78e 100644
--- a/tests/returntypes/test_autograd_new.py
+++ b/tests/returntypes/test_autograd_new.py
@@ -202,7 +202,8 @@ class TestBatchTransformExecution:
         res = qml.execute([tape], dev, None, device_batch_transform=True)
         spy.assert_called()
 
-        assert isinstance(res[0], float)
+        assert isinstance(res[0], np.ndarray)
+        assert res[0].shape == ()
         assert np.allclose(res[0], np.cos(y), atol=0.1)
 
     def test_batch_transform_dynamic_shots(self):
diff --git a/tests/transforms/test_hamiltonian_expand.py b/tests/transforms/test_hamiltonian_expand.py
index 27b84f508..16b9d5799 100644
--- a/tests/transforms/test_hamiltonian_expand.py
+++ b/tests/transforms/test_hamiltonian_expand.py
@@ -77,6 +77,17 @@ OUTPUTS = [-1.5, -6, -1.5, -8]
 class TestHamiltonianExpand:
     """Tests for the hamiltonian_expand transform"""
 
+    def test_ham_with_no_terms_raises(self):
+        """Tests that the hamiltonian_expand transform raises an error for a Hamiltonian with no terms."""
+        mps = [qml.expval(qml.Hamiltonian([], []))]
+        tape = qml.tape.QuantumTape([], mps)
+
+        with pytest.raises(
+            ValueError,
+            match="The Hamiltonian in the tape has no terms defined - cannot perform the Hamiltonian expansion.",
+        ):
+            qml.transforms.hamiltonian_expand(tape)
+
     @pytest.mark.parametrize(("tape", "output"), zip(TAPES, OUTPUTS))
     def test_hamiltonians(self, tape, output):
         """Tests that the hamiltonian_expand transform returns the correct value"""
@@ -93,6 +104,7 @@ class TestHamiltonianExpand:
         expval = fn(results)
 
         assert np.isclose(output, expval)
+        assert type(results[0]) == type(expval)
 
     @pytest.mark.parametrize(("tape", "output"), zip(TAPES, OUTPUTS))
     def test_hamiltonians_no_grouping(self, tape, output):
@@ -111,6 +123,7 @@ class TestHamiltonianExpand:
         expval = fn(results)
 
         assert np.isclose(output, expval)
+        assert type(results[0]) == type(expval)
 
     def test_grouping_is_used(self):
         """Test that the grouping in a Hamiltonian is used"""