Repository: Qiskit/qiskit

Issue:
``ScipyOptimizer`` options are overriden by default values
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: 
- **Python version**: 3.7.9
- **Operating system**: macOS

### What is the current behavior?

The `ScipyOptimizer`s allow two ways to specify settings of an optimizer:
* directly as named arguments (e.g. `L_BFGS_B(maxiter=10)`)
* via an `options` argument (e.g. `L_BFGS_B(options={"maxiter": 10})`)

However using the second method via `options`, does not actually set the options since they are overriden by the default value.

### Steps to reproduce the problem

```python
from qiskit.algorithms.optimizers import L_BFGS_B

opt = L_BFGS_B(options={"maxiter": 2})
print(opt._options)
```
prints
```
{'maxiter': 15000, 'maxfun': 1000, 'ftol': 2.220446049250313e-15, 'iprint': -1, 'eps': 1e-08}
```


### What is the expected behavior & suggested solution

I think it would be cleaner to not have the `options` in the initializer and let the user just use named arguments. We could still keep `_options` under the hood in the `SciPyOptimizer`, just not expose it.

Alternatively we could make sure all named arguments are `None` by default and then only set the value in the options if they are not none. However that is a bit cumbersome and less nice to read in the docs, so I think just removing `options` from the initializer would be cleaner.







Discussion:
I would like to contribute to this 

Great! How would you like to solve the issue? 🙂 
I think the methods mentioned in the issue itself are good paths to solve the given problem:
1. Simply remove `options` this will take away the problem.
2. Correct the `options` parameter by initialling the parameters to `None` and whatever other thing is required. - _I think this would be prefered as it provides more options to the user_
Ok, sounds good @singhmeet11 let's go with option 2 👍🏻 

Resolving patch:
diff --git a/qiskit/algorithms/optimizers/scipy_optimizer.py b/qiskit/algorithms/optimizers/scipy_optimizer.py
--- a/qiskit/algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit/algorithms/optimizers/scipy_optimizer.py
@@ -86,11 +86,23 @@ def get_support_level(self):
 
     @property
     def settings(self) -> Dict[str, Any]:
-        settings = {
-            "max_evals_grouped": self._max_evals_grouped,
-            "options": self._options,
-            **self._kwargs,
-        }
+        options = self._options.copy()
+        if hasattr(self, "_OPTIONS"):
+            # all _OPTIONS should be keys in self._options, but add a failsafe here
+            attributes = [
+                option
+                for option in self._OPTIONS  # pylint: disable=no-member
+                if option in options.keys()
+            ]
+
+            settings = {attr: options.pop(attr) for attr in attributes}
+        else:
+            settings = {}
+
+        settings["max_evals_grouped"] = self._max_evals_grouped
+        settings["options"] = options
+        settings.update(self._kwargs)
+
         # the subclasses don't need the "method" key as the class type specifies the method
         if self.__class__ == SciPyOptimizer:
             settings["method"] = self._method


Test patch:
diff --git a/test/python/algorithms/optimizers/test_optimizers.py b/test/python/algorithms/optimizers/test_optimizers.py
--- a/test/python/algorithms/optimizers/test_optimizers.py
+++ b/test/python/algorithms/optimizers/test_optimizers.py
@@ -226,6 +226,31 @@ def test_scipy(self, method, options):
         self.assertEqual(from_dict._method, method.lower())
         self.assertEqual(from_dict._options, options)
 
+    def test_independent_reconstruction(self):
+        """Test the SciPyOptimizers don't reset all settings upon creating a new instance.
+
+        COBYLA is used as representative example here."""
+
+        kwargs = {"coffee": "without sugar"}
+        options = {"tea": "with milk"}
+        optimizer = COBYLA(maxiter=1, options=options, **kwargs)
+        serialized = optimizer.settings
+        from_dict = COBYLA(**serialized)
+
+        with self.subTest(msg="test attributes"):
+            self.assertEqual(from_dict.settings["maxiter"], 1)
+
+        with self.subTest(msg="test options"):
+            # options should only contain values that are *not* already in the initializer
+            # (e.g. should not contain maxiter)
+            self.assertEqual(from_dict.settings["options"], {"tea": "with milk"})
+
+        with self.subTest(msg="test kwargs"):
+            self.assertEqual(from_dict.settings["coffee"], "without sugar")
+
+        with self.subTest(msg="option ids differ"):
+            self.assertNotEqual(id(serialized["options"]), id(from_dict.settings["options"]))
+
     def test_adam(self):
         """Test ADAM is serializable."""