Repository: PennyLaneAI/pennylane
Pull request #2109 (2022-01-18 23:30:22)

Rotosolve warning no trainables
**Context:**
As discussed in #2100 , the warning that `RotosolveOptimizer` will only update arguments marked as trainable is made more visible in this PR. Additionally, a warning is raised if no trainable arguments are found.

**Description of the Change:**
Doc string improvement (enhance visibility of existing warning regarding training).
Introduce a warning for use cases in which the optimizer would not do anything.

**Benefits:**
UI improvement

**Possible Drawbacks:**
N/A

**Related GitHub Issues:**
#2100 

codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#2109](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (f2a99f8) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/64806dc30fbf3beaf33da5462465de9c1cf99b6c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (64806dc) will **decrease** coverage by `2.21%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@            Coverage Diff             @@
##           master    #2109      +/-   ##
==========================================
- Coverage   99.19%   96.97%   -2.22%     
==========================================
  Files         228      228              
  Lines       17532    17534       +2     
==========================================
- Hits        17391    17004     -387     
- Misses        141      530     +389     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/optimize/rotosolve.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wdGltaXplL3JvdG9zb2x2ZS5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/interfaces/batch/jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2ludGVyZmFjZXMvYmF0Y2gvamF4LnB5) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pennylane/interfaces/jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2ludGVyZmFjZXMvamF4LnB5) | `0.00% <0.00%> (-97.44%)` | :arrow_down: |
| [pennylane/devices/default\_qubit\_jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdF9qYXgucHk=) | `0.00% <0.00%> (-96.63%)` | :arrow_down: |
| [pennylane/qnn/cost.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3Fubi9jb3N0LnB5) | `42.85% <0.00%> (-57.15%)` | :arrow_down: |
| [pennylane/math/multi\_dispatch.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21hdGgvbXVsdGlfZGlzcGF0Y2gucHk=) | `76.68% <0.00%> (-23.32%)` | :arrow_down: |
| [pennylane/transforms/classical\_jacobian.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvY2xhc3NpY2FsX2phY29iaWFuLnB5) | `77.08% <0.00%> (-22.92%)` | :arrow_down: |
| [pennylane/math/utils.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21hdGgvdXRpbHMucHk=) | `84.41% <0.00%> (-15.59%)` | :arrow_down: |
| [pennylane/math/is\_independent.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL21hdGgvaXNfaW5kZXBlbmRlbnQucHk=) | `88.50% <0.00%> (-11.50%)` | :arrow_down: |
| [pennylane/collections/sum.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2NvbGxlY3Rpb25zL3N1bS5weQ==) | `89.47% <0.00%> (-10.53%)` | :arrow_down: |
| ... and [13 more](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [64806dc...f2a99f8](https://codecov.io/gh/PennyLaneAI/pennylane/pull/2109?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index f9054eeb5..097ac57e4 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -249,6 +249,10 @@
 
 <h3>Improvements</h3>
 
+* The `RotosolveOptimizer` now raises an error if no trainable arguments are
+  detected, instead of silently skipping update steps for all arguments.
+  [(#2109)](https://github.com/PennyLaneAI/pennylane/pull/2109)
+
 * The function `qml.math.safe_squeeze` is introduced and `gradient_transform` allows
   for QNode argument axes of size `1`.
   [(#2080)](https://github.com/PennyLaneAI/pennylane/pull/2080)
diff --git a/pennylane/optimize/rotosolve.py b/pennylane/optimize/rotosolve.py
index 44f74f74b..d6a2522b6 100644
--- a/pennylane/optimize/rotosolve.py
+++ b/pennylane/optimize/rotosolve.py
@@ -50,6 +50,11 @@ def _validate_inputs(requires_grad, args, nums_frequency, spectra):
     """Checks that for each trainable argument either the number of
     frequencies or the frequency spectrum is given."""
 
+    if not any(requires_grad.values()):
+        raise ValueError(
+            "Found no parameters to optimize. The parameters to optimize "
+            "have to be marked as trainable."
+        )
     for arg, (arg_name, _requires_grad) in zip(args, requires_grad.items()):
         if _requires_grad:
             _nums_frequency = nums_frequency.get(arg_name, {})
@@ -169,6 +174,13 @@ class RotosolveOptimizer:
     and the reconstruction method used for more general operations is described in
     `Wierichs et al. (2021) <https://arxiv.org/abs/2107.12390>`_.
 
+    .. warning::
+
+        ``RotosolveOptimizer`` will only update parameters that are *explicitly*
+        marked as trainable. This can be done via ``requires_grad`` if using Autograd
+        or PyTorch. ``RotosolveOptimizer`` is not yet implemented to work in a stable
+        manner with TensorFlow or JAX.
+
     **Example:**
 
     Initialize the optimizer and set the number of steps to optimize over.
@@ -393,12 +405,6 @@ class RotosolveOptimizer:
             For each univariate reconstruction, the data in ``nums_frequency`` takes
             precedence over the information in ``spectra``.
 
-        .. warning::
-
-            ``RotosolveOptimizer`` will only update parameters that are *explicitly*
-            markes as trainable. Either via ``requires_grad`` if using Autograd or PyTorch,
-            or by using `tf.Variable` tensors inside a `GradientTape` if using TensorFlow.
-
         """
         # todo: does this signature call cover all cases?
         sign_fn = objective_fn.func if isinstance(objective_fn, qml.QNode) else objective_fn
@@ -558,11 +564,6 @@ class RotosolveOptimizer:
             For each univariate reconstruction, the data in ``nums_frequency`` takes
             precedence over the information in ``spectra``.
 
-        .. warning::
-
-            ``RotosolveOptimizer`` will only update parameters that are *explicitly*
-            markes as trainable. Either via ``requires_grad`` if using Autograd or PyTorch,
-            or by using `tf.Variable` tensors inside a `GradientTape` if using TensorFlow.
         """
         x_new, _, *y_output = self.step_and_cost(
             objective_fn,


Test patch:
diff --git a/tests/optimize/test_rotosolve.py b/tests/optimize/test_rotosolve.py
index 1f10e5764..32fe4957e 100644
--- a/tests/optimize/test_rotosolve.py
+++ b/tests/optimize/test_rotosolve.py
@@ -93,6 +93,19 @@ def test_error_missing_frequency_info_single_par():
         opt.step(fun, x, nums_frequency=nums_frequency, spectra=spectra)
 
 
+def test_error_no_trainable_args():
+    """Test that an error is raised if none of the arguments is trainable."""
+
+    opt = RotosolveOptimizer()
+    fun = lambda x, y, z: 1.0
+    x = np.arange(4, requires_grad=False)
+    y = np.arange(2, requires_grad=False)
+    z = [1.2, -0.4, -9.1]
+
+    with pytest.raises(ValueError, match="Found no parameters to optimize."):
+        opt.step(fun, x, nums_frequency=None, spectra=None)
+
+
 classical_functions = [
     lambda x: np.sin(x + 0.124) * 2.5123,
     lambda x: -np.cos(x[0] + 0.12) * 0.872 + np.sin(x[1] - 2.01) - np.cos(x[2] - 1.35) * 0.111,
@@ -190,9 +203,12 @@ class TestWithClassicalFunction:
 
         opt = RotosolveOptimizer(substep_optimizer, substep_kwargs)
 
-        # Parameters are not marked as trainable -> Expect only one execution for f0
+        # Make only the first argument trainable
+        param = (np.array(param[0], requires_grad=True),) + param[1:]
+        # Only one argument is marked as trainable -> Expect only the executions for that arg
         new_param = opt.step(_fun, *param, nums_frequency=nums_freq)
-        assert num_calls == 1
+        exp_num_calls_single_trainable = sum(2 * num + 1 for num in nums_freq["x"].values())
+        assert num_calls == exp_num_calls_single_trainable
         num_calls = 0
 
         # Parameters are now marked as trainable -> Expect full number of executions
@@ -207,7 +223,9 @@ class TestWithClassicalFunction:
         Includes testing of the parameter output shape and the old cost when using step_and_cost."""
         opt = RotosolveOptimizer(substep_optimizer, substep_kwargs)
 
-        # Without trainable parameters, param should not be changed
+        # Make only the first argument trainable
+        param = (np.array(param[0], requires_grad=True),) + param[1:]
+        # Only one argument is marked as trainable -> All other arguments have to stay fixed
         new_param_step = opt.step(
             fun,
             *param,
@@ -217,11 +235,7 @@ class TestWithClassicalFunction:
         if len(param) == 1:
             new_param_step = (new_param_step,)
 
-        assert np.allclose(
-            np.fromiter(_flatten(param), dtype=float),
-            np.fromiter(_flatten(new_param_step), dtype=float),
-            atol=1e-5,
-        )
+        assert all(np.allclose(p, new_p) for p, new_p in zip(param[1:], new_param_step[1:]))
 
         # With trainable parameters, training should happen
         param = tuple(np.array(p, requires_grad=True) for p in param)
@@ -320,17 +334,14 @@ def test_multiple_steps(fun, x_min, param, num_freq):
 
 
 classical_functions_deact = [
-    lambda x: np.sin(x + 0.124) * 2.5123,
     lambda x, y: -np.cos(x + 0.12) * 0.872 + np.sin(y[0] - 2.01) - np.cos(y[1] - 1.35) * 0.111,
     lambda x, y, z: -np.cos(x + 0.12) * 0.872 + np.sin(y - 2.01) - np.cos(z - 1.35) * 0.111,
 ]
 classical_minima_deact = [
-    (0.24,),
     (-0.12, [0.8, 0.1]),
     (-0.12, 0.2, 1.35),
 ]
 classical_params_deact = [
-    (np.array(0.24, requires_grad=False),),
     (np.array(0.3, requires_grad=True), np.array([0.8, 0.1], requires_grad=False)),
     (
         np.array(0.1, requires_grad=True),
@@ -339,7 +350,6 @@ classical_params_deact = [
     ),
 ]
 classical_nums_frequency_deact = [
-    {"x": {(): 1}},
     {"x": {(): 1}, "y": {(0,): 1, (1,): 1}},
     {"x": {(): 1}, "y": {(): 1}, "z": {(): 1}},
 ]