Repository: PennyLaneAI/pennylane (python)

Issue:
[BUG] Incorrect control values in `ControlledQubitUnitary.pow()`
### Expected behavior

For some integer `x` and 2x2 unitary matrix `mat`, using `ControlledQubitUnitary.pow()` has the following expected behaviour:
```pycon
>>> op = qml.ControlledQubitUnitary(mat, control_wires=[0, 1], wires=2, control_values="00")
>>> pow_ops = op.pow(x)
>>> np.allclose(np.linalg.matrix_power(mat, x), pow_ops[0].data[0])
True
>>> pow_ops[0].hyperparameters["control_values"]
00
```

### Actual behavior

For some integer `x` and 2x2 unitary matrix `mat`, using `ControlledQubitUnitary.pow()` has the following **actual** behaviour:
```pycon
>>> op = qml.ControlledQubitUnitary(mat, control_wires=[0, 1], wires=2, control_values="00")
>>> pow_ops = op.pow(x)
>>> np.allclose(np.linalg.matrix_power(mat, x), pow_ops[0].data[0])
True
>>> pow_ops[0].hyperparameters["control_values"]
None
```


### Additional information

This is because the `pow` method doesn't update the `control_values` hyperparameter for the returned `ControlledQubitUnitary` list.

### Source code

_No response_

### Tracebacks

_No response_

### System information

```shell
This isn't included because the issue is related to an incorrect implementation of ControlledQubitUnitary.pow()
```


### Existing GitHub issues

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

Pull request description:
Fix `ControlledQubitUnitary.pow`
Fixes #3190 

`ControlledQubitUnitary.pow` now copies over the control values of the original operation.

While there, I added the `ControlledQubitUnitary.control_values` property, because it makes life easier.  While `Controlled` also has the `control_values` property, they differ in types, as `ControlledQubitUnitary` still uses the old string-based representation, not the iterable of booleans representation. 

We should go back and fix that up at some point.

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 2949050b8..5d9832fe5 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -140,6 +140,9 @@ keyword argument when using `GellMann`, which determines which of the 8 Gell-Man
 
 * New `null.qubit` device. The `null.qubit`performs no operations or memory allocations. 
   [(#2589)](https://github.com/PennyLaneAI/pennylane/pull/2589)
+
+* `ControlledQubitUnitary` now has a `control_values` property.
+  [(#3206)](https://github.com/PennyLaneAI/pennylane/pull/3206)
   
 <h3>Breaking changes</h3>
 
@@ -200,6 +203,9 @@ keyword argument when using `GellMann`, which determines which of the 8 Gell-Man
 
 <h3>Bug fixes</h3>
 
+* `ControlledQubitUnitary.pow` now copies over the `control_values`.
+  [(#3206)](https://github.com/PennyLaneAI/pennylane/pull/3206)
+
 * The evaluation of QNodes that return either `vn_entropy` or `mutual_info` raises an
   informative error message when using devices that define a vector of shots.
   [(#3180)](https://github.com/PennyLaneAI/pennylane/pull/3180)
diff --git a/pennylane/ops/qubit/matrix_ops.py b/pennylane/ops/qubit/matrix_ops.py
index 6ecbdb67e..fa9e66195 100644
--- a/pennylane/ops/qubit/matrix_ops.py
+++ b/pennylane/ops/qubit/matrix_ops.py
@@ -361,12 +361,19 @@ class ControlledQubitUnitary(QubitUnitary):
     def control_wires(self):
         return self.hyperparameters["control_wires"]
 
+    @property
+    def control_values(self):
+        """str.  Specifies whether or not to control on zero "0" or one "1" for each
+        control wire."""
+        return self.hyperparameters["control_values"]
+
     def pow(self, z):
         if isinstance(z, int):
             return [
                 ControlledQubitUnitary(
                     qml.math.linalg.matrix_power(self.data[0], z),
                     control_wires=self.control_wires,
+                    control_values=self.control_values,
                     wires=self.hyperparameters["u_wires"],
                 )
             ]
@@ -374,8 +381,7 @@ class ControlledQubitUnitary(QubitUnitary):
 
     def _controlled(self, wire):
         ctrl_wires = self.control_wires + wire
-        old_control_values = self.hyperparameters["control_values"]
-        values = None if old_control_values is None else f"{old_control_values}1"
+        values = None if self.control_values is None else f"{self.control_values}1"
         new_op = ControlledQubitUnitary(
             *self.parameters,
             control_wires=ctrl_wires,


Test patch:
diff --git a/tests/ops/qubit/test_matrix_ops.py b/tests/ops/qubit/test_matrix_ops.py
index 61e5ffa30..203b17469 100644
--- a/tests/ops/qubit/test_matrix_ops.py
+++ b/tests/ops/qubit/test_matrix_ops.py
@@ -817,13 +817,16 @@ class TestControlledQubitUnitary:
             ]
         )
 
-        op = qml.ControlledQubitUnitary(U1, control_wires=("b", "c"), wires="a")
+        op = qml.ControlledQubitUnitary(
+            U1, control_wires=("b", "c"), wires="a", control_values="01"
+        )
 
         pow_ops = op.pow(n)
         assert len(pow_ops) == 1
 
         assert pow_ops[0].hyperparameters["u_wires"] == op.hyperparameters["u_wires"]
         assert pow_ops[0].control_wires == op.control_wires
+        assert pow_ops[0].control_values == op.control_values
 
         op_mat_to_pow = qml.math.linalg.matrix_power(op.data[0], n)
         assert qml.math.allclose(pow_ops[0].data[0], op_mat_to_pow)