diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 17acd824fe7..6c89bfe1c45 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -323,6 +323,10 @@
Bug fixes 🐛
+* Fixes a bug where fractional powers and adjoints of operators were commuted, which is
+ not well-defined/correct in general. Adjoints of fractional powers can no longer be evaluated.
+ [(#5835)](https://github.com/PennyLaneAI/pennylane/pull/5835)
+
* `qml.qnn.TorchLayer` now works with tuple returns.
[(#5816)](https://github.com/PennyLaneAI/pennylane/pull/5816)
diff --git a/pennylane/ops/identity.py b/pennylane/ops/identity.py
index dababf87e93..bdb2d917ad8 100644
--- a/pennylane/ops/identity.py
+++ b/pennylane/ops/identity.py
@@ -195,7 +195,8 @@ def identity_op(*params):
def adjoint(self):
return I(wires=self.wires)
- def pow(self, _):
+ # pylint: disable=unused-argument
+ def pow(self, z):
return [I(wires=self.wires)]
diff --git a/pennylane/ops/op_math/pow.py b/pennylane/ops/op_math/pow.py
index 98a724b0603..a6daafb9150 100644
--- a/pennylane/ops/op_math/pow.py
+++ b/pennylane/ops/op_math/pow.py
@@ -22,6 +22,7 @@
import pennylane as qml
from pennylane import math as qmlmath
from pennylane.operation import (
+ AdjointUndefinedError,
DecompositionUndefinedError,
Observable,
Operation,
@@ -341,8 +342,17 @@ def generator(self):
def pow(self, z):
return [Pow(base=self.base, z=self.z * z)]
+ # pylint: disable=arguments-renamed, invalid-overridden-method
+ @property
+ def has_adjoint(self):
+ return isinstance(self.z, int)
+
def adjoint(self):
- return Pow(base=qml.adjoint(self.base), z=self.z)
+ if isinstance(self.z, int):
+ return Pow(base=qml.adjoint(self.base), z=self.z)
+ raise AdjointUndefinedError(
+ "The adjoint of Pow operators only is well-defined for integer powers."
+ )
def simplify(self) -> Union["Pow", Identity]:
# try using pauli_rep:
diff --git a/tests/ops/op_math/test_pow_op.py b/tests/ops/op_math/test_pow_op.py
index c1bbc07dc57..f41bf6083f6 100644
--- a/tests/ops/op_math/test_pow_op.py
+++ b/tests/ops/op_math/test_pow_op.py
@@ -20,7 +20,7 @@
import pennylane as qml
from pennylane import numpy as np
-from pennylane.operation import DecompositionUndefinedError
+from pennylane.operation import AdjointUndefinedError, DecompositionUndefinedError
from pennylane.ops.op_math.controlled import ControlledOp
from pennylane.ops.op_math.pow import Pow, PowOperation
@@ -144,7 +144,7 @@ class CustomOp(qml.operation.Operation):
assert "control_wires" in dir(op)
def test_observable(self, power_method):
- """Test that when the base is an Observable, Adjoint will also inherit from Observable."""
+ """Test that when the base is an Observable, Pow will also inherit from Observable."""
class CustomObs(qml.operation.Observable):
num_wires = 1
@@ -166,7 +166,7 @@ class CustomObs(qml.operation.Observable):
@pytest.mark.usefixtures("use_legacy_opmath")
def test_observable_legacy_opmath(self, power_method):
- """Test that when the base is an Observable, Adjoint will also inherit from Observable."""
+ """Test that when the base is an Observable, Pow will also inherit from Observable."""
class CustomObs(qml.operation.Observable):
num_wires = 1
@@ -279,6 +279,7 @@ def test_hamiltonian_base(self, power_method):
assert op.num_wires == 2
+# pylint: disable=too-many-public-methods
@pytest.mark.parametrize("power_method", [Pow, pow_using_dunder_method, qml.pow])
class TestProperties:
"""Test Pow properties."""
@@ -317,6 +318,26 @@ def test_has_matrix_false(self, power_method):
assert op.has_matrix is False
+ @pytest.mark.parametrize("z", [-2, 3, 2])
+ def test_has_adjoint_true(self, z, power_method):
+ """Test `has_adjoint` property is true for integer powers."""
+ # Note that even if the base would have `base.has_adjoint=False`, `qml.adjoint`
+ # would succeed because it would create an `Adjoint(base)` operator.
+ base = qml.PauliX(0)
+ op: Pow = power_method(base=base, z=z)
+
+ assert op.has_adjoint is True
+
+ @pytest.mark.parametrize("z", [-2.0, 1.0, 0.32])
+ def test_has_adjoint_false(self, z, power_method):
+ """Test `has_adjoint` property is false for non-integer powers."""
+ # Note that the integer power check is a type check, so that floats like 2.
+ # are not considered to be integers.
+
+ op: Pow = power_method(base=TempOperator(wires=0), z=z)
+
+ assert op.has_adjoint is False
+
@pytest.mark.parametrize("z", [1, 3])
def test_has_decomposition_true_via_int(self, power_method, z):
"""Test `has_decomposition` property is true if the power is an interger."""
@@ -465,6 +486,26 @@ def test_pauli_rep_none_if_base_pauli_rep_none(self, power_method):
op = power_method(base, z=2)
assert op.pauli_rep is None
+ @pytest.mark.parametrize("z", [-2, 3, 2])
+ def test_adjoint_integer_power(self, z, power_method):
+ """Test the `adjoint` method for integer powers."""
+ base = qml.PauliX(0)
+ op: Pow = power_method(base=base, z=z)
+ adj_op = op.adjoint()
+
+ assert isinstance(adj_op, Pow)
+ assert adj_op.z is op.z
+ assert qml.equal(adj_op.base, qml.ops.Adjoint(qml.X(0)))
+
+ @pytest.mark.parametrize("z", [-2.0, 1.0, 0.32])
+ def test_adjoint_non_integer_power_raises(self, z, power_method):
+ """Test that the `adjoint` method raises and error for non-integer powers."""
+
+ base = qml.PauliX(0)
+ op: Pow = power_method(base=base, z=z)
+ with pytest.raises(AdjointUndefinedError, match="The adjoint of Pow operators"):
+ _ = op.adjoint()
+
class TestSimplify:
"""Test Pow simplify method and depth property."""
@@ -476,7 +517,7 @@ def test_depth_property(self):
def test_simplify_nested_pow_ops(self):
"""Test the simplify method with nested pow operations."""
- pow_op = Pow(base=Pow(base=qml.adjoint(Pow(base=qml.CNOT([1, 0]), z=1.2)), z=2), z=5)
+ pow_op = Pow(base=Pow(base=qml.adjoint(Pow(base=qml.CNOT([1, 0]), z=2)), z=1.2), z=5)
final_op = qml.Identity([1, 0])
simplified_op = pow_op.simplify()