Repository: Qiskit/qiskit

Issue:
Get Learning Rate SPSA & QNSPSA
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?

For `SPSA` as well as `QNSPSA` you can set `learning_rate=None`.
When calling the `.settings()` property of the optimizers after running the optimization, it will return `learning_rate=None` again.
However, the effective `learning_rate` is actually [changed](https://github.com/Qiskit/qiskit-terra/blob/deebd5cce63601f5a6bb4aa762baedd9cb4876b3/qiskit/algorithms/optimizers/spsa.py#L501) by the `calibrate` function. Can `self.learning_rate` be updated accordingly?

  @Cryoris @des137 @mishmash



Discussion:
Yes that's an interesting information, however it's not quite straightforward to resolve because
1. the learning rate is calibrated based on the function and initial point. That means it's particular to an individual minimization, but the (QN)SPSA objects can be re-used for different optimizations -- therefore we can't really store the calibrated learning rate as it'll interfere with the following runs.
2. Even if we would return the learning rate, it would be a generator (or function that returns a generator) and probably of not so much use since you can't ask it: "what functional form and what parameters do you have?"

One possible solution would be to print this information into the logger so that if the automatically calibrated learning rate is used, it would show something like
```
SPSA calibration started...
SPSA calibration finished:
- Learning rate: eta / (n + offset) ** power with eta = .., offset = .. and power = ...
- Perturbation: ... something similar ...
```
would that resolve the problem? Or are you really interested in the actual generator without the above information?
Thank you for looking into this, @Cryoris. A (semi-intermediate) solution which can print the learning rate in the logger file sounds like a good idea. Basically, we would like to know (and, possibly store) the learning rate after the calibration step.
Do you want to store it to use it later in the code or just to read out the values? 
Just to read out the values of both the `learning_rate` and the `perturbation`.
Ok, then I'll add the logger for now 👍🏻 

Resolving patch:
diff --git a/qiskit/algorithms/optimizers/spsa.py b/qiskit/algorithms/optimizers/spsa.py
--- a/qiskit/algorithms/optimizers/spsa.py
+++ b/qiskit/algorithms/optimizers/spsa.py
@@ -315,6 +315,7 @@ def calibrate(
             tuple(generator, generator): A tuple of powerseries generators, the first one for the
                 learning rate and the second one for the perturbation.
         """
+        logger.info("SPSA: Starting calibration of learning rate and perturbation.")
         if target_magnitude is None:
             target_magnitude = 2 * np.pi / 10
 
@@ -347,6 +348,15 @@ def calibrate(
             warnings.warn(f"Calibration failed, using {target_magnitude} for `a`")
             a = target_magnitude
 
+        logger.info("Finished calibration:")
+        logger.info(
+            " -- Learning rate: a / ((A + n) ^ alpha) with a = %s, A = %s, alpha = %s",
+            a,
+            stability_constant,
+            alpha,
+        )
+        logger.info(" -- Perturbation: c / (n ^ gamma) with c = %s, gamma = %s", c, gamma)
+
         # set up the powerseries
         def learning_rate():
             return powerseries(a, alpha, stability_constant)
@@ -529,8 +539,7 @@ def minimize(
                     fun, x, max_evals_grouped=self._max_evals_grouped
                 )
 
-        logger.info("=" * 30)
-        logger.info("Starting SPSA optimization")
+        logger.info("SPSA: Starting optimization.")
         start = time()
 
         # keep track of the last few steps to return their average
@@ -611,8 +620,7 @@ def minimize(
                     logger.info("terminated optimization at {k}/{self.maxiter} iterations")
                     break
 
-        logger.info("SPSA finished in %s", time() - start)
-        logger.info("=" * 30)
+        logger.info("SPSA: Finished in %s", time() - start)
 
         if self.last_avg > 1:
             x = np.mean(last_steps, axis=0)


Test patch: