Repository: PennyLaneAI/pennylane

Issue:
[BUG] `qml.ctrl` does not modify `parameter_frequencies` correctly
### Expected behavior

`qml.ctrl(op)` can be differentiated with the parameter-shift rule if `op` can be differentiated with the parameter-shift rule.
The differentiation yields the correct derivative.

### Actual behavior

The differentiation of `qml.ctrl(op)` will be wrong whenever the generator of the original op is a `SparseHamiltonian` and has more than 1 eigenvalue.


### Additional information

This is because `qml.eigvals` is used to compute the `parameter_frequencies` of `qml.ctrl(op)` and `qml.eigvals` has a kwarg `k`, which defaults to 1 and determines the number of eigenvalues that are being computed for `SparseHamiltonian` inputs.

### Source code

```shell
>>> @qml.qnode(qml.device("default.qubit"))
>>> def circuit(x):
...     qml.Hadamard(0)
...     qml.Hadamard(1)
...     qml.Hadamard(2)
...     qml.ctrl(qml.DoubleExcitationMinus(x, [1, 2, 3, 4]), 0)
...     return qml.expval(qml.dot([1, 1], [qml.PauliY(0), qml.PauliZ(3)]))

>>> x = pnp.array(0.7, requires_grad=True)
>>> circuit(x)
>>> print(qml.grad(circuit)(x))
>>> print(qml.gradients.param_shift(circuit)(x))

-0.43279197822247817
-0.3522647673177669
```


### Tracebacks

_No response_

### System information

```shell
pl dev
```


### Existing GitHub issues

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


Discussion:

Resolving patch:
diff --git a/pennylane/operation.py b/pennylane/operation.py
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -1760,7 +1760,7 @@ def parameter_frequencies(self):
                 warnings.filterwarnings(
                     action="ignore", message=r".+ eigenvalues will be computed numerically\."
                 )
-                eigvals = qml.eigvals(gen)
+                eigvals = qml.eigvals(gen, k=2**self.num_wires)
 
             eigvals = tuple(np.round(eigvals, 8))
             return [qml.gradients.eigvals_to_frequencies(eigvals)]
diff --git a/pennylane/ops/op_math/controlled.py b/pennylane/ops/op_math/controlled.py
--- a/pennylane/ops/op_math/controlled.py
+++ b/pennylane/ops/op_math/controlled.py
@@ -696,7 +696,7 @@ def parameter_frequencies(self):
                 warnings.filterwarnings(
                     action="ignore", message=r".+ eigenvalues will be computed numerically\."
                 )
-                base_gen_eigvals = qml.eigvals(base_gen)
+                base_gen_eigvals = qml.eigvals(base_gen, k=2**self.base.num_wires)
 
             # The projectors in the full generator add a eigenvalue of `0` to
             # the eigenvalues of the base generator.


Test patch:
diff --git a/tests/ops/op_math/test_controlled.py b/tests/ops/op_math/test_controlled.py
--- a/tests/ops/op_math/test_controlled.py
+++ b/tests/ops/op_math/test_controlled.py
@@ -544,12 +544,13 @@ class DummyOp(Operation):
             (qml.RX(1.23, wires=0), [(0.5, 1.0)]),
             (qml.PhaseShift(-2.4, wires=0), [(1,)]),
             (qml.IsingZZ(-9.87, (0, 1)), [(0.5, 1.0)]),
+            (qml.DoubleExcitationMinus(0.7, [0, 1, 2, 3]), [(0.5, 1.0)]),
         ],
     )
     def test_parameter_frequencies(self, base, expected):
         """Test parameter-frequencies against expected values."""
 
-        op = Controlled(base, (3, 4))
+        op = Controlled(base, (4, 5))
         assert op.parameter_frequencies == expected
 
     def test_parameter_frequencies_no_generator_error(self):
diff --git a/tests/test_operation.py b/tests/test_operation.py
--- a/tests/test_operation.py
+++ b/tests/test_operation.py
@@ -836,6 +836,16 @@ def parameter_frequencies(self):
         for i in range(num_param):
             assert f[i] == (0.2, x[i])
 
+    def test_frequencies_sparse_generator(self):
+        """Test that the parameter frequencies are correctly deduced from a generator
+        that is a ``SparseHamiltonian``."""
+        DummyOp = copy.copy(qml.DoubleExcitationPlus)
+        DummyOp.parameter_frequencies = qml.operation.Operation.parameter_frequencies
+
+        op = DummyOp(0.7, [0, 1, 2, 3])
+        assert isinstance(op.generator(), qml.SparseHamiltonian)
+        assert op.parameter_frequencies == [(1.0,)]
+
     def test_no_wires_passed(self):
         """Test exception raised if no wires are passed"""