Repository: PennyLaneAI/pennylane (python)

Issue:
[BUG] `qml.pow()` is not working properly with the interfaces
### Expected behavior

I would expect this code work

```python

import tensorflow as tf

dev = qml.device("default.qubit")
@qml.qnode(dev)
def circuit(theta):
    qml.pow(qml.RX(theta, wires = 0), z=3)
    return qml.expval(qml.PauliZ(0))


phi = tf.Variable(1.0)
circuit(phi)

```

If we change and set `z = 3.0`, the code will work.
However, in `torch` the behavior is the opposite. This code will not work:

```python
import torch

dev = qml.device("default.qubit")
@qml.qnode(dev)
def circuit(theta):
    qml.pow(qml.RX(theta, wires = 0), z=3.)
    qml.ctrl(qml.pow(qml.RX(theta, wires = 0), z=3), control = 3)
    return qml.expval(qml.PauliZ(0))

phi = torch.tensor(1.0, requires_grad = True)
circuit(phi)
```



### Actual behavior

```
KeyError                                  Traceback (most recent call last)
[/usr/local/lib/python3.10/dist-packages/autoray/autoray.py](https://8x3darvacxn-496ff2e9c6d22116-0-colab.googleusercontent.com/outputframe.html?vrz=colab_20231108-060210_RC00_580467830#) in get_lib_fn(backend, fn)
    472     try:
--> 473         lib_fn = _FUNCS[backend, fn]
    474     except KeyError:

KeyError: ('tensorflow', 'linalg.matrix_power')
```

### Additional information

Christina has identified that if you put `lazy = False` the code works. Also it seems to be related with the type of `z`
I think Jax is working like Torch

### System information

```shell
Name: PennyLane
Version: 0.34.0.dev0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/PennyLaneAI/pennylane
Author: 
Author-email: 
License: Apache License 2.0
Location: /usr/local/lib/python3.10/dist-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Lightning

Platform info:           Linux-5.15.120+-x86_64-with-glibc2.35
Python version:          3.10.12
Numpy version:           1.23.5
Scipy version:           1.11.3
Installed devices:
- lightning.qubit (PennyLane-Lightning-0.33.1)
- default.gaussian (PennyLane-0.34.0.dev0)
- default.mixed (PennyLane-0.34.0.dev0)
- default.qubit (PennyLane-0.34.0.dev0)
- default.qubit.autograd (PennyLane-0.34.0.dev0)
- default.qubit.jax (PennyLane-0.34.0.dev0)
- default.qubit.legacy (PennyLane-0.34.0.dev0)
- default.qubit.tf (PennyLane-0.34.0.dev0)
- default.qubit.torch (PennyLane-0.34.0.dev0)
- default.qutrit (PennyLane-0.34.0.dev0)
- null.qubit (PennyLane-0.34.0.dev0)
```


### Existing GitHub issues

- [X] I have searched existing GitHub issues to make sure the issue does not already exist.

Pull request description:
fallback to scipy when linalg.matrix_power isn't available
**Context:**
Tensorflow does not have a `matrix_power` function. For non-int powers, we always fall back to scipy, and we noticed that this works with Tensorflow as well! We can use this...

**Description of the Change:**
- Whenever trying to raise TF matrices to an integer matrix-power, use `scipy.linalg.fractional_matrix_power`. Until today, we just raised an error. `Pow` is not trainable regardless, so there's no harm in adding this.
- This also indirectly allowed for raising _any_ `QubitUnitary` to a non-int power (eg: `qml.QubitUnitary(np.eye(2), wires=[0]).pow(3.5)`)

**Benefits:**
Less errors! more features!

**Possible Drawbacks:**
- Still not differentiable
- `QubitUnitary.pow` looks more complex to support more stuff

**Related GitHub Issues:**
Fixes #4825 [sc-49952]

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index e1f25e613..39f56814d 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -128,6 +128,10 @@
   with QNodes.
   [(#4831)](https://github.com/PennyLaneAI/pennylane/pull/4831)
 
+* `qml.pow(op)` and `qml.QubitUnitary.pow()` now also work with Tensorflow data raised to an
+  integer power.
+  [(#4827)](https://github.com/PennyLaneAI/pennylane/pull/4827)
+
 <h3>Contributors ✍️</h3>
 
 This release contains contributions from (in alphabetical order):
diff --git a/pennylane/ops/op_math/pow.py b/pennylane/ops/op_math/pow.py
index 0d8654636..1efcde675 100644
--- a/pennylane/ops/op_math/pow.py
+++ b/pennylane/ops/op_math/pow.py
@@ -259,7 +259,7 @@ class Pow(ScalarSymbolicOp):
 
     @staticmethod
     def _matrix(scalar, mat):
-        if isinstance(scalar, int):
+        if isinstance(scalar, int) and qml.math.get_deep_interface(mat) != "tensorflow":
             return qmlmath.linalg.matrix_power(mat, scalar)
         return fractional_matrix_power(mat, scalar)
 
diff --git a/pennylane/ops/qubit/matrix_ops.py b/pennylane/ops/qubit/matrix_ops.py
index eca006e05..a88905925 100644
--- a/pennylane/ops/qubit/matrix_ops.py
+++ b/pennylane/ops/qubit/matrix_ops.py
@@ -20,6 +20,7 @@ import warnings
 from itertools import product
 
 import numpy as np
+from scipy.linalg import fractional_matrix_power
 from pennylane.math import norm, cast, eye, zeros, transpose, conj, sqrt, sqrt_matrix
 from pennylane import numpy as pnp
 
@@ -232,9 +233,14 @@ class QubitUnitary(Operation):
         return QubitUnitary(qml.math.moveaxis(qml.math.conj(U), -2, -1), wires=self.wires)
 
     def pow(self, z):
-        if isinstance(z, int):
-            return [QubitUnitary(qml.math.linalg.matrix_power(self.matrix(), z), wires=self.wires)]
-        return super().pow(z)
+        mat = self.matrix()
+        if isinstance(z, int) and qml.math.get_deep_interface(mat) != "tensorflow":
+            pow_mat = qml.math.linalg.matrix_power(mat, z)
+        elif self.batch_size is not None or qml.math.shape(z) != ():
+            return super().pow(z)
+        else:
+            pow_mat = qml.math.convert_like(fractional_matrix_power(mat, z), mat)
+        return [QubitUnitary(pow_mat, wires=self.wires)]
 
     def _controlled(self, wire):
         return qml.ControlledQubitUnitary(*self.parameters, control_wires=wire, wires=self.wires)


Test patch:
diff --git a/tests/ops/op_math/test_controlled_ops.py b/tests/ops/op_math/test_controlled_ops.py
index 6dafd4ba8..e777b6885 100644
--- a/tests/ops/op_math/test_controlled_ops.py
+++ b/tests/ops/op_math/test_controlled_ops.py
@@ -19,6 +19,7 @@ import copy
 
 import numpy as np
 import pytest
+from scipy.linalg import fractional_matrix_power
 from scipy.sparse import csr_matrix
 from scipy.stats import unitary_group
 
@@ -393,7 +394,7 @@ class TestControlledQubitUnitary:
         assert qml.math.allclose(pow_ops[0].data[0], op_mat_to_pow)
 
     def test_noninteger_pow(self):
-        """Test that a ControlledQubitUnitary raised to a non-integer power raises an error."""
+        """Test that a ControlledQubitUnitary raised to a non-integer power evalutes."""
         U1 = np.array(
             [
                 [0.73708696 + 0.61324932j, 0.27034258 + 0.08685028j],
@@ -403,8 +404,11 @@ class TestControlledQubitUnitary:
 
         op = qml.ControlledQubitUnitary(U1, control_wires=("b", "c"), wires="a")
 
-        with pytest.raises(qml.operation.PowUndefinedError):
-            op.pow(0.12)
+        z = 0.12
+        [pow_op] = op.pow(z)
+        expected = np.eye(8, dtype=complex)
+        expected[-2:, -2:] = fractional_matrix_power(U1, z)
+        assert qml.math.allequal(pow_op.matrix(), expected)
 
     def test_noninteger_pow_broadcasted(self):
         """Test that a ControlledQubitUnitary raised to a non-integer power raises an error."""
diff --git a/tests/ops/op_math/test_pow_op.py b/tests/ops/op_math/test_pow_op.py
index af7f45508..7ed54391d 100644
--- a/tests/ops/op_math/test_pow_op.py
+++ b/tests/ops/op_math/test_pow_op.py
@@ -706,9 +706,9 @@ class TestMatrix:
         base = qml.IsingZZ(param, wires=(0, 1))
         op = Pow(base, z)
 
-        mat = qml.matrix(op)
-        shortcut = base.pow(z)[0]
-        shortcut_mat = qml.matrix(shortcut)
+        mat = op.matrix()
+        [shortcut] = base.pow(z)
+        shortcut_mat = shortcut.matrix()
 
         return qml.math.allclose(mat, shortcut_mat)
 
@@ -751,6 +751,15 @@ class TestMatrix:
         param = tf.Variable(2.34)
         assert self.check_matrix(param, z)
 
+    @pytest.mark.tf
+    def test_matrix_tf_int_z(self):
+        """Test that matrix works with integer power."""
+        import tensorflow as tf
+
+        theta = tf.Variable(1.0)
+        mat = qml.pow(qml.RX(theta, wires=0), z=3).matrix()
+        assert qml.math.allclose(mat, qml.RX.compute_matrix(3))
+
     def test_matrix_wire_order(self):
         """Test that the wire_order keyword rearranges ording."""
 
@@ -796,7 +805,7 @@ class TestSparseMatrix:
         sparse_mat_array = sparse_mat.toarray()
 
         assert qml.math.allclose(sparse_mat_array, H_cubed.toarray())
-        assert qml.math.allclose(sparse_mat_array, qml.matrix(op))
+        assert qml.math.allclose(sparse_mat_array, op.matrix())
 
     def test_sparse_matrix_float_exponent(self):
         """Test that even a sparse-matrix defining op raised to a float power
diff --git a/tests/ops/qubit/test_matrix_ops.py b/tests/ops/qubit/test_matrix_ops.py
index 79cc0291c..5fe6bc5f7 100644
--- a/tests/ops/qubit/test_matrix_ops.py
+++ b/tests/ops/qubit/test_matrix_ops.py
@@ -25,7 +25,7 @@ import pennylane as qml
 from pennylane import numpy as pnp
 from pennylane.operation import DecompositionUndefinedError
 from pennylane.wires import Wires
-from pennylane.ops.qubit.matrix_ops import _walsh_hadamard_transform
+from pennylane.ops.qubit.matrix_ops import _walsh_hadamard_transform, fractional_matrix_power
 
 
 class TestQubitUnitary:
@@ -38,9 +38,10 @@ class TestQubitUnitary:
         )
 
         op = qml.QubitUnitary(U, wires="a")
+        [pow_op] = op.pow(0.123)
+        expected = fractional_matrix_power(U, 0.123)
 
-        with pytest.raises(qml.operation.PowUndefinedError):
-            op.pow(0.123)
+        assert qml.math.allclose(pow_op.matrix(), expected)
 
     def test_qubit_unitary_noninteger_pow_broadcasted(self):
         """Test broadcasted QubitUnitary raised to a non-integer power raises an error."""
@@ -188,6 +189,16 @@ class TestQubitUnitary:
         with pytest.raises(ValueError, match="must be of shape"):
             qml.QubitUnitary(U, wires=range(num_wires + 1)).matrix()
 
+    @pytest.mark.tf
+    def test_qubit_unitary_int_pow_tf(self):
+        """Test that QubitUnitary.pow works with tf and int z values."""
+        import tensorflow as tf
+
+        mat = tf.Variable([[1, 0], [0, tf.exp(1j)]])
+        expected = tf.Variable([[1, 0], [0, tf.exp(3j)]])
+        [op] = qml.QubitUnitary(mat, wires=[0]).pow(3)
+        assert qml.math.allclose(op.matrix(), expected)
+
     @pytest.mark.jax
     @pytest.mark.parametrize(
         "U,num_wires", [(H, 1), (np.kron(H, H), 2), (np.tensordot([1j, -1, 1], H, axes=0), 1)]