Repository: PennyLaneAI/pennylane
Pull request #7223 (2025-04-07 18:14:55)

[Decomposition] fix error when resource params contain uncomparable dict keys
**Context:**

Fixes an error discovered in `labs`.

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7223?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:
> Please [upload](https://docs.codecov.com/docs/codecov-uploader) report for BASE (`v0.41.0-rc0@625f114`). [Learn more](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) about missing BASE report.

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


```diff
@@              Coverage Diff               @@
##             v0.41.0-rc0    #7223   +/-   ##
==============================================
  Coverage               ?   99.64%           
==============================================
  Files                  ?      513           
  Lines                  ?    50154           
  Branches               ?        0           
==============================================
  Hits                   ?    49978           
  Misses                 ?      176           
  Partials               ?        0           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7223?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>
JerryChen97: Did we successfully trigger the Lab Tests? Didn't see them in the list

astralcai: > Did we successfully trigger the Lab Tests? Didn't see them in the list

Weird... I thought adding the `labs` label should trigger labs CI
JerryChen97: > > Did we successfully trigger the Lab Tests? Didn't see them in the list
> 
> Weird... I thought adding the `labs` label should trigger labs CI

Yeah... So I knew that previously a simple adding label won't triggerred it automatically; had to re-commit to trigger. However, this time, I didn't even see it in the skipped list. I went to Action also there's no place for me to trigger it. So I'm just lost.
Jaybsoni: It only triggers when the changes are localized to the labs repository.
astralcai: > It only triggers when the changes are localized to the labs repository.

Can we manually trigger it? The changes are not localized to the `labs` repository but it affects `labs`.
Jaybsoni: > > It only triggers when the changes are localized to the labs repository.
> 
> Can we manually trigger it? The changes are not localized to the `labs` repository but it affects `labs`.

I don't think so, we might need to change the action to allow for manual triggering. One suggestion would be to open a dummy PR which merges into this one with a cosmetic change in the labs repo if you really want to check. Otherwise I would say if you run pytest on the `pennylane/labs/tests` and it passes locally, then it should be good to go 👍🏼 
JerryChen97: Before we figured out how to trigger Labs Tests I guess we can only rely on locals. However, I'm not sure if the following failure is related or not. I post here in case it could be useful: 
```bash
(venv) ➜  pennylane git:(resource-bug) ✗ pytest pennylane/labs/tests/resource_estimation/test_resource_container.py::TestCompressedResourceOp::test_init
=========================================================== test session starts ===========================================================
platform linux -- Python 3.11.11, pytest-8.3.4, pluggy-1.5.0
benchmark: 5.1.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/yushao.chen/Documents/pennylane/pennylane/labs/tests
configfile: pytest.ini
plugins: forked-1.6.0, split-0.10.0, nbmake-1.5.5, mock-3.14.0, rng-1.0.0, anyio-4.8.0, flaky-3.8.1, xdist-3.6.1, cov-6.0.0, benchmark-5.1.0
collected 5 items                                                                                                                         

pennylane/labs/tests/resource_estimation/test_resource_container.py ..F..                                                           [100%]

================================================================ FAILURES =================================================================
____________________________ TestCompressedResourceOp.test_init[DummyQSVT-ResourceDummyQSVT-parameters2-None] _____________________________

self = <test_resource_container.TestCompressedResourceOp object at 0x7f77c5027dd0>, name = 'DummyQSVT'
op_type = <class 'test_resource_container.ResourceDummyQSVT'>, parameters = {'num_angles': 5, 'num_wires': 3}, name_param = None

    @pytest.mark.parametrize("name, op_type, parameters, name_param", compressed_ops_and_params_lst)
    def test_init(self, name, op_type, parameters, name_param):
        """Test that we can correctly instantiate CompressedResourceOp"""
        cr_op = CompressedResourceOp(op_type, parameters, name=name_param)
    
        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())
E       AssertionError: assert (('num_angles...um_wires', 3)) == (('num_wires'...m_angles', 5))
E         
E         At index 0 diff: ('num_angles', 5) != ('num_wires', 3)
E         Use -v to get more diff

pennylane/labs/tests/resource_estimation/test_resource_container.py:92: AssertionError
============================================================ warnings summary =============================================================
venv/lib/python3.11/site-packages/autograd/numpy/numpy_wrapper.py:7
  /home/yushao.chen/Documents/pennylane/venv/lib/python3.11/site-packages/autograd/numpy/numpy_wrapper.py:7: DeprecationWarning: numpy.core.einsumfunc is deprecated and has been renamed to numpy._core.einsumfunc. The numpy._core namespace contains private NumPy internals and its use is discouraged, as NumPy internals can change without warning in any release. In practice, most real-world usage of numpy.core is to access functionality in the public NumPy API. If that is the case, use the public NumPy API. If not, you are using NumPy internals. If you would still like to access an internal attribute, use numpy._core.einsumfunc._parse_einsum_input.
    from numpy.core.einsumfunc import _parse_einsum_input

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
========================================================= short test summary info =========================================================
FAILED pennylane/labs/tests/resource_estimation/test_resource_container.py::TestCompressedResourceOp::test_init[DummyQSVT-ResourceDummyQSVT-parameters2-None] - AssertionError: assert (('num_angles...um_wires', 3)) == (('num_wires'...m_angles', 5))
```
JerryChen97: ```yml
name: PennyLane Labs Unit-Tests
on:
  pull_request:
    branches:
      - master
```
Oh it seems truly only to work for master-based PR :sweat: 
astralcai: Changelog added.

Patch:
diff --git a/doc/releases/changelog-0.41.0.md b/doc/releases/changelog-0.41.0.md
index 44392c5cb..71a02cd48 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 
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


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."""