Repository: PennyLaneAI/pennylane

Issue:
[BUG] `qml.Adjoint` and `qml.Pow` do not commute when exponent is fractional
### Expected behavior

Taking the `qml.adjoint()` of a powered-operator, i.e. an operator resulting from `qml.pow`, returns the same result whether we use `lazy=True` or `lazy=False` in `qml.adjoint()`.

### Actual behavior

Setting the flag `lazy=False` when using a fractional power (`z`) in `qml.pow` produces a matrix with the imaginary component having the opposite sign as with `lazy=True`. The correct matrix is the one with `lazy=True`

### Additional information

_No response_

### Source code

```shell
import pennylane as qml 

power = 0.5
op = qml.Hadamard(0)
powered_op = qml.pow(op, power, lazy=True)

powered_op_adj_true = qml.adjoint(powered_op, lazy=True)
matrix_true = qml.matrix(powered_op_adj_true)

powered_op_adj_false = qml.adjoint(powered_op, lazy=False)
matrix_false = qml.matrix(powered_op_adj_false)

np.allclose(matrix_true, matrix_false)
>> False
```


### Tracebacks

_No response_

### System information

```shell
Name: PennyLane
Version: 0.36.0
Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /Users/emiliano.godinez/Documents/software_development/pennylane-iqm/venv/lib/python3.9/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: pennylane_iqm, PennyLane_Lightning

Platform info:           macOS-14.5-arm64-arm-64bit
Python version:          3.9.19
Numpy version:           1.26.4
Scipy version:           1.13.1
Installed devices:
- iqm.remote (pennylane-iqm-0.0.1)
- default.clifford (PennyLane-0.36.0)
- default.gaussian (PennyLane-0.36.0)
- default.mixed (PennyLane-0.36.0)
- default.qubit (PennyLane-0.36.0)
- default.qubit.autograd (PennyLane-0.36.0)
- default.qubit.jax (PennyLane-0.36.0)
- default.qubit.legacy (PennyLane-0.36.0)
- default.qubit.tf (PennyLane-0.36.0)
- default.qubit.torch (PennyLane-0.36.0)
- default.qutrit (PennyLane-0.36.0)
- default.qutrit.mixed (PennyLane-0.36.0)
- null.qubit (PennyLane-0.36.0)
- lightning.qubit (PennyLane-Lightning-0.36.0)
```


### Existing GitHub issues

- [X] I have searched existing GitHub issues to make sure the issue does not already exist.


Discussion:
I'm not sure we have a well-defined behaviour for non-integer powers, because the power function has branch cuts in this case.

Resolving patch:
diff --git a/pennylane/ops/identity.py b/pennylane/ops/identity.py
--- 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
--- 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:


Test patch:
diff --git a/tests/ops/op_math/test_pow_op.py b/tests/ops/op_math/test_pow_op.py
--- 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()