Repository: PennyLaneAI/pennylane

Issue:
[Bug] `PauliSentence.to_mat(wire_order)` cannot handle Identities with wires
```python
PauliSentence({PauliWord({}): 1.}).to_mat([0, 1])
```

```pycon
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[4], line 1
----> 1 PauliSentence({PauliWord({}): 1.}).to_mat([0, 1])

File ~/Xanadu/pennylane/pennylane/pauli/pauli_arithmetic.py:876, in PauliSentence.to_mat(self, wire_order, format, buffer_size)
    864 mats_and_wires_gen = (
    865     (
    866         coeff * pw.to_mat(wire_order=_pw_wires(pw.wires), format=format),
   (...)
    869     for pw, coeff in self.items()
    870 )
    872 reduced_mat, result_wire_order = math.reduce_matrices(
    873     mats_and_wires_gen=mats_and_wires_gen, reduce_func=math.add
    874 )
--> 876 return math.expand_matrix(reduced_mat, result_wire_order, wire_order=wire_order)

File ~/Xanadu/pennylane/pennylane/math/matrix_manipulation.py:135, in expand_matrix(mat, wires, wire_order, sparse_format)
    131 # get a subset of `wire_order` values that contain all wire labels inside `wires` argument
    132 # e.g. wire_order = [0, 1, 2, 3, 4]; wires = [3, 0, 2]
    133 # --> subset_wire_order = [0, 1, 2, 3]; expanded_wires = [3, 0, 2, 1]
    134 wire_indices = [wire_order.index(wire) for wire in wires]
--> 135 subset_wire_order = wire_order[min(wire_indices) : max(wire_indices) + 1]
    136 wire_difference = list(set(subset_wire_order) - set(wires))
    137 expanded_wires = wires + wire_difference

ValueError: min() arg is an empty sequence
```

Discussion:
trbromley: Thanks @Tarun-Kumar07 for this! I've tagged some reviewers who will take a look soon.
albi3ro: Just for our internal tracking: [sc-58518]
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5407?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.63%. Comparing base [(`186faaf`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/186faaff78f789c06490366d8aa655cf5592171b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`c5bd4ae`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5407?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


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


```diff
@@            Coverage Diff             @@
##           master    #5407      +/-   ##
==========================================
- Coverage   99.63%   99.63%   -0.01%     
==========================================
  Files         399      399              
  Lines       37112    36829     -283     
==========================================
- Hits        36977    36693     -284     
- Misses        135      136       +1     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/5407?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/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 0759bc717..2012081bb 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -264,9 +264,13 @@
 * We no longer perform unwanted dtype promotion in the `pauli_rep` of `SProd` instances when using tensorflow.
   [(#5246)](https://github.com/PennyLaneAI/pennylane/pull/5246)
 
-* Fixed `TestQubitIntegration.test_counts` in `tests/interfaces/test_jax_qnode.py` to always produce counts for all outcomes.
+* Fixed `TestQubitIntegration.test_counts` in `tests/interfaces/test_jax_qnode.py` to always produce counts for all
+  outcomes.
   [(#5336)](https://github.com/PennyLaneAI/pennylane/pull/5336)
 
+* Fixed `PauliSentence.to_mat(wire_order)` to support identities with wires.
+  [(#5407)](https://github.com/PennyLaneAI/pennylane/pull/5407)
+
 <h3>Contributors ✍️</h3>
 
 This release contains contributions from (in alphabetical order):
diff --git a/pennylane/pauli/pauli_arithmetic.py b/pennylane/pauli/pauli_arithmetic.py
index 358047137..8b64583c0 100644
--- a/pennylane/pauli/pauli_arithmetic.py
+++ b/pennylane/pauli/pauli_arithmetic.py
@@ -832,7 +832,16 @@ class PauliSentence(dict):
             the PauliWord is empty ({}), choose any arbitrary wire from the
             PauliSentence it is composed in.
             """
-            return w or Wires(self.wires[0]) if self.wires else self.wires
+            if w:
+                # PauliWord is not empty, so we can use its wires
+                return Wires(w)
+
+            if wire_order:
+                # PauliWord is empty, treat it as Identity operator on any wire
+                # Pick any arbitrary wire from wire_order
+                return Wires(wire_order[0])
+
+            return wire_order
 
         if len(self) == 0:
             n = len(wire_order) if wire_order is not None else 0


Test patch:
diff --git a/tests/pauli/test_pauli_arithmetic.py b/tests/pauli/test_pauli_arithmetic.py
index 210541cf8..79bb8297c 100644
--- a/tests/pauli/test_pauli_arithmetic.py
+++ b/tests/pauli/test_pauli_arithmetic.py
@@ -23,7 +23,6 @@ import pennylane as qml
 from pennylane import numpy as np
 from pennylane.pauli.pauli_arithmetic import PauliWord, PauliSentence, I, X, Y, Z
 
-
 matI = np.eye(2)
 matX = np.array([[0, 1], [1, 0]])
 matY = np.array([[0, -1j], [1j, 0]])
@@ -877,6 +876,11 @@ class TestPauliSentence:
         assert sparse.issparse(res_sparse)
         assert qml.math.allclose(res_sparse.todense(), true_res)
 
+    def test_empty_pauli_to_mat_with_wire_order(self):
+        """Test the to_mat method with an empty PauliSentence and PauliWord and an external wire order."""
+        actual = PauliSentence({PauliWord({}): 1.5}).to_mat([0, 1])
+        assert np.allclose(actual, 1.5 * np.eye(4))
+
     ps_wire_order = ((ps1, []), (ps1, [0, 1, 2, "a", "b"]), (ps3, [0, 1, "c"]))
 
     @pytest.mark.parametrize("ps, wire_order", ps_wire_order)