Repository: PennyLaneAI/pennylane

Issue:
[BUG] pennylane.tape.Unwarp does not unwrap Hamiltonian coefficients
### Expected behavior

`qml.tape.unwrap` convert all ArrayBox in a tape to usual arrays.

### Actual behavior

Coefficients of the `Hamiltonian` object instance (accessible via `ham.coeffs`) are not unwrapped.

### Additional information

_No response_

### Source code

_No response_

### Tracebacks

_No response_

### System information

```shell
Name: PennyLane
Version: 0.26.0.dev0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /home/chae-yeun/work/Xanadu/devel-py39/lib/python3.9/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, retworkx, scipy, semantic-version, toml
Required-by: PennyLane-Lightning

Platform info:           Linux-5.14.0-1042-oem-x86_64-with-glibc2.35
Python version:          3.9.13
Numpy version:           1.23.2
Scipy version:           1.9.0
Installed devices:
- lightning.qubit (PennyLane-Lightning-0.26.0.dev1)
- default.gaussian (PennyLane-0.26.0.dev0)
- default.mixed (PennyLane-0.26.0.dev0)
- default.qubit (PennyLane-0.26.0.dev0)
- default.qubit.autograd (PennyLane-0.26.0.dev0)
- default.qubit.jax (PennyLane-0.26.0.dev0)
- default.qubit.tf (PennyLane-0.26.0.dev0)
- default.qubit.torch (PennyLane-0.26.0.dev0)
- default.qutrit (PennyLane-0.26.0.dev0)
```


### Existing GitHub issues

- [X] I have searched existing GitHub issues to make sure the issue does not already exist.

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#4540](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (3382547) into [master](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/a9bc37a5f10490694179d17a6becfbf00e3976ab?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (a9bc37a) will **decrease** coverage by `0.06%`.
> Report is 8 commits behind head on master.
> The diff coverage is `99.77%`.

```diff
@@            Coverage Diff             @@
##           master    #4540      +/-   ##
==========================================
- Coverage   99.71%   99.65%   -0.06%     
==========================================
  Files         379      377       -2     
  Lines       34398    33102    -1296     
==========================================
- Hits        34300    32988    -1312     
- Misses         98      114      +16     
```


| [Files Changed](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/\_\_init\_\_.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL19faW5pdF9fLnB5) | `100.00% <ø> (ø)` | |
| [pennylane/\_device.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL19kZXZpY2UucHk=) | `99.40% <ø> (-0.30%)` | :arrow_down: |
| [pennylane/data/data\_manager/\_\_init\_\_.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RhdGEvZGF0YV9tYW5hZ2VyL19faW5pdF9fLnB5) | `98.55% <ø> (ø)` | |
| [pennylane/devices/experimental/default\_qubit\_2.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZXhwZXJpbWVudGFsL2RlZmF1bHRfcXViaXRfMi5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/drawer/style.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RyYXdlci9zdHlsZS5weQ==) | `99.09% <ø> (ø)` | |
| [pennylane/gradients/finite\_difference.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2dyYWRpZW50cy9maW5pdGVfZGlmZmVyZW5jZS5weQ==) | `100.00% <ø> (+0.53%)` | :arrow_up: |
| [pennylane/gradients/hadamard\_gradient.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2dyYWRpZW50cy9oYWRhbWFyZF9ncmFkaWVudC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/gradients/parameter\_shift.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2dyYWRpZW50cy9wYXJhbWV0ZXJfc2hpZnQucHk=) | `100.00% <ø> (ø)` | |
| [pennylane/gradients/pulse\_generator\_gradient.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2dyYWRpZW50cy9wdWxzZV9nZW5lcmF0b3JfZ3JhZGllbnQucHk=) | `100.00% <ø> (ø)` | |
| [pennylane/gradients/pulse\_gradient.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2dyYWRpZW50cy9wdWxzZV9ncmFkaWVudC5weQ==) | `99.47% <ø> (-0.01%)` | :arrow_down: |
| ... and [72 more](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/4540/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)



Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index ea10f7a9b..2f805d618 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -66,6 +66,9 @@
 
 <h3>Bug fixes 🐛</h3>
 
+* `convert_to_numpy_parameters` now uses `qml.ops.functions.bind_new_parameters`. This reinitializes the operation and
+  makes sure everything references the new numpy parameters.
+
 * `tf.function` no longer breaks `ProbabilityMP.process_state` which is needed by new devices.
   [(#4470)](https://github.com/PennyLaneAI/pennylane/pull/4470)
 
diff --git a/pennylane/ops/functions/bind_new_parameters.py b/pennylane/ops/functions/bind_new_parameters.py
index 2c7c11784..1d7879305 100644
--- a/pennylane/ops/functions/bind_new_parameters.py
+++ b/pennylane/ops/functions/bind_new_parameters.py
@@ -47,7 +47,7 @@ def bind_new_parameters(op: Operator, params: Sequence[TensorLike]) -> Operator:
     """
     try:
         return op.__class__(*params, wires=op.wires, **copy.deepcopy(op.hyperparameters))
-    except TypeError:
+    except (TypeError, ValueError):
         # operation is doing something different with its call signature.
         new_op = copy.deepcopy(op)
         new_op.data = tuple(params)
@@ -168,7 +168,10 @@ def bind_new_parameters_pow(op: Pow, params: Sequence[TensorLike]):
 
 @bind_new_parameters.register
 def bind_new_parameters_hamiltonian(op: qml.Hamiltonian, params: Sequence[TensorLike]):
-    return qml.Hamiltonian(params, op.ops)
+    new_H = qml.Hamiltonian(params, op.ops)
+    if op.grouping_indices is not None:
+        new_H.grouping_indices = op.grouping_indices
+    return new_H
 
 
 @bind_new_parameters.register
diff --git a/pennylane/transforms/convert_to_numpy_parameters.py b/pennylane/transforms/convert_to_numpy_parameters.py
index d16454c1e..92bebf416 100644
--- a/pennylane/transforms/convert_to_numpy_parameters.py
+++ b/pennylane/transforms/convert_to_numpy_parameters.py
@@ -15,34 +15,32 @@
 This file contains preprocessings steps that may be called internally
 during execution.
 """
-import copy
-
 import pennylane as qml
 from pennylane import math
 from pennylane.tape import QuantumScript
 
 
+# pylint: disable=no-member
 def _convert_op_to_numpy_data(op: qml.operation.Operator) -> qml.operation.Operator:
     if math.get_interface(*op.data) == "numpy":
         return op
     # Use operator method to change parameters when it become available
-    copied_op = copy.copy(op)
-    copied_op.data = math.unwrap(op.data)
-    return copied_op
+    return qml.ops.functions.bind_new_parameters(op, math.unwrap(op.data))
 
 
+# pylint: disable=no-member
 def _convert_measurement_to_numpy_data(
     m: qml.measurements.MeasurementProcess,
 ) -> qml.measurements.MeasurementProcess:
-    if m.obs is None or math.get_interface(*m.obs.data) == "numpy":
+    if m.obs is None:
+        if m.eigvals() is None or math.get_interface(m.eigvals()) == "numpy":
+            return m
+        return type(m)(wires=m.wires, eigvals=math.unwrap(m.eigvals()))
+
+    if math.get_interface(*m.obs.data) == "numpy":
         return m
-    # Use measurement method to change parameters when it becomes available
-    copied_m = copy.copy(m)
-    if isinstance(copied_m.obs, qml.operation.Tensor):
-        copied_m.obs.data = math.unwrap([o.data for o in m.obs.obs])
-    else:
-        copied_m.obs.data = math.unwrap(m.obs.data)
-    return copied_m
+    new_obs = qml.ops.functions.bind_new_parameters(m.obs, math.unwrap(m.obs.data))
+    return type(m)(obs=new_obs)
 
 
 # pylint: disable=protected-access


Test patch:
diff --git a/tests/interfaces/test_autograd_qnode.py b/tests/interfaces/test_autograd_qnode.py
index d1512ef4f..476e694f6 100644
--- a/tests/interfaces/test_autograd_qnode.py
+++ b/tests/interfaces/test_autograd_qnode.py
@@ -87,7 +87,7 @@ class TestQNode:
 
         def mock_apply(*args, **kwargs):
             for op in args[0]:
-                param_data.extend(op.data.copy())
+                param_data.extend(op.data)
 
         mocker.patch.object(dev, "apply", side_effect=mock_apply)
         circuit(x, y)
diff --git a/tests/ops/functions/test_bind_new_parameters.py b/tests/ops/functions/test_bind_new_parameters.py
index f9ea25ff1..4c4f24418 100644
--- a/tests/ops/functions/test_bind_new_parameters.py
+++ b/tests/ops/functions/test_bind_new_parameters.py
@@ -207,6 +207,15 @@ def test_hamiltonian(H, new_coeffs, expected_H):
     assert new_H is not H
 
 
+def test_hamiltonian_grouping_indices():
+    """Test that bind_new_parameters with a Hamiltonian preserves the grouping indices."""
+    H = qml.Hamiltonian([1.0, 2.0], [qml.PauliX(0), qml.PauliX(1)])
+    H.compute_grouping()
+    new_H = bind_new_parameters(H, [2.3, 3.4])
+    assert H.grouping_indices == new_H.grouping_indices
+    assert new_H.data == (2.3, 3.4)
+
+
 @pytest.mark.parametrize(
     "op, new_params, expected_op",
     [
diff --git a/tests/transforms/test_convert_to_numpy_parameters.py b/tests/transforms/test_convert_to_numpy_parameters.py
index 7dd2c4a8c..8e3712195 100644
--- a/tests/transforms/test_convert_to_numpy_parameters.py
+++ b/tests/transforms/test_convert_to_numpy_parameters.py
@@ -44,11 +44,16 @@ def test_convert_arrays_to_numpy(framework, shots):
 
     numpy_data = np.array(0.62)
 
-    ops = [qml.RX(x, 0), qml.RY(y, 1), qml.CNOT((0, 1)), qml.RZ(numpy_data, 0)]
+    ops = [
+        qml.StatePrep(state, 0),
+        qml.RX(x, 0),
+        qml.RY(y, 1),
+        qml.CNOT((0, 1)),
+        qml.RZ(numpy_data, 0),
+    ]
     m = [qml.state(), qml.expval(qml.Hermitian(M, 0))]
-    prep = [qml.StatePrep(state, 0)]
 
-    qs = qml.tape.QuantumScript(ops, m, prep, shots=shots)
+    qs = qml.tape.QuantumScript(ops, m, shots=shots)
     new_qs = convert_to_numpy_parameters(qs)
 
     # check ops that should be unaltered
@@ -111,3 +116,24 @@ def test_unwraps_tensor_observables():
 
     unwrapped_m = _convert_measurement_to_numpy_data(m)
     assert qml.math.get_interface(*unwrapped_m.obs.data) == "numpy"
+
+
+@pytest.mark.torch
+def test_unwraps_mp_eigvals():
+    """Test that a measurememnt process with autograd eigvals unwraps them to numpy."""
+    import torch
+
+    eigvals = torch.tensor([0.5, 0.5])
+    m = qml.measurements.ExpectationMP(eigvals=eigvals, wires=qml.wires.Wires(0))
+
+    unwrapped_m = _convert_measurement_to_numpy_data(m)
+    assert qml.math.get_interface(unwrapped_m.eigvals) == "numpy"
+
+
+def test_mp_numpy_eigvals():
+    """Test that a measurement process with numpy eigvals is returned unchanged."""
+    eigvals = np.array([0.5, 0.5])
+    m = qml.measurements.ExpectationMP(eigvals=eigvals, wires=qml.wires.Wires(0))
+
+    unwrapped_m = _convert_measurement_to_numpy_data(m)
+    assert m is unwrapped_m