Repository navigation
Commit 8419760
Fix Pow/Pow2 eigvals branch cut on Python 3.14 (#10139)
> [!NOTE]
> Drafted with AI assistance (GitHub Copilot CLI), reviewed before
opening.
**Context:**
`Pow.eigvals()` / `Pow2.eigvals()` return the complex conjugate of the
correct eigenvalues for non-integer `z`, on Python 3.14 only. Regression
from #9802, which changed the cast idiom to `((1 + 0j) * value) **
self.z`.
Python 3.14 implements C99 mixed-mode real/complex arithmetic
([python/cpython#69639](python/cpython#69639)),
so `complex * float` is now componentwise instead of promoting the float
to `complex(x, +0.0)`:
| | `(1+0j) * -1.0` | `** 0.5` |
|---|---|---|
| 3.13 | `(-1+0j)` | `+1j` |
| 3.14 | `(-1-0j)` | `-1j` |
That `-0.0` puts `-1` on the wrong side of the branch cut of `**`. It is
specific to multiplication — `-1.0 + 0j` and `complex(-1.0)` are
unaffected.
All six `core-tests (*, 3.14)` shards fail on `main` (9 failures,
scattered by `pytest-split`):
https://github.com/PennyLaneAI/pennylane/actions/runs/34502136095
**Description of the Change:**
Restore the cast-based form in `pow.py` and `pow2.py`:
```python
return math.cast(self.base.eigvals(), "complex128") ** self.z
```
Both tests now also assert `eigvals() == np.diag(qml.matrix(op))`, so
branch-cut flips are caught on any Python version rather than only
against a hardcoded literal.
**Benefits:**
`qml.eigvals` was silently returning wrong values for fractional powers
of operators with negative eigenvalues, affecting anything that pairs
eigvals with diagonalizing gates. The NaN fix from #9802 (#9632) still
holds.
**Possible Drawbacks:**
`eigvals()` now returns a tensor rather than a `list`. This matches the
documented `Returns: tensor_like` contract, and nothing depends on the
list form.
**Related GitHub Issues:**
Regression from #9802.
---
Verified on 3.14 and 3.13: 182 passed across `test_pow_op.py` +
`test_pow2.py` (9 failed on 3.14 before). Wider `tests/ops/op_math/` +
`test_eigvals.py` run on 3.14 shows no new failures.
Follow-up: eight other live uses of the `(1 + 0j) *` promotion idiom
(`channel.py`, `qchem_ops.py`, `parametric_ops_single_qubit.py`,
`controlled_ops.py`) are benign today, but 3.14 changes signed-zero
behaviour there too.
[sc-130383]
---------
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>1 parent e159bd3 commit 8419760
5 files changed
Lines changed: 11 additions & 2 deletions
File tree
- doc/releases
- pennylane/ops/op_math
- tests/ops/op_math
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1470 | 1470 | | |
1471 | 1471 | | |
1472 | 1472 | | |
| 1473 | + | |
1473 | 1474 | | |
1474 | 1475 | | |
1475 | 1476 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
337 | 337 | | |
338 | 338 | | |
339 | 339 | | |
340 | | - | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
341 | 343 | | |
342 | 344 | | |
343 | 345 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
243 | 243 | | |
244 | 244 | | |
245 | 245 | | |
246 | | - | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
247 | 249 | | |
248 | 250 | | |
249 | 251 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
356 | 356 | | |
357 | 357 | | |
358 | 358 | | |
| 359 | + | |
| 360 | + | |
359 | 361 | | |
360 | 362 | | |
361 | 363 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
446 | 446 | | |
447 | 447 | | |
448 | 448 | | |
| 449 | + | |
| 450 | + | |
449 | 451 | | |
450 | 452 | | |
451 | 453 | | |
| |||
0 commit comments