Repository: PennyLaneAI/pennylane
Pull request #7236 (2025-04-09 03:08:03)

Daily rc sync to master

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7236?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.64%. Comparing base [(`29a90b9`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/29a90b90322cc59c04a13dbdd5883c9ef4e6bb25?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`70efb3b`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/70efb3bc6f914657045cdacaf0a0403da6eac475?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    #7236   +/-   ##
=======================================
  Coverage   99.64%   99.64%           
=======================================
  Files         513      513           
  Lines       50172    50172           
=======================================
  Hits        49996    49996           
  Misses        176      176           
```

</details>

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

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/doc/releases/changelog-0.41.0.md b/doc/releases/changelog-0.41.0.md
index 44392c5cb..ec717b33c 100644
--- a/doc/releases/changelog-0.41.0.md
+++ b/doc/releases/changelog-0.41.0.md
@@ -17,6 +17,7 @@ module.
 [(#7058)](https://github.com/PennyLaneAI/pennylane/pull/7058)
 [(#7064)](https://github.com/PennyLaneAI/pennylane/pull/7064)
 [(#6951)](https://github.com/PennyLaneAI/pennylane/pull/6951)
+[(#7223)](https://github.com/PennyLaneAI/pennylane/pull/7223)
 
 PennyLane's new decomposition system offers a graph-based alternative to the current system, which provides 
 better resource efficiency and versatility by traversing an internal graph structure that is weighted 
@@ -712,6 +713,7 @@ With `qml.decompositions.enable_graph()`, the following new features are availab
 
 * `PSWAP.matrix()` and `PSWAP.eigvals()` now support parameter broadcasting.
   [(#7179)](https://github.com/PennyLaneAI/pennylane/pull/7179)
+  [(#7228)](https://github.com/PennyLaneAI/pennylane/pull/7228)
 
 * `Device.eval_jaxpr` now accepts an `execution_config` keyword argument.
   [(#6991)](https://github.com/PennyLaneAI/pennylane/pull/6991)
diff --git a/pennylane/decomposition/resources.py b/pennylane/decomposition/resources.py
index 9188a87d9..14188075e 100644
--- a/pennylane/decomposition/resources.py
+++ b/pennylane/decomposition/resources.py
@@ -137,7 +137,9 @@ class CompressedResourceOp:
 
 def _make_hashable(d):
     if isinstance(d, dict):
-        return tuple(sorted(((k, _make_hashable(v)) for k, v in d.items()), key=lambda x: x[0]))
+        return tuple(
+            sorted(((str(k), _make_hashable(v)) for k, v in d.items()), key=lambda x: x[0])
+        )
     if hasattr(d, "tolist"):
         return d.tolist()
     return d
diff --git a/pennylane/ops/qubit/parametric_ops_multi_qubit.py b/pennylane/ops/qubit/parametric_ops_multi_qubit.py
index 9462e87a3..f039b9092 100644
--- a/pennylane/ops/qubit/parametric_ops_multi_qubit.py
+++ b/pennylane/ops/qubit/parametric_ops_multi_qubit.py
@@ -1702,7 +1702,7 @@ class PSWAP(Operation):
 
         e = qml.math.exp(1j * phi)
         one = qml.math.ones_like(phi)
-        return qml.math.stack([one, one, -e, e])
+        return qml.math.transpose(qml.math.stack([one, one, -e, e]))
 
     def adjoint(self) -> "PSWAP":
         (phi,) = self.parameters


Test patch:
diff --git a/pennylane/labs/tests/resource_estimation/test_resource_container.py b/pennylane/labs/tests/resource_estimation/test_resource_container.py
index 4dbe7e516..4c3983929 100644
--- a/pennylane/labs/tests/resource_estimation/test_resource_container.py
+++ b/pennylane/labs/tests/resource_estimation/test_resource_container.py
@@ -89,7 +89,7 @@ class TestCompressedResourceOp:
         assert cr_op._name == name
         assert cr_op.op_type is op_type
         assert cr_op.params == parameters
-        assert cr_op._hashable_params == tuple(parameters.items())
+        assert cr_op._hashable_params == tuple(sorted((str(k), v) for k, v in parameters.items()))
 
     def test_hash(self):
         """Test that the hash method behaves as expected"""
diff --git a/tests/decomposition/test_resources.py b/tests/decomposition/test_resources.py
index 6bf7209e0..328c92d45 100644
--- a/tests/decomposition/test_resources.py
+++ b/tests/decomposition/test_resources.py
@@ -136,6 +136,24 @@ class TestCompressedResourceOp:
         )
         assert isinstance(hash(op), int)
 
+    def test_hash_unhashable_keys(self):
+        """Tests that a CompressedResourceOp is hashable when the params contain unhashable keys."""
+
+        op = CompressedResourceOp(
+            qml.ops.Exp,
+            {
+                "base_class": qml.ops.LinearCombination,
+                "base_params": {},
+                "base_pauli_rep": qml.Hamiltonian(
+                    [1.11, 0.12, -3.4, 5],
+                    [qml.X(0) @ qml.X(1), qml.Z(2), qml.Y(0) @ qml.Y(1), qml.I((0, 1, 2))],
+                ).pauli_rep,
+                "coeff": 1.2j,
+                "num_steps": 3,
+            },
+        )
+        assert isinstance(hash(op), int)
+
     def test_same_params_same_hash(self):
         """Tests that two ops with the same params have the same hash."""
 
diff --git a/tests/ops/qubit/test_parametric_ops.py b/tests/ops/qubit/test_parametric_ops.py
index 8e57e73c9..01959a507 100644
--- a/tests/ops/qubit/test_parametric_ops.py
+++ b/tests/ops/qubit/test_parametric_ops.py
@@ -699,6 +699,9 @@ class TestDecompositions:
         assert np.allclose(decomposed_matrix, exp)
 
 
+pswap_angles = list(np.linspace(-np.pi, np.pi, 11)) + [np.linspace(-1, 1, 11)]
+
+
 class TestMatrix:
     def test_phase_shift(self, tol):
         """Test phase shift is correct"""
@@ -1001,44 +1004,89 @@ class TestMatrix:
             qml.PSWAP(param, wires=[0, 1]).matrix(), get_expected(param), atol=tol, rtol=0
         )
 
-    @pytest.mark.parametrize("phi", np.linspace(-np.pi, np.pi, 10))
+    def test_pswap_broadcasted(self, tol):
+        """Test that the broadcasted PSWAP operation is correct"""
+        batch_size = 3
+        z = np.zeros(batch_size)
+        mat = qml.PSWAP.compute_matrix(z)
+        assert qml.math.shape(mat) == (batch_size, 4, 4)
+        assert np.allclose(mat, np.diag([1, 1, 1, 1])[[0, 2, 1, 3]], atol=tol, rtol=0)
+        mat = qml.PSWAP(z, wires=[0, 1]).matrix()
+        assert qml.math.shape(mat) == (batch_size, 4, 4)
+        assert np.allclose(
+            mat,
+            np.diag([1, 1, 1, 1])[[0, 2, 1, 3]],
+            atol=tol,
+            rtol=0,
+        )
+
+        def get_expected(theta):
+            return np.array(
+                [np.diag([1, np.exp(1j * t), np.exp(1j * t), 1])[[0, 2, 1, 3]] for t in theta]
+            )
+
+        param = np.array([np.pi / 2, np.pi])
+        assert np.allclose(qml.PSWAP.compute_matrix(param), get_expected(param), atol=tol, rtol=0)
+        assert np.allclose(
+            qml.PSWAP(param, wires=[0, 1]).matrix(), get_expected(param), atol=tol, rtol=0
+        )
+
+        param = np.array([2.152, np.pi / 2, 0.213])
+        assert np.allclose(qml.PSWAP.compute_matrix(param), get_expected(param), atol=tol, rtol=0)
+        assert np.allclose(
+            qml.PSWAP(param, wires=[0, 1]).matrix(), get_expected(param), atol=tol, rtol=0
+        )
+
+    @pytest.mark.parametrize("phi", pswap_angles)
     def test_pswap_eigvals(self, phi):
         """Test eigenvalues computation for PSWAP"""
         evs = qml.PSWAP.compute_eigvals(phi)
-        evs_expected = [1, 1, -qml.math.exp(1j * phi), qml.math.exp(1j * phi)]
+        if len(qml.math.shape(phi)) > 0:
+            evs_expected = np.stack([[1, 1, -exp, exp] for exp in qml.math.exp(1j * phi)])
+        else:
+            evs_expected = [1, 1, -qml.math.exp(1j * phi), qml.math.exp(1j * phi)]
         assert qml.math.allclose(evs, evs_expected)
 
     @pytest.mark.tf
-    @pytest.mark.parametrize("phi", np.linspace(-np.pi, np.pi, 10))
+    @pytest.mark.parametrize("phi", pswap_angles)
     def test_pswap_eigvals_tf(self, phi):
         """Test eigenvalues computation for PSWAP using Tensorflow interface"""
         import tensorflow as tf
 
         param_tf = tf.Variable(phi)
         evs = qml.PSWAP.compute_eigvals(param_tf)
-        evs_expected = [1, 1, -qml.math.exp(1j * phi), qml.math.exp(1j * phi)]
+        if len(qml.math.shape(phi)) > 0:
+            evs_expected = np.stack([[1, 1, -exp, exp] for exp in qml.math.exp(1j * phi)])
+        else:
+            evs_expected = [1, 1, -qml.math.exp(1j * phi), qml.math.exp(1j * phi)]
         assert qml.math.allclose(evs, evs_expected)
 
     @pytest.mark.torch
-    @pytest.mark.parametrize("phi", np.linspace(-np.pi, np.pi, 10))
+    @pytest.mark.parametrize("phi", pswap_angles)
     def test_pswap_eigvals_torch(self, phi):
         """Test eigenvalues computation for PSWAP using Torch interface"""
         import torch
 
         param_torch = torch.tensor(phi)
         evs = qml.PSWAP.compute_eigvals(param_torch)
-        evs_expected = [1, 1, -qml.math.exp(1j * phi), qml.math.exp(1j * phi)]
+        if len(qml.math.shape(phi)) > 0:
+            evs_expected = np.stack([[1, 1, -exp, exp] for exp in qml.math.exp(1j * phi)])
+        else:
+            evs_expected = [1, 1, -qml.math.exp(1j * phi), qml.math.exp(1j * phi)]
         assert qml.math.allclose(evs, evs_expected)
 
     @pytest.mark.jax
-    @pytest.mark.parametrize("phi", np.linspace(-np.pi, np.pi, 10))
+    @pytest.mark.parametrize("phi", pswap_angles)
     def test_pswap_eigvals_jax(self, phi):
         """Test eigenvalues computation for PSWAP using JAX interface"""
         import jax
 
         param_jax = jax.numpy.array(phi)
         evs = qml.PSWAP.compute_eigvals(param_jax)
-        evs_expected = [1, 1, -qml.math.exp(1j * phi), qml.math.exp(1j * phi)]
+        if len(qml.math.shape(phi)) > 0:
+            evs_expected = np.stack([[1, 1, -exp, exp] for exp in qml.math.exp(1j * phi)])
+        else:
+            evs_expected = [1, 1, -qml.math.exp(1j * phi), qml.math.exp(1j * phi)]
         assert qml.math.allclose(evs, evs_expected)
 
     @pytest.mark.tf