From bc4c98cd53e824c09cb0c9a726c69a867d31e029 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 13 Feb 2024 13:49:33 +0000
Subject: [PATCH 01/39] Avoid multiply defined bounds options in optimizer

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 230822cf..b016f135 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -145,6 +145,15 @@ def minimize(
             swapped_deprecated_args = True
             self._options["maxfun"] = self._options.pop("maxiter")
 
+        # Avoid clashing bounds defined in kwargs or _options
+        if 'bounds' in self._kwargs and not self.is_bounds_ignored:
+            bounds = self._kwargs['bounds']
+            del self._kwargs['bounds']
+
+        if 'bounds' in self._options and not self.is_bounds_ignored:
+            bounds = self._options['bounds']
+            del self._options['bounds']
+
         raw_result = minimize(
             fun=fun,
             x0=x0,

From 85e16b6f15d1f7100b275399335edbe03589f800 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 13 Feb 2024 15:30:47 +0000
Subject: [PATCH 02/39] Update change date

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index b016f135..199a9ba5 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -1,6 +1,6 @@
 # This code is part of a Qiskit project.
 #
-# (C) Copyright IBM 2018, 2023.
+# (C) Copyright IBM 2018, 2024.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory

From 6604837648a8fac07bbb32d9d69fee017a23e1ca Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Wed, 14 Feb 2024 15:27:48 +0100
Subject: [PATCH 03/39] Add warnings for parsing unsupported bounds

---
 .../optimizers/scipy_optimizer.py             | 30 ++++++++++++-------
 1 file changed, 19 insertions(+), 11 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 199a9ba5..85446d0a 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -19,6 +19,7 @@
 import numpy as np
 from scipy.optimize import minimize
 
+from qiskit_algorithms.exceptions import QiskitAlgorithmsOptimizersWarning
 from qiskit_algorithms.utils.validation import validate_min
 from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT
 
@@ -116,9 +117,25 @@ def minimize(
         jac: Callable[[POINT], POINT] | None = None,
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
-        # Remove ignored parameters to suppress the warning of scipy.optimize.minimize
-        if self.is_bounds_ignored:
+        # Loop up for bounds specified in options or kwargs
+        if 'bounds' in self._kwargs:
+            bounds = self._kwargs['bounds']
+            del self._kwargs['bounds']
+
+        if 'bounds' in self._options:
+            bounds = self._options['bounds']
+            del self._options['bounds']
+
+        # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
+        if self.is_bounds_ignored and bounds is not None:
+            warnings.warn(
+                (f'Optimizer method {self._method:s} does not support bounds. '
+                 f'Got bounds={bounds}, setting bounds=None.'),
+                QiskitAlgorithmsOptimizersWarning
+            )
             bounds = None
+            
+        # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
             jac = None
 
@@ -145,15 +162,6 @@ def minimize(
             swapped_deprecated_args = True
             self._options["maxfun"] = self._options.pop("maxiter")
 
-        # Avoid clashing bounds defined in kwargs or _options
-        if 'bounds' in self._kwargs and not self.is_bounds_ignored:
-            bounds = self._kwargs['bounds']
-            del self._kwargs['bounds']
-
-        if 'bounds' in self._options and not self.is_bounds_ignored:
-            bounds = self._options['bounds']
-            del self._options['bounds']
-
         raw_result = minimize(
             fun=fun,
             x0=x0,

From 77abbcbbeedba111cfde75df0cfea2940c9790dc Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Wed, 14 Feb 2024 15:28:46 +0100
Subject: [PATCH 04/39] Add algorithms  and optimizer specific warnings

---
 qiskit_algorithms/exceptions.py | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/qiskit_algorithms/exceptions.py b/qiskit_algorithms/exceptions.py
index 7eda309e..4b7d4b6b 100644
--- a/qiskit_algorithms/exceptions.py
+++ b/qiskit_algorithms/exceptions.py
@@ -1,6 +1,6 @@
 # This code is part of a Qiskit project.
 #
-# (C) Copyright IBM 2017, 2023.
+# (C) Copyright IBM 2017, 2024.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -10,12 +10,30 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
-"""Exception for errors raised by Algorithms module."""
+"""Exception and warnings for errors raised by Algorithms module."""
 
 from qiskit.exceptions import QiskitError
+import warnings
 
 
 class AlgorithmError(QiskitError):
     """For Algorithm specific errors."""
 
     pass
+
+class QiskitAlgorithmsWarning(UserWarning):
+    """Base class for warnings raised by Qiskit Algorithms."""
+
+    def __init__(self, *message):
+        """Set the error message."""
+        super().__init__(" ".join(message))
+        self.message = " ".join(message)
+
+    def __str__(self):
+        """Return the message."""
+        return repr(self.message)
+    
+class QiskitAlgorithmsOptimizersWarning(QiskitAlgorithmsWarning):
+    """For Algorithm specific warnings."""
+    
+    pass

From 0163c53d986dbcd801f67f8e69ac3be774504eb4 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Wed, 14 Feb 2024 18:11:57 +0100
Subject: [PATCH 05/39] Fix missed import

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 85446d0a..61f38d2d 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -15,6 +15,7 @@
 
 from collections.abc import Callable
 from typing import Any
+import warnings
 
 import numpy as np
 from scipy.optimize import minimize
@@ -129,8 +130,7 @@ def minimize(
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
         if self.is_bounds_ignored and bounds is not None:
             warnings.warn(
-                (f'Optimizer method {self._method:s} does not support bounds. '
-                 f'Got bounds={bounds}, setting bounds=None.'),
+                f'Optimizer method {self._method} does not support bounds.',
                 QiskitAlgorithmsOptimizersWarning
             )
             bounds = None

From 343ae7c56f9ca12c74d75b8476d41fd541d417cb Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Thu, 15 Feb 2024 10:23:09 +0100
Subject: [PATCH 06/39] Apply style patches

---
 qiskit_algorithms/exceptions.py                |  7 ++++---
 .../optimizers/scipy_optimizer.py              | 18 +++++++++---------
 2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/qiskit_algorithms/exceptions.py b/qiskit_algorithms/exceptions.py
index 4b7d4b6b..9d274e1a 100644
--- a/qiskit_algorithms/exceptions.py
+++ b/qiskit_algorithms/exceptions.py
@@ -13,7 +13,6 @@
 """Exception and warnings for errors raised by Algorithms module."""
 
 from qiskit.exceptions import QiskitError
-import warnings
 
 
 class AlgorithmError(QiskitError):
@@ -21,6 +20,7 @@ class AlgorithmError(QiskitError):
 
     pass
 
+
 class QiskitAlgorithmsWarning(UserWarning):
     """Base class for warnings raised by Qiskit Algorithms."""
 
@@ -32,8 +32,9 @@ def __init__(self, *message):
     def __str__(self):
         """Return the message."""
         return repr(self.message)
-    
+
+
 class QiskitAlgorithmsOptimizersWarning(QiskitAlgorithmsWarning):
     """For Algorithm specific warnings."""
-    
+
     pass
diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 61f38d2d..6b8dd9eb 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -119,22 +119,22 @@ def minimize(
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
         # Loop up for bounds specified in options or kwargs
-        if 'bounds' in self._kwargs:
-            bounds = self._kwargs['bounds']
-            del self._kwargs['bounds']
+        if "bounds" in self._kwargs:
+            bounds = self._kwargs["bounds"]
+            del self._kwargs["bounds"]
 
-        if 'bounds' in self._options:
-            bounds = self._options['bounds']
-            del self._options['bounds']
+        if "bounds" in self._options:
+            bounds = self._options["bounds"]
+            del self._options["bounds"]
 
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
         if self.is_bounds_ignored and bounds is not None:
             warnings.warn(
-                f'Optimizer method {self._method} does not support bounds.',
-                QiskitAlgorithmsOptimizersWarning
+                f"Optimizer method {self._method} does not support bounds.",
+                QiskitAlgorithmsOptimizersWarning,
             )
             bounds = None
-            
+
         # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
             jac = None

From d1db2a3c92d9438eb40319f15d5c01efdc861c10 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Mon, 19 Feb 2024 18:24:16 +0000
Subject: [PATCH 07/39] Add unit tests and introduce _bounds attribute

---
 .../optimizers/scipy_optimizer.py             |  27 ++--
 test/optimizers/test_optimizers.py            | 130 +++++++++++++++++-
 2 files changed, 146 insertions(+), 11 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 6b8dd9eb..2df993e5 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -78,6 +78,17 @@ def __init__(
         self._max_evals_grouped = max_evals_grouped
         self._kwargs = kwargs
 
+        # Initialise bounds and re-allocate if definition in options or kwargs
+        self._bounds = None
+
+        if "bounds" in self._kwargs:
+            self._bounds = self._kwargs["bounds"]
+            del self._kwargs["bounds"]
+
+        if "bounds" in self._options:
+            self._bounds = self._options["bounds"]
+            del self._options["bounds"]
+
     def get_support_level(self):
         """Return support level dictionary"""
         return {
@@ -118,22 +129,18 @@ def minimize(
         jac: Callable[[POINT], POINT] | None = None,
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
-        # Loop up for bounds specified in options or kwargs
-        if "bounds" in self._kwargs:
-            bounds = self._kwargs["bounds"]
-            del self._kwargs["bounds"]
 
-        if "bounds" in self._options:
-            bounds = self._options["bounds"]
-            del self._options["bounds"]
+        # Overwrite the previous bounds if bounds are parsed in .minimize()
+        if bounds is not None:
+            self._bounds = bounds
 
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
-        if self.is_bounds_ignored and bounds is not None:
+        if self.is_bounds_ignored and self._bounds is not None:
             warnings.warn(
                 f"Optimizer method {self._method} does not support bounds.",
                 QiskitAlgorithmsOptimizersWarning,
             )
-            bounds = None
+            self._bounds = None
 
         # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
@@ -167,7 +174,7 @@ def minimize(
             x0=x0,
             method=self._method,
             jac=jac,
-            bounds=bounds,
+            bounds=self._bounds,
             options=self._options,
             **self._kwargs,
         )
diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index 731bb90a..fe572659 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -49,6 +49,7 @@
     SciPyOptimizer,
 )
 from qiskit_algorithms.utils import algorithm_globals
+from qiskit_algorithms.exceptions import QiskitAlgorithmsOptimizersWarning
 
 
 @ddt
@@ -183,6 +184,133 @@ def callback(x):
         self.run_optimizer(optimizer, max_nfev=10000)
         self.assertTrue(values)  # Check the list is nonempty.
 
+    def test_scipy_optimizer_parse_bounds(self):
+        """
+        Test the parsing of bounds in SciPyOptimizer.
+
+        This function tests the behavior of parsing bounds in the SciPyOptimizer class.
+        It checks whether the bounds are correctly allocated and raises an assertion error
+        if unexpected TypeErrors are encountered during parsing.
+
+        Cases:
+        1. Test parsing bounds specified in the 'options' parameter of SciPyOptimizer.
+        2. Test parsing bounds specified directly in the 'bounds' parameter (as kwarg) of SciPyOptimizer.
+        3. Test parsing bounds specified directly in the 'minimize' method of SciPyOptimizer.
+
+        This function aims to ensure that bounds are correctly allocated within the object attributes
+        and not in the 'options' or 'kwargs' dictionaries, to avoid known parsing issues.
+
+        Raises:
+            AssertionError: If unexpected TypeErrors are raised during parsing of bounds.
+        """
+
+        cases = {}
+
+        try:
+            optimizer = SciPyOptimizer('SLSQP', maxiter=10, options={'bounds': [(0., 1.)]})
+
+            # Make sure the minimize method takes place
+            optimizer.minimize(lambda x: -x, 1.)
+
+        except TypeError:
+            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
+            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._options.")
+
+        cases['SciPyOptimizer._options'] = optimizer
+
+        try:
+            optimizer = SciPyOptimizer('SLSQP', maxiter=10, bounds=[(0., 1.)])
+            optimizer.minimize(lambda x: -x, 1.)
+
+        except TypeError:
+            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
+            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._kwargs.")
+
+        cases['SciPyOptimizer._kwargs'] = optimizer
+
+        try:
+            optimizer = SciPyOptimizer('SLSQP', maxiter=10)
+            optimizer.minimize(lambda x: -x, 1., bounds=[(0., 1.)])
+
+        except TypeError:
+            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
+            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...).")
+
+        cases['SciPyOptimizer.minimize'] = optimizer
+
+        # Check that the bounds are correctly allocated in the attr and not in dicts
+        # This avoids the Scipy parsing issue documented in
+        # https://github.com/qiskit-community/qiskit-machine-learning/issues/570
+        for optimizer in cases.values():
+            self.assertTrue(hasattr(optimizer, '_bounds'))
+            self.assertFalse('bounds' in optimizer._options)
+            self.assertFalse('bounds' in optimizer._kwargs)
+            self.assertFalse(optimizer._bounds is None)  # Because we set bounds to a non-empty tuple
+
+    def test_scipy_optimizer_bounds_overwrite(self):
+        """
+            Test bounds overwrite behavior of SciPyOptimizer.
+
+            This function tests the behavior of SciPyOptimizer when new bounds are supplied to it
+            after initial bounds have been set. It verifies that the new bounds overwrite the old
+            ones correctly.
+
+            Raises:
+                AssertionError: If the bounds overwrite behavior is incorrect.
+            """
+        # Initialize SciPyOptimizer with initial bounds
+        optimizer = SciPyOptimizer('SLSQP', maxiter=10, bounds=[(0., 1.)])
+
+        # Record the old bounds
+        old_bounds = np.asarray(list(optimizer._bounds[0]))
+
+        # Call the minimize method with new bounds
+        optimizer.minimize(lambda x: -x, 1., bounds=[(2., 3.)])
+
+        # Record the new bounds
+        new_bounds = np.asarray(list(optimizer._bounds[0]))
+
+        # Verify that the old bounds and new bounds are not identical
+        self.assertFalse(np.any(old_bounds == new_bounds))
+
+        # Verify that the new bounds match the expected values
+        self.assertTrue(np.all(new_bounds == np.array([2., 3.])))
+
+    def test_scipy_optimizer_warning(self):
+        """
+        Test warning behavior of SciPyOptimizer with unsupported bounds.
+
+        This function tests the warning behavior of SciPyOptimizer when using the COBYLA optimizer
+        with bounds supplied, which are unsupported by COBYLA. The function verifies that a specific
+        warning, QiskitAlgorithmsOptimizersWarning, is raised when bounds are supplied to COBYLA,
+        and the optimizer ignores them as expected.
+
+        Cases:
+        1. Test warning when bounds are supplied via 'options' parameter.
+        2. Test warning when bounds are supplied directly as kwargs.
+        3. Test warning when bounds are supplied via 'minimize' method.
+
+        Raises:
+            AssertionError: If the expected warning is not raised.
+        """
+
+        # Cobyla does not support bounds and is expected to warn if bounds (then ignored) are supplied
+        optimizer = SciPyOptimizer('cobyla', maxiter=10, options={'bounds': [(0., 1.)]})
+
+        # Using lambda function to pass a function that will raise the specific warning
+        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            optimizer.minimize(lambda x: -x, 1.)
+
+        # The same should happen with bounds parsed as kwargs
+        optimizer = SciPyOptimizer('cobyla', maxiter=10, bounds=[(0., 1.)])
+        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            optimizer.minimize(lambda x: -x, 1.)
+
+        # The same should happen with bounds parsed via minimize(...)
+        optimizer = SciPyOptimizer('cobyla', maxiter=10)
+        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            optimizer.minimize(lambda x: -x, 1., bounds=[(0., 1.)])
+
     # ESCH and ISRES do not do well with rosen
     @data(
         (CRS, True),
@@ -346,7 +474,7 @@ def test_spsa_custom_iterators(self):
         def powerlaw():
             n = 0
             while True:
-                yield rate**n
+                yield rate ** n
                 n += 1
 
         def steps():

From 83df2c1a7c7a9cd8caa0661b0659471a82e52315 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Mon, 19 Feb 2024 19:00:36 +0000
Subject: [PATCH 08/39] Formatting and final patches to tests

---
 requirements.txt                   |  1 +
 test/optimizers/test_optimizers.py | 80 ++++++++++++++++--------------
 2 files changed, 45 insertions(+), 36 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index a9a2724a..0143579c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
 qiskit>=0.44
 scipy>=1.4
 numpy>=1.17
+scikit-quant
diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index fe572659..15cbbac6 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -150,7 +150,7 @@ def test_gsls(self):
             max_eval=10000,
             min_step_size=1.0e-12,
         )
-        x_0 = [1.3, 0.7, 0.8, 1.9, 1.2]
+        x_0 = np.asarray([1.3, 0.7, 0.8, 1.9, 1.2])
 
         algorithm_globals.random_seed = 1
         res = optimizer.minimize(rosen, x_0)
@@ -204,68 +204,76 @@ def test_scipy_optimizer_parse_bounds(self):
             AssertionError: If unexpected TypeErrors are raised during parsing of bounds.
         """
 
-        cases = {}
+        optimizer_instances = []
 
         try:
-            optimizer = SciPyOptimizer('SLSQP', maxiter=10, options={'bounds': [(0., 1.)]})
+            optimizer = SciPyOptimizer("SLSQP", options={"bounds": [(0.0, 1.0)]})
 
             # Make sure the minimize method takes place
-            optimizer.minimize(lambda x: -x, 1.)
+            optimizer.minimize(lambda x: -x, 1.0)
 
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._options.")
+            self.fail(
+                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._options."
+            )
 
-        cases['SciPyOptimizer._options'] = optimizer
+        optimizer_instances.append(optimizer)
 
         try:
-            optimizer = SciPyOptimizer('SLSQP', maxiter=10, bounds=[(0., 1.)])
-            optimizer.minimize(lambda x: -x, 1.)
+            optimizer = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
+            optimizer.minimize(lambda x: -x, 1.0)
 
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._kwargs.")
+            self.fail(
+                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._kwargs."
+            )
 
-        cases['SciPyOptimizer._kwargs'] = optimizer
+        optimizer_instances.append(optimizer)
 
         try:
-            optimizer = SciPyOptimizer('SLSQP', maxiter=10)
-            optimizer.minimize(lambda x: -x, 1., bounds=[(0., 1.)])
+            optimizer = SciPyOptimizer("SLSQP")
+            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...).")
+            self.fail(
+                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...)."
+            )
 
-        cases['SciPyOptimizer.minimize'] = optimizer
+        optimizer_instances.append(optimizer)
 
         # Check that the bounds are correctly allocated in the attr and not in dicts
         # This avoids the Scipy parsing issue documented in
         # https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-        for optimizer in cases.values():
-            self.assertTrue(hasattr(optimizer, '_bounds'))
-            self.assertFalse('bounds' in optimizer._options)
-            self.assertFalse('bounds' in optimizer._kwargs)
-            self.assertFalse(optimizer._bounds is None)  # Because we set bounds to a non-empty tuple
+        for optimizer in optimizer_instances:
+            self.assertTrue(hasattr(optimizer, "_bounds"))
+            self.assertFalse("bounds" in optimizer._options)
+            self.assertFalse("bounds" in optimizer._kwargs)
+            self.assertFalse(
+                optimizer._bounds is None
+            )  # Because we set bounds to a non-empty tuple
 
     def test_scipy_optimizer_bounds_overwrite(self):
         """
-            Test bounds overwrite behavior of SciPyOptimizer.
+        Test bounds overwrite behavior of SciPyOptimizer.
 
-            This function tests the behavior of SciPyOptimizer when new bounds are supplied to it
-            after initial bounds have been set. It verifies that the new bounds overwrite the old
-            ones correctly.
+        This function tests the behavior of SciPyOptimizer when new bounds are supplied to it
+        after initial bounds have been set. It verifies that the new bounds overwrite the old
+        ones correctly.
 
-            Raises:
-                AssertionError: If the bounds overwrite behavior is incorrect.
-            """
+        Raises:
+            AssertionError: If the bounds overwrite behavior is incorrect.
+        """
         # Initialize SciPyOptimizer with initial bounds
-        optimizer = SciPyOptimizer('SLSQP', maxiter=10, bounds=[(0., 1.)])
+        optimizer = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
 
         # Record the old bounds
         old_bounds = np.asarray(list(optimizer._bounds[0]))
 
         # Call the minimize method with new bounds
-        optimizer.minimize(lambda x: -x, 1., bounds=[(2., 3.)])
+        optimizer.minimize(lambda x: -x, 1.0, bounds=[(2.0, 3.0)])
 
         # Record the new bounds
         new_bounds = np.asarray(list(optimizer._bounds[0]))
@@ -274,7 +282,7 @@ def test_scipy_optimizer_bounds_overwrite(self):
         self.assertFalse(np.any(old_bounds == new_bounds))
 
         # Verify that the new bounds match the expected values
-        self.assertTrue(np.all(new_bounds == np.array([2., 3.])))
+        self.assertTrue(np.all(new_bounds == np.array([2.0, 3.0])))
 
     def test_scipy_optimizer_warning(self):
         """
@@ -295,21 +303,21 @@ def test_scipy_optimizer_warning(self):
         """
 
         # Cobyla does not support bounds and is expected to warn if bounds (then ignored) are supplied
-        optimizer = SciPyOptimizer('cobyla', maxiter=10, options={'bounds': [(0., 1.)]})
+        optimizer = SciPyOptimizer("cobyla", options={"bounds": [(0.0, 1.0)]})
 
         # Using lambda function to pass a function that will raise the specific warning
         with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1.)
+            optimizer.minimize(lambda x: -x, 1.0)
 
         # The same should happen with bounds parsed as kwargs
-        optimizer = SciPyOptimizer('cobyla', maxiter=10, bounds=[(0., 1.)])
+        optimizer = SciPyOptimizer("cobyla", bounds=[(0.0, 1.0)])
         with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1.)
+            optimizer.minimize(lambda x: -x, 1.0)
 
         # The same should happen with bounds parsed via minimize(...)
-        optimizer = SciPyOptimizer('cobyla', maxiter=10)
+        optimizer = SciPyOptimizer("cobyla")
         with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1., bounds=[(0., 1.)])
+            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
     # ESCH and ISRES do not do well with rosen
     @data(
@@ -474,7 +482,7 @@ def test_spsa_custom_iterators(self):
         def powerlaw():
             n = 0
             while True:
-                yield rate ** n
+                yield rate**n
                 n += 1
 
         def steps():

From 28c5f6746cb334e2e6f290a88ba00c8337fafaf4 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Fri, 23 Feb 2024 16:42:58 +0000
Subject: [PATCH 09/39] Bounds parsing forbidden in __init__ and allowed in
 minimize()

---
 .../optimizers/scipy_optimizer.py             |  24 ++-
 test/optimizers/test_optimizers.py            | 148 ++++++------------
 2 files changed, 63 insertions(+), 109 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 2df993e5..215534a9 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -82,12 +82,15 @@ def __init__(
         self._bounds = None
 
         if "bounds" in self._kwargs:
-            self._bounds = self._kwargs["bounds"]
-            del self._kwargs["bounds"]
-
+            raise RuntimeError(
+                "Optimizer bounds should be parsed in SciPyOptimizer.minimize() and not in "
+                "SciPyOptimizer.__init__()."
+            )
         if "bounds" in self._options:
-            self._bounds = self._options["bounds"]
-            del self._options["bounds"]
+            raise RuntimeError(
+                "Optimizer bounds should be parsed in SciPyOptimizer.minimize() as a kwarg and not as "
+                "options."
+            )
 
     def get_support_level(self):
         """Return support level dictionary"""
@@ -130,17 +133,24 @@ def minimize(
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
 
-        # Overwrite the previous bounds if bounds are parsed in .minimize()
+        # Overwrite the previous bounds
         if bounds is not None:
             self._bounds = bounds
 
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
         if self.is_bounds_ignored and self._bounds is not None:
             warnings.warn(
-                f"Optimizer method {self._method} does not support bounds.",
+                f"Optimizer method {self._method} does not support bounds. Bounds ignored.",
                 QiskitAlgorithmsOptimizersWarning,
             )
             self._bounds = None
+        elif not self.is_bounds_ignored and self._bounds is None:
+            warnings.warn(
+                f"Optimizer method {self._method} may require defining bounds. "
+                f"Check the Scipy documentation for more info: "
+                f"https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html",
+                QiskitAlgorithmsOptimizersWarning,
+            )
 
         # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index 15cbbac6..ea9cdca1 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -186,55 +186,34 @@ def callback(x):
 
     def test_scipy_optimizer_parse_bounds(self):
         """
-        Test the parsing of bounds in SciPyOptimizer.
-
-        This function tests the behavior of parsing bounds in the SciPyOptimizer class.
-        It checks whether the bounds are correctly allocated and raises an assertion error
-        if unexpected TypeErrors are encountered during parsing.
-
-        Cases:
-        1. Test parsing bounds specified in the 'options' parameter of SciPyOptimizer.
-        2. Test parsing bounds specified directly in the 'bounds' parameter (as kwarg) of SciPyOptimizer.
-        3. Test parsing bounds specified directly in the 'minimize' method of SciPyOptimizer.
-
-        This function aims to ensure that bounds are correctly allocated within the object attributes
-        and not in the 'options' or 'kwargs' dictionaries, to avoid known parsing issues.
+        Test the parsing of bounds in SciPyOptimizer.minimize method. Verifies that the bounds are
+        correctly parsed and set within the optimizer object.
 
         Raises:
-            AssertionError: If unexpected TypeErrors are raised during parsing of bounds.
-        """
-
-        optimizer_instances = []
+            AssertionError: If any of the assertions fail.
+            AssertionError: If a TypeError is raised unexpectedly while parsing bounds.
 
+        """
         try:
-            optimizer = SciPyOptimizer("SLSQP", options={"bounds": [(0.0, 1.0)]})
-
-            # Make sure the minimize method takes place
-            optimizer.minimize(lambda x: -x, 1.0)
-
-        except TypeError:
-            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail(
-                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._options."
-            )
+            # Initialize SciPyOptimizer instance with SLSQP method
+            optimizer = SciPyOptimizer("SLSQP")
 
-        optimizer_instances.append(optimizer)
+            # Ensure that _bounds attribute is initially present and =None
+            self.assertTrue(hasattr(optimizer, "_bounds"))
+            self.assertTrue(optimizer._bounds is None)
 
-        try:
-            optimizer = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
-            optimizer.minimize(lambda x: -x, 1.0)
+            # Call minimize method with a simple lambda function and bounds
+            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
-        except TypeError:
-            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail(
-                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._kwargs."
-            )
+            # Assert that _bounds attribute is set after minimize method call
+            self.assertTrue(hasattr(optimizer, "_bounds"))
 
-        optimizer_instances.append(optimizer)
+            # Assert that "bounds" is not present in optimizer options and kwargs
+            self.assertFalse("bounds" in optimizer._options)
+            self.assertFalse("bounds" in optimizer._kwargs)
 
-        try:
-            optimizer = SciPyOptimizer("SLSQP")
-            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
+            # Assert that _bounds attribute is not None after setting bounds
+            self.assertFalse(optimizer._bounds is None)
 
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
@@ -242,83 +221,48 @@ def test_scipy_optimizer_parse_bounds(self):
                 "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...)."
             )
 
-        optimizer_instances.append(optimizer)
+        # Finally, expect exceptions if bounds are parsed incorrectly, i.e. differently than as in Scipy
+        with self.assertRaises(RuntimeError):
+            _ = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
+        with self.assertRaises(RuntimeError):
+            _ = SciPyOptimizer("SLSQP", options={"bounds": [(0.0, 1.0)]})
 
-        # Check that the bounds are correctly allocated in the attr and not in dicts
-        # This avoids the Scipy parsing issue documented in
-        # https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-        for optimizer in optimizer_instances:
-            self.assertTrue(hasattr(optimizer, "_bounds"))
-            self.assertFalse("bounds" in optimizer._options)
-            self.assertFalse("bounds" in optimizer._kwargs)
-            self.assertFalse(
-                optimizer._bounds is None
-            )  # Because we set bounds to a non-empty tuple
-
-    def test_scipy_optimizer_bounds_overwrite(self):
+    def test_scipy_optimizer_warning(self):
         """
-        Test bounds overwrite behavior of SciPyOptimizer.
-
-        This function tests the behavior of SciPyOptimizer when new bounds are supplied to it
-        after initial bounds have been set. It verifies that the new bounds overwrite the old
-        ones correctly.
+        Test warning handling in SciPyOptimizer.minimize method when using unsupported
+        optimizer. Verifies that a warning is raised when attempting to use an
+        optimizer that does not support bounds.
 
         Raises:
-            AssertionError: If the bounds overwrite behavior is incorrect.
-        """
-        # Initialize SciPyOptimizer with initial bounds
-        optimizer = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
-
-        # Record the old bounds
-        old_bounds = np.asarray(list(optimizer._bounds[0]))
-
-        # Call the minimize method with new bounds
-        optimizer.minimize(lambda x: -x, 1.0, bounds=[(2.0, 3.0)])
-
-        # Record the new bounds
-        new_bounds = np.asarray(list(optimizer._bounds[0]))
-
-        # Verify that the old bounds and new bounds are not identical
-        self.assertFalse(np.any(old_bounds == new_bounds))
-
-        # Verify that the new bounds match the expected values
-        self.assertTrue(np.all(new_bounds == np.array([2.0, 3.0])))
+            AssertionError: If the expected warning is not raised.
 
-    def test_scipy_optimizer_warning(self):
         """
-        Test warning behavior of SciPyOptimizer with unsupported bounds.
-
-        This function tests the warning behavior of SciPyOptimizer when using the COBYLA optimizer
-        with bounds supplied, which are unsupported by COBYLA. The function verifies that a specific
-        warning, QiskitAlgorithmsOptimizersWarning, is raised when bounds are supplied to COBYLA,
-        and the optimizer ignores them as expected.
+        # Initialize SciPyOptimizer instance with "cobyla" method
+        optimizer = SciPyOptimizer("cobyla")
 
-        Cases:
-        1. Test warning when bounds are supplied via 'options' parameter.
-        2. Test warning when bounds are supplied directly as kwargs.
-        3. Test warning when bounds are supplied via 'minimize' method.
+        # Use assertWarns context manager to check if the expected warning is raised
+        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            # Call minimize method with a simple lambda function and bounds for unsupported optimizer
+            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
-        Raises:
-            AssertionError: If the expected warning is not raised.
+    def test_scipy_optimizer_bounds_required(self):
         """
+        Test bounds requirement handling in SciPyOptimizer.minimize method for optimizers
+        that require bounds. Verifies that an exception is raised when attempting to use
+        an optimizer that requires bounds without supplying them.
 
-        # Cobyla does not support bounds and is expected to warn if bounds (then ignored) are supplied
-        optimizer = SciPyOptimizer("cobyla", options={"bounds": [(0.0, 1.0)]})
+        Raises:
+            AssertionError: If the expected exception is not raised.
 
-        # Using lambda function to pass a function that will raise the specific warning
-        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1.0)
+        """
+        # Initialize SciPyOptimizer instance with "SLSQP" method
+        optimizer = SciPyOptimizer("SLSQP")
 
-        # The same should happen with bounds parsed as kwargs
-        optimizer = SciPyOptimizer("cobyla", bounds=[(0.0, 1.0)])
+        # Use assertWarns context manager to check if the expected exception is raised
         with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            # Call minimize method with a simple lambda function without supplying required bounds
             optimizer.minimize(lambda x: -x, 1.0)
 
-        # The same should happen with bounds parsed via minimize(...)
-        optimizer = SciPyOptimizer("cobyla")
-        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
-
     # ESCH and ISRES do not do well with rosen
     @data(
         (CRS, True),

From 6d057ec704c3270fa79fbfd5215c339df811182d Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Sat, 24 Feb 2024 15:14:33 +0000
Subject: [PATCH 10/39] Add bounds type hinting

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 215534a9..69ff5384 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -79,7 +79,7 @@ def __init__(
         self._kwargs = kwargs
 
         # Initialise bounds and re-allocate if definition in options or kwargs
-        self._bounds = None
+        self._bounds: list[tuple[float, float]] | None = None
 
         if "bounds" in self._kwargs:
             raise RuntimeError(

From cdfc487af4e1b9b17715466c7687296cab6f2bb2 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Sat, 24 Feb 2024 15:16:21 +0000
Subject: [PATCH 11/39] Change copyright in tests

---
 test/optimizers/test_optimizers.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index ea9cdca1..8fb9151e 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -1,6 +1,6 @@
 # This code is part of a Qiskit project.
 #
-# (C) Copyright IBM 2018, 2023.
+# (C) Copyright IBM 2018, 2024.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory

From 7c0bf6bdaa238e06d96188d5579629ae933d35b5 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Sat, 24 Feb 2024 18:59:01 +0000
Subject: [PATCH 12/39] Remove scikit-quant dependency

---
 requirements.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 0143579c..a9a2724a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,3 @@
 qiskit>=0.44
 scipy>=1.4
 numpy>=1.17
-scikit-quant

From a4f45ee2638371e2ee58731ddc87d95dd70f7cd4 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Sat, 24 Feb 2024 19:01:38 +0000
Subject: [PATCH 13/39] Update qiskit_algorithms/optimizers/scipy_optimizer.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 69ff5384..d1941a6b 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -78,7 +78,7 @@ def __init__(
         self._max_evals_grouped = max_evals_grouped
         self._kwargs = kwargs
 
-        # Initialise bounds and re-allocate if definition in options or kwargs
+        # Initialize bounds and re-allocate if definition in options or kwargs
         self._bounds: list[tuple[float, float]] | None = None
 
         if "bounds" in self._kwargs:

From 18b0f77756b23cafb4c0ea5bc3a616c7f9d0b951 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 27 Feb 2024 19:33:04 +0000
Subject: [PATCH 14/39] Update qiskit_algorithms/optimizers/scipy_optimizer.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index d1941a6b..2af01040 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -83,8 +83,8 @@ def __init__(
 
         if "bounds" in self._kwargs:
             raise RuntimeError(
-                "Optimizer bounds should be parsed in SciPyOptimizer.minimize() and not in "
-                "SciPyOptimizer.__init__()."
+                "Optimizer bounds should be passed to SciPyOptimizer.minimize() and is not  "
+                "supported in SciPyOptimizer constructor kwargs."
             )
         if "bounds" in self._options:
             raise RuntimeError(

From 140fce64032bc245819722ce67288de729e6eef5 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 27 Feb 2024 19:34:32 +0000
Subject: [PATCH 15/39] Update qiskit_algorithms/optimizers/scipy_optimizer.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 2af01040..8df1ed45 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -88,7 +88,7 @@ def __init__(
             )
         if "bounds" in self._options:
             raise RuntimeError(
-                "Optimizer bounds should be parsed in SciPyOptimizer.minimize() as a kwarg and not as "
+                "Optimizer bounds should be passed to SciPyOptimizer.minimize() and is not as "
                 "options."
             )
 

From 2fbd23a391181ce158ca3c86e18d5578af5d5e94 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 27 Feb 2024 19:45:52 +0000
Subject: [PATCH 16/39] Remove internal implementation checks in
 test_optimizers.py

---
 test/optimizers/test_optimizers.py | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index 8fb9151e..989d3626 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -198,10 +198,6 @@ def test_scipy_optimizer_parse_bounds(self):
             # Initialize SciPyOptimizer instance with SLSQP method
             optimizer = SciPyOptimizer("SLSQP")
 
-            # Ensure that _bounds attribute is initially present and =None
-            self.assertTrue(hasattr(optimizer, "_bounds"))
-            self.assertTrue(optimizer._bounds is None)
-
             # Call minimize method with a simple lambda function and bounds
             optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 

From 30c4b7236500d02c6875edb29fda40af94c21a00 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 13 Feb 2024 13:49:33 +0000
Subject: [PATCH 17/39] Avoid multiply defined bounds options in optimizer

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 230822cf..b016f135 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -145,6 +145,15 @@ def minimize(
             swapped_deprecated_args = True
             self._options["maxfun"] = self._options.pop("maxiter")
 
+        # Avoid clashing bounds defined in kwargs or _options
+        if 'bounds' in self._kwargs and not self.is_bounds_ignored:
+            bounds = self._kwargs['bounds']
+            del self._kwargs['bounds']
+
+        if 'bounds' in self._options and not self.is_bounds_ignored:
+            bounds = self._options['bounds']
+            del self._options['bounds']
+
         raw_result = minimize(
             fun=fun,
             x0=x0,

From 06240b838c88d307f571ff1a0ba226f5eb180afb Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 13 Feb 2024 15:30:47 +0000
Subject: [PATCH 18/39] Update change date

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index b016f135..199a9ba5 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -1,6 +1,6 @@
 # This code is part of a Qiskit project.
 #
-# (C) Copyright IBM 2018, 2023.
+# (C) Copyright IBM 2018, 2024.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory

From be6dd75376cdb5be438e36723b4c7aaa58f338aa Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Wed, 14 Feb 2024 15:27:48 +0100
Subject: [PATCH 19/39] Add warnings for parsing unsupported bounds

---
 .../optimizers/scipy_optimizer.py             | 30 ++++++++++++-------
 1 file changed, 19 insertions(+), 11 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 199a9ba5..85446d0a 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -19,6 +19,7 @@
 import numpy as np
 from scipy.optimize import minimize
 
+from qiskit_algorithms.exceptions import QiskitAlgorithmsOptimizersWarning
 from qiskit_algorithms.utils.validation import validate_min
 from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT
 
@@ -116,9 +117,25 @@ def minimize(
         jac: Callable[[POINT], POINT] | None = None,
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
-        # Remove ignored parameters to suppress the warning of scipy.optimize.minimize
-        if self.is_bounds_ignored:
+        # Loop up for bounds specified in options or kwargs
+        if 'bounds' in self._kwargs:
+            bounds = self._kwargs['bounds']
+            del self._kwargs['bounds']
+
+        if 'bounds' in self._options:
+            bounds = self._options['bounds']
+            del self._options['bounds']
+
+        # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
+        if self.is_bounds_ignored and bounds is not None:
+            warnings.warn(
+                (f'Optimizer method {self._method:s} does not support bounds. '
+                 f'Got bounds={bounds}, setting bounds=None.'),
+                QiskitAlgorithmsOptimizersWarning
+            )
             bounds = None
+            
+        # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
             jac = None
 
@@ -145,15 +162,6 @@ def minimize(
             swapped_deprecated_args = True
             self._options["maxfun"] = self._options.pop("maxiter")
 
-        # Avoid clashing bounds defined in kwargs or _options
-        if 'bounds' in self._kwargs and not self.is_bounds_ignored:
-            bounds = self._kwargs['bounds']
-            del self._kwargs['bounds']
-
-        if 'bounds' in self._options and not self.is_bounds_ignored:
-            bounds = self._options['bounds']
-            del self._options['bounds']
-
         raw_result = minimize(
             fun=fun,
             x0=x0,

From 28d1493f63ddcb85a71d0d05d7b2b485498588cd Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Wed, 14 Feb 2024 15:28:46 +0100
Subject: [PATCH 20/39] Add algorithms  and optimizer specific warnings

---
 qiskit_algorithms/exceptions.py | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/qiskit_algorithms/exceptions.py b/qiskit_algorithms/exceptions.py
index 7eda309e..4b7d4b6b 100644
--- a/qiskit_algorithms/exceptions.py
+++ b/qiskit_algorithms/exceptions.py
@@ -1,6 +1,6 @@
 # This code is part of a Qiskit project.
 #
-# (C) Copyright IBM 2017, 2023.
+# (C) Copyright IBM 2017, 2024.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -10,12 +10,30 @@
 # copyright notice, and modified files need to carry a notice indicating
 # that they have been altered from the originals.
 
-"""Exception for errors raised by Algorithms module."""
+"""Exception and warnings for errors raised by Algorithms module."""
 
 from qiskit.exceptions import QiskitError
+import warnings
 
 
 class AlgorithmError(QiskitError):
     """For Algorithm specific errors."""
 
     pass
+
+class QiskitAlgorithmsWarning(UserWarning):
+    """Base class for warnings raised by Qiskit Algorithms."""
+
+    def __init__(self, *message):
+        """Set the error message."""
+        super().__init__(" ".join(message))
+        self.message = " ".join(message)
+
+    def __str__(self):
+        """Return the message."""
+        return repr(self.message)
+    
+class QiskitAlgorithmsOptimizersWarning(QiskitAlgorithmsWarning):
+    """For Algorithm specific warnings."""
+    
+    pass

From 8446806b2fc7ea154e629713b2bc947b2c143108 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Wed, 14 Feb 2024 18:11:57 +0100
Subject: [PATCH 21/39] Fix missed import

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 85446d0a..61f38d2d 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -15,6 +15,7 @@
 
 from collections.abc import Callable
 from typing import Any
+import warnings
 
 import numpy as np
 from scipy.optimize import minimize
@@ -129,8 +130,7 @@ def minimize(
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
         if self.is_bounds_ignored and bounds is not None:
             warnings.warn(
-                (f'Optimizer method {self._method:s} does not support bounds. '
-                 f'Got bounds={bounds}, setting bounds=None.'),
+                f'Optimizer method {self._method} does not support bounds.',
                 QiskitAlgorithmsOptimizersWarning
             )
             bounds = None

From f47d31a6d7de8d89b8041cc3c18b8d742346bc5a Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Thu, 15 Feb 2024 10:23:09 +0100
Subject: [PATCH 22/39] Apply style patches

---
 qiskit_algorithms/exceptions.py                |  7 ++++---
 .../optimizers/scipy_optimizer.py              | 18 +++++++++---------
 2 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/qiskit_algorithms/exceptions.py b/qiskit_algorithms/exceptions.py
index 4b7d4b6b..9d274e1a 100644
--- a/qiskit_algorithms/exceptions.py
+++ b/qiskit_algorithms/exceptions.py
@@ -13,7 +13,6 @@
 """Exception and warnings for errors raised by Algorithms module."""
 
 from qiskit.exceptions import QiskitError
-import warnings
 
 
 class AlgorithmError(QiskitError):
@@ -21,6 +20,7 @@ class AlgorithmError(QiskitError):
 
     pass
 
+
 class QiskitAlgorithmsWarning(UserWarning):
     """Base class for warnings raised by Qiskit Algorithms."""
 
@@ -32,8 +32,9 @@ def __init__(self, *message):
     def __str__(self):
         """Return the message."""
         return repr(self.message)
-    
+
+
 class QiskitAlgorithmsOptimizersWarning(QiskitAlgorithmsWarning):
     """For Algorithm specific warnings."""
-    
+
     pass
diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 61f38d2d..6b8dd9eb 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -119,22 +119,22 @@ def minimize(
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
         # Loop up for bounds specified in options or kwargs
-        if 'bounds' in self._kwargs:
-            bounds = self._kwargs['bounds']
-            del self._kwargs['bounds']
+        if "bounds" in self._kwargs:
+            bounds = self._kwargs["bounds"]
+            del self._kwargs["bounds"]
 
-        if 'bounds' in self._options:
-            bounds = self._options['bounds']
-            del self._options['bounds']
+        if "bounds" in self._options:
+            bounds = self._options["bounds"]
+            del self._options["bounds"]
 
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
         if self.is_bounds_ignored and bounds is not None:
             warnings.warn(
-                f'Optimizer method {self._method} does not support bounds.',
-                QiskitAlgorithmsOptimizersWarning
+                f"Optimizer method {self._method} does not support bounds.",
+                QiskitAlgorithmsOptimizersWarning,
             )
             bounds = None
-            
+
         # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
             jac = None

From d7a09863723edaf0bac89cd48a7eaa9f38fb06c9 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Mon, 19 Feb 2024 18:24:16 +0000
Subject: [PATCH 23/39] Add unit tests and introduce _bounds attribute

---
 .../optimizers/scipy_optimizer.py             |  27 ++--
 test/optimizers/test_optimizers.py            | 130 +++++++++++++++++-
 2 files changed, 146 insertions(+), 11 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 6b8dd9eb..2df993e5 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -78,6 +78,17 @@ def __init__(
         self._max_evals_grouped = max_evals_grouped
         self._kwargs = kwargs
 
+        # Initialise bounds and re-allocate if definition in options or kwargs
+        self._bounds = None
+
+        if "bounds" in self._kwargs:
+            self._bounds = self._kwargs["bounds"]
+            del self._kwargs["bounds"]
+
+        if "bounds" in self._options:
+            self._bounds = self._options["bounds"]
+            del self._options["bounds"]
+
     def get_support_level(self):
         """Return support level dictionary"""
         return {
@@ -118,22 +129,18 @@ def minimize(
         jac: Callable[[POINT], POINT] | None = None,
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
-        # Loop up for bounds specified in options or kwargs
-        if "bounds" in self._kwargs:
-            bounds = self._kwargs["bounds"]
-            del self._kwargs["bounds"]
 
-        if "bounds" in self._options:
-            bounds = self._options["bounds"]
-            del self._options["bounds"]
+        # Overwrite the previous bounds if bounds are parsed in .minimize()
+        if bounds is not None:
+            self._bounds = bounds
 
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
-        if self.is_bounds_ignored and bounds is not None:
+        if self.is_bounds_ignored and self._bounds is not None:
             warnings.warn(
                 f"Optimizer method {self._method} does not support bounds.",
                 QiskitAlgorithmsOptimizersWarning,
             )
-            bounds = None
+            self._bounds = None
 
         # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
@@ -167,7 +174,7 @@ def minimize(
             x0=x0,
             method=self._method,
             jac=jac,
-            bounds=bounds,
+            bounds=self._bounds,
             options=self._options,
             **self._kwargs,
         )
diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index 731bb90a..fe572659 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -49,6 +49,7 @@
     SciPyOptimizer,
 )
 from qiskit_algorithms.utils import algorithm_globals
+from qiskit_algorithms.exceptions import QiskitAlgorithmsOptimizersWarning
 
 
 @ddt
@@ -183,6 +184,133 @@ def callback(x):
         self.run_optimizer(optimizer, max_nfev=10000)
         self.assertTrue(values)  # Check the list is nonempty.
 
+    def test_scipy_optimizer_parse_bounds(self):
+        """
+        Test the parsing of bounds in SciPyOptimizer.
+
+        This function tests the behavior of parsing bounds in the SciPyOptimizer class.
+        It checks whether the bounds are correctly allocated and raises an assertion error
+        if unexpected TypeErrors are encountered during parsing.
+
+        Cases:
+        1. Test parsing bounds specified in the 'options' parameter of SciPyOptimizer.
+        2. Test parsing bounds specified directly in the 'bounds' parameter (as kwarg) of SciPyOptimizer.
+        3. Test parsing bounds specified directly in the 'minimize' method of SciPyOptimizer.
+
+        This function aims to ensure that bounds are correctly allocated within the object attributes
+        and not in the 'options' or 'kwargs' dictionaries, to avoid known parsing issues.
+
+        Raises:
+            AssertionError: If unexpected TypeErrors are raised during parsing of bounds.
+        """
+
+        cases = {}
+
+        try:
+            optimizer = SciPyOptimizer('SLSQP', maxiter=10, options={'bounds': [(0., 1.)]})
+
+            # Make sure the minimize method takes place
+            optimizer.minimize(lambda x: -x, 1.)
+
+        except TypeError:
+            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
+            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._options.")
+
+        cases['SciPyOptimizer._options'] = optimizer
+
+        try:
+            optimizer = SciPyOptimizer('SLSQP', maxiter=10, bounds=[(0., 1.)])
+            optimizer.minimize(lambda x: -x, 1.)
+
+        except TypeError:
+            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
+            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._kwargs.")
+
+        cases['SciPyOptimizer._kwargs'] = optimizer
+
+        try:
+            optimizer = SciPyOptimizer('SLSQP', maxiter=10)
+            optimizer.minimize(lambda x: -x, 1., bounds=[(0., 1.)])
+
+        except TypeError:
+            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
+            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...).")
+
+        cases['SciPyOptimizer.minimize'] = optimizer
+
+        # Check that the bounds are correctly allocated in the attr and not in dicts
+        # This avoids the Scipy parsing issue documented in
+        # https://github.com/qiskit-community/qiskit-machine-learning/issues/570
+        for optimizer in cases.values():
+            self.assertTrue(hasattr(optimizer, '_bounds'))
+            self.assertFalse('bounds' in optimizer._options)
+            self.assertFalse('bounds' in optimizer._kwargs)
+            self.assertFalse(optimizer._bounds is None)  # Because we set bounds to a non-empty tuple
+
+    def test_scipy_optimizer_bounds_overwrite(self):
+        """
+            Test bounds overwrite behavior of SciPyOptimizer.
+
+            This function tests the behavior of SciPyOptimizer when new bounds are supplied to it
+            after initial bounds have been set. It verifies that the new bounds overwrite the old
+            ones correctly.
+
+            Raises:
+                AssertionError: If the bounds overwrite behavior is incorrect.
+            """
+        # Initialize SciPyOptimizer with initial bounds
+        optimizer = SciPyOptimizer('SLSQP', maxiter=10, bounds=[(0., 1.)])
+
+        # Record the old bounds
+        old_bounds = np.asarray(list(optimizer._bounds[0]))
+
+        # Call the minimize method with new bounds
+        optimizer.minimize(lambda x: -x, 1., bounds=[(2., 3.)])
+
+        # Record the new bounds
+        new_bounds = np.asarray(list(optimizer._bounds[0]))
+
+        # Verify that the old bounds and new bounds are not identical
+        self.assertFalse(np.any(old_bounds == new_bounds))
+
+        # Verify that the new bounds match the expected values
+        self.assertTrue(np.all(new_bounds == np.array([2., 3.])))
+
+    def test_scipy_optimizer_warning(self):
+        """
+        Test warning behavior of SciPyOptimizer with unsupported bounds.
+
+        This function tests the warning behavior of SciPyOptimizer when using the COBYLA optimizer
+        with bounds supplied, which are unsupported by COBYLA. The function verifies that a specific
+        warning, QiskitAlgorithmsOptimizersWarning, is raised when bounds are supplied to COBYLA,
+        and the optimizer ignores them as expected.
+
+        Cases:
+        1. Test warning when bounds are supplied via 'options' parameter.
+        2. Test warning when bounds are supplied directly as kwargs.
+        3. Test warning when bounds are supplied via 'minimize' method.
+
+        Raises:
+            AssertionError: If the expected warning is not raised.
+        """
+
+        # Cobyla does not support bounds and is expected to warn if bounds (then ignored) are supplied
+        optimizer = SciPyOptimizer('cobyla', maxiter=10, options={'bounds': [(0., 1.)]})
+
+        # Using lambda function to pass a function that will raise the specific warning
+        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            optimizer.minimize(lambda x: -x, 1.)
+
+        # The same should happen with bounds parsed as kwargs
+        optimizer = SciPyOptimizer('cobyla', maxiter=10, bounds=[(0., 1.)])
+        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            optimizer.minimize(lambda x: -x, 1.)
+
+        # The same should happen with bounds parsed via minimize(...)
+        optimizer = SciPyOptimizer('cobyla', maxiter=10)
+        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            optimizer.minimize(lambda x: -x, 1., bounds=[(0., 1.)])
+
     # ESCH and ISRES do not do well with rosen
     @data(
         (CRS, True),
@@ -346,7 +474,7 @@ def test_spsa_custom_iterators(self):
         def powerlaw():
             n = 0
             while True:
-                yield rate**n
+                yield rate ** n
                 n += 1
 
         def steps():

From adfb264970f863c7216123cf4ada55ab19857321 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Mon, 19 Feb 2024 19:00:36 +0000
Subject: [PATCH 24/39] Formatting and final patches to tests

---
 requirements.txt                   |  1 +
 test/optimizers/test_optimizers.py | 80 ++++++++++++++++--------------
 2 files changed, 45 insertions(+), 36 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index a9a2724a..0143579c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
 qiskit>=0.44
 scipy>=1.4
 numpy>=1.17
+scikit-quant
diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index fe572659..15cbbac6 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -150,7 +150,7 @@ def test_gsls(self):
             max_eval=10000,
             min_step_size=1.0e-12,
         )
-        x_0 = [1.3, 0.7, 0.8, 1.9, 1.2]
+        x_0 = np.asarray([1.3, 0.7, 0.8, 1.9, 1.2])
 
         algorithm_globals.random_seed = 1
         res = optimizer.minimize(rosen, x_0)
@@ -204,68 +204,76 @@ def test_scipy_optimizer_parse_bounds(self):
             AssertionError: If unexpected TypeErrors are raised during parsing of bounds.
         """
 
-        cases = {}
+        optimizer_instances = []
 
         try:
-            optimizer = SciPyOptimizer('SLSQP', maxiter=10, options={'bounds': [(0., 1.)]})
+            optimizer = SciPyOptimizer("SLSQP", options={"bounds": [(0.0, 1.0)]})
 
             # Make sure the minimize method takes place
-            optimizer.minimize(lambda x: -x, 1.)
+            optimizer.minimize(lambda x: -x, 1.0)
 
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._options.")
+            self.fail(
+                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._options."
+            )
 
-        cases['SciPyOptimizer._options'] = optimizer
+        optimizer_instances.append(optimizer)
 
         try:
-            optimizer = SciPyOptimizer('SLSQP', maxiter=10, bounds=[(0., 1.)])
-            optimizer.minimize(lambda x: -x, 1.)
+            optimizer = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
+            optimizer.minimize(lambda x: -x, 1.0)
 
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._kwargs.")
+            self.fail(
+                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._kwargs."
+            )
 
-        cases['SciPyOptimizer._kwargs'] = optimizer
+        optimizer_instances.append(optimizer)
 
         try:
-            optimizer = SciPyOptimizer('SLSQP', maxiter=10)
-            optimizer.minimize(lambda x: -x, 1., bounds=[(0., 1.)])
+            optimizer = SciPyOptimizer("SLSQP")
+            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail("TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...).")
+            self.fail(
+                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...)."
+            )
 
-        cases['SciPyOptimizer.minimize'] = optimizer
+        optimizer_instances.append(optimizer)
 
         # Check that the bounds are correctly allocated in the attr and not in dicts
         # This avoids the Scipy parsing issue documented in
         # https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-        for optimizer in cases.values():
-            self.assertTrue(hasattr(optimizer, '_bounds'))
-            self.assertFalse('bounds' in optimizer._options)
-            self.assertFalse('bounds' in optimizer._kwargs)
-            self.assertFalse(optimizer._bounds is None)  # Because we set bounds to a non-empty tuple
+        for optimizer in optimizer_instances:
+            self.assertTrue(hasattr(optimizer, "_bounds"))
+            self.assertFalse("bounds" in optimizer._options)
+            self.assertFalse("bounds" in optimizer._kwargs)
+            self.assertFalse(
+                optimizer._bounds is None
+            )  # Because we set bounds to a non-empty tuple
 
     def test_scipy_optimizer_bounds_overwrite(self):
         """
-            Test bounds overwrite behavior of SciPyOptimizer.
+        Test bounds overwrite behavior of SciPyOptimizer.
 
-            This function tests the behavior of SciPyOptimizer when new bounds are supplied to it
-            after initial bounds have been set. It verifies that the new bounds overwrite the old
-            ones correctly.
+        This function tests the behavior of SciPyOptimizer when new bounds are supplied to it
+        after initial bounds have been set. It verifies that the new bounds overwrite the old
+        ones correctly.
 
-            Raises:
-                AssertionError: If the bounds overwrite behavior is incorrect.
-            """
+        Raises:
+            AssertionError: If the bounds overwrite behavior is incorrect.
+        """
         # Initialize SciPyOptimizer with initial bounds
-        optimizer = SciPyOptimizer('SLSQP', maxiter=10, bounds=[(0., 1.)])
+        optimizer = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
 
         # Record the old bounds
         old_bounds = np.asarray(list(optimizer._bounds[0]))
 
         # Call the minimize method with new bounds
-        optimizer.minimize(lambda x: -x, 1., bounds=[(2., 3.)])
+        optimizer.minimize(lambda x: -x, 1.0, bounds=[(2.0, 3.0)])
 
         # Record the new bounds
         new_bounds = np.asarray(list(optimizer._bounds[0]))
@@ -274,7 +282,7 @@ def test_scipy_optimizer_bounds_overwrite(self):
         self.assertFalse(np.any(old_bounds == new_bounds))
 
         # Verify that the new bounds match the expected values
-        self.assertTrue(np.all(new_bounds == np.array([2., 3.])))
+        self.assertTrue(np.all(new_bounds == np.array([2.0, 3.0])))
 
     def test_scipy_optimizer_warning(self):
         """
@@ -295,21 +303,21 @@ def test_scipy_optimizer_warning(self):
         """
 
         # Cobyla does not support bounds and is expected to warn if bounds (then ignored) are supplied
-        optimizer = SciPyOptimizer('cobyla', maxiter=10, options={'bounds': [(0., 1.)]})
+        optimizer = SciPyOptimizer("cobyla", options={"bounds": [(0.0, 1.0)]})
 
         # Using lambda function to pass a function that will raise the specific warning
         with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1.)
+            optimizer.minimize(lambda x: -x, 1.0)
 
         # The same should happen with bounds parsed as kwargs
-        optimizer = SciPyOptimizer('cobyla', maxiter=10, bounds=[(0., 1.)])
+        optimizer = SciPyOptimizer("cobyla", bounds=[(0.0, 1.0)])
         with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1.)
+            optimizer.minimize(lambda x: -x, 1.0)
 
         # The same should happen with bounds parsed via minimize(...)
-        optimizer = SciPyOptimizer('cobyla', maxiter=10)
+        optimizer = SciPyOptimizer("cobyla")
         with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1., bounds=[(0., 1.)])
+            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
     # ESCH and ISRES do not do well with rosen
     @data(
@@ -474,7 +482,7 @@ def test_spsa_custom_iterators(self):
         def powerlaw():
             n = 0
             while True:
-                yield rate ** n
+                yield rate**n
                 n += 1
 
         def steps():

From cd7a34e5aca84e36e102132c4b50f52fe2fa681e Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Fri, 23 Feb 2024 16:42:58 +0000
Subject: [PATCH 25/39] Bounds parsing forbidden in __init__ and allowed in
 minimize()

---
 .../optimizers/scipy_optimizer.py             |  24 ++-
 test/optimizers/test_optimizers.py            | 148 ++++++------------
 2 files changed, 63 insertions(+), 109 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 2df993e5..215534a9 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -82,12 +82,15 @@ def __init__(
         self._bounds = None
 
         if "bounds" in self._kwargs:
-            self._bounds = self._kwargs["bounds"]
-            del self._kwargs["bounds"]
-
+            raise RuntimeError(
+                "Optimizer bounds should be parsed in SciPyOptimizer.minimize() and not in "
+                "SciPyOptimizer.__init__()."
+            )
         if "bounds" in self._options:
-            self._bounds = self._options["bounds"]
-            del self._options["bounds"]
+            raise RuntimeError(
+                "Optimizer bounds should be parsed in SciPyOptimizer.minimize() as a kwarg and not as "
+                "options."
+            )
 
     def get_support_level(self):
         """Return support level dictionary"""
@@ -130,17 +133,24 @@ def minimize(
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
 
-        # Overwrite the previous bounds if bounds are parsed in .minimize()
+        # Overwrite the previous bounds
         if bounds is not None:
             self._bounds = bounds
 
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
         if self.is_bounds_ignored and self._bounds is not None:
             warnings.warn(
-                f"Optimizer method {self._method} does not support bounds.",
+                f"Optimizer method {self._method} does not support bounds. Bounds ignored.",
                 QiskitAlgorithmsOptimizersWarning,
             )
             self._bounds = None
+        elif not self.is_bounds_ignored and self._bounds is None:
+            warnings.warn(
+                f"Optimizer method {self._method} may require defining bounds. "
+                f"Check the Scipy documentation for more info: "
+                f"https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html",
+                QiskitAlgorithmsOptimizersWarning,
+            )
 
         # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index 15cbbac6..ea9cdca1 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -186,55 +186,34 @@ def callback(x):
 
     def test_scipy_optimizer_parse_bounds(self):
         """
-        Test the parsing of bounds in SciPyOptimizer.
-
-        This function tests the behavior of parsing bounds in the SciPyOptimizer class.
-        It checks whether the bounds are correctly allocated and raises an assertion error
-        if unexpected TypeErrors are encountered during parsing.
-
-        Cases:
-        1. Test parsing bounds specified in the 'options' parameter of SciPyOptimizer.
-        2. Test parsing bounds specified directly in the 'bounds' parameter (as kwarg) of SciPyOptimizer.
-        3. Test parsing bounds specified directly in the 'minimize' method of SciPyOptimizer.
-
-        This function aims to ensure that bounds are correctly allocated within the object attributes
-        and not in the 'options' or 'kwargs' dictionaries, to avoid known parsing issues.
+        Test the parsing of bounds in SciPyOptimizer.minimize method. Verifies that the bounds are
+        correctly parsed and set within the optimizer object.
 
         Raises:
-            AssertionError: If unexpected TypeErrors are raised during parsing of bounds.
-        """
-
-        optimizer_instances = []
+            AssertionError: If any of the assertions fail.
+            AssertionError: If a TypeError is raised unexpectedly while parsing bounds.
 
+        """
         try:
-            optimizer = SciPyOptimizer("SLSQP", options={"bounds": [(0.0, 1.0)]})
-
-            # Make sure the minimize method takes place
-            optimizer.minimize(lambda x: -x, 1.0)
-
-        except TypeError:
-            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail(
-                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._options."
-            )
+            # Initialize SciPyOptimizer instance with SLSQP method
+            optimizer = SciPyOptimizer("SLSQP")
 
-        optimizer_instances.append(optimizer)
+            # Ensure that _bounds attribute is initially present and =None
+            self.assertTrue(hasattr(optimizer, "_bounds"))
+            self.assertTrue(optimizer._bounds is None)
 
-        try:
-            optimizer = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
-            optimizer.minimize(lambda x: -x, 1.0)
+            # Call minimize method with a simple lambda function and bounds
+            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
-        except TypeError:
-            # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-            self.fail(
-                "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer._kwargs."
-            )
+            # Assert that _bounds attribute is set after minimize method call
+            self.assertTrue(hasattr(optimizer, "_bounds"))
 
-        optimizer_instances.append(optimizer)
+            # Assert that "bounds" is not present in optimizer options and kwargs
+            self.assertFalse("bounds" in optimizer._options)
+            self.assertFalse("bounds" in optimizer._kwargs)
 
-        try:
-            optimizer = SciPyOptimizer("SLSQP")
-            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
+            # Assert that _bounds attribute is not None after setting bounds
+            self.assertFalse(optimizer._bounds is None)
 
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
@@ -242,83 +221,48 @@ def test_scipy_optimizer_parse_bounds(self):
                 "TypeError was raised unexpectedly when parsing bounds in SciPyOptimizer.minimize(...)."
             )
 
-        optimizer_instances.append(optimizer)
+        # Finally, expect exceptions if bounds are parsed incorrectly, i.e. differently than as in Scipy
+        with self.assertRaises(RuntimeError):
+            _ = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
+        with self.assertRaises(RuntimeError):
+            _ = SciPyOptimizer("SLSQP", options={"bounds": [(0.0, 1.0)]})
 
-        # Check that the bounds are correctly allocated in the attr and not in dicts
-        # This avoids the Scipy parsing issue documented in
-        # https://github.com/qiskit-community/qiskit-machine-learning/issues/570
-        for optimizer in optimizer_instances:
-            self.assertTrue(hasattr(optimizer, "_bounds"))
-            self.assertFalse("bounds" in optimizer._options)
-            self.assertFalse("bounds" in optimizer._kwargs)
-            self.assertFalse(
-                optimizer._bounds is None
-            )  # Because we set bounds to a non-empty tuple
-
-    def test_scipy_optimizer_bounds_overwrite(self):
+    def test_scipy_optimizer_warning(self):
         """
-        Test bounds overwrite behavior of SciPyOptimizer.
-
-        This function tests the behavior of SciPyOptimizer when new bounds are supplied to it
-        after initial bounds have been set. It verifies that the new bounds overwrite the old
-        ones correctly.
+        Test warning handling in SciPyOptimizer.minimize method when using unsupported
+        optimizer. Verifies that a warning is raised when attempting to use an
+        optimizer that does not support bounds.
 
         Raises:
-            AssertionError: If the bounds overwrite behavior is incorrect.
-        """
-        # Initialize SciPyOptimizer with initial bounds
-        optimizer = SciPyOptimizer("SLSQP", bounds=[(0.0, 1.0)])
-
-        # Record the old bounds
-        old_bounds = np.asarray(list(optimizer._bounds[0]))
-
-        # Call the minimize method with new bounds
-        optimizer.minimize(lambda x: -x, 1.0, bounds=[(2.0, 3.0)])
-
-        # Record the new bounds
-        new_bounds = np.asarray(list(optimizer._bounds[0]))
-
-        # Verify that the old bounds and new bounds are not identical
-        self.assertFalse(np.any(old_bounds == new_bounds))
-
-        # Verify that the new bounds match the expected values
-        self.assertTrue(np.all(new_bounds == np.array([2.0, 3.0])))
+            AssertionError: If the expected warning is not raised.
 
-    def test_scipy_optimizer_warning(self):
         """
-        Test warning behavior of SciPyOptimizer with unsupported bounds.
-
-        This function tests the warning behavior of SciPyOptimizer when using the COBYLA optimizer
-        with bounds supplied, which are unsupported by COBYLA. The function verifies that a specific
-        warning, QiskitAlgorithmsOptimizersWarning, is raised when bounds are supplied to COBYLA,
-        and the optimizer ignores them as expected.
+        # Initialize SciPyOptimizer instance with "cobyla" method
+        optimizer = SciPyOptimizer("cobyla")
 
-        Cases:
-        1. Test warning when bounds are supplied via 'options' parameter.
-        2. Test warning when bounds are supplied directly as kwargs.
-        3. Test warning when bounds are supplied via 'minimize' method.
+        # Use assertWarns context manager to check if the expected warning is raised
+        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            # Call minimize method with a simple lambda function and bounds for unsupported optimizer
+            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
-        Raises:
-            AssertionError: If the expected warning is not raised.
+    def test_scipy_optimizer_bounds_required(self):
         """
+        Test bounds requirement handling in SciPyOptimizer.minimize method for optimizers
+        that require bounds. Verifies that an exception is raised when attempting to use
+        an optimizer that requires bounds without supplying them.
 
-        # Cobyla does not support bounds and is expected to warn if bounds (then ignored) are supplied
-        optimizer = SciPyOptimizer("cobyla", options={"bounds": [(0.0, 1.0)]})
+        Raises:
+            AssertionError: If the expected exception is not raised.
 
-        # Using lambda function to pass a function that will raise the specific warning
-        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1.0)
+        """
+        # Initialize SciPyOptimizer instance with "SLSQP" method
+        optimizer = SciPyOptimizer("SLSQP")
 
-        # The same should happen with bounds parsed as kwargs
-        optimizer = SciPyOptimizer("cobyla", bounds=[(0.0, 1.0)])
+        # Use assertWarns context manager to check if the expected exception is raised
         with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
+            # Call minimize method with a simple lambda function without supplying required bounds
             optimizer.minimize(lambda x: -x, 1.0)
 
-        # The same should happen with bounds parsed via minimize(...)
-        optimizer = SciPyOptimizer("cobyla")
-        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
-
     # ESCH and ISRES do not do well with rosen
     @data(
         (CRS, True),

From 960cd7cdea781fd66e79300c974731ab0e6a74fa Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Sat, 24 Feb 2024 15:14:33 +0000
Subject: [PATCH 26/39] Add bounds type hinting

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 215534a9..69ff5384 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -79,7 +79,7 @@ def __init__(
         self._kwargs = kwargs
 
         # Initialise bounds and re-allocate if definition in options or kwargs
-        self._bounds = None
+        self._bounds: list[tuple[float, float]] | None = None
 
         if "bounds" in self._kwargs:
             raise RuntimeError(

From 91ffeff26962262a7fec71bef20aae18334e4c23 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Sat, 24 Feb 2024 15:16:21 +0000
Subject: [PATCH 27/39] Change copyright in tests

---
 test/optimizers/test_optimizers.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index ea9cdca1..8fb9151e 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -1,6 +1,6 @@
 # This code is part of a Qiskit project.
 #
-# (C) Copyright IBM 2018, 2023.
+# (C) Copyright IBM 2018, 2024.
 #
 # This code is licensed under the Apache License, Version 2.0. You may
 # obtain a copy of this license in the LICENSE.txt file in the root directory

From ca0d277bc8ddef15f5b815c0bb9b49b53d0836e5 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Sat, 24 Feb 2024 18:59:01 +0000
Subject: [PATCH 28/39] Remove scikit-quant dependency

---
 requirements.txt | 1 -
 1 file changed, 1 deletion(-)

diff --git a/requirements.txt b/requirements.txt
index 0143579c..a9a2724a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,3 @@
 qiskit>=0.44
 scipy>=1.4
 numpy>=1.17
-scikit-quant

From a7bbd21f25321bc87162b6aa17f0031a4401faa6 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Sat, 24 Feb 2024 19:01:38 +0000
Subject: [PATCH 29/39] Update qiskit_algorithms/optimizers/scipy_optimizer.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 69ff5384..d1941a6b 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -78,7 +78,7 @@ def __init__(
         self._max_evals_grouped = max_evals_grouped
         self._kwargs = kwargs
 
-        # Initialise bounds and re-allocate if definition in options or kwargs
+        # Initialize bounds and re-allocate if definition in options or kwargs
         self._bounds: list[tuple[float, float]] | None = None
 
         if "bounds" in self._kwargs:

From 3611957ec64bc9b415665de46c52c2b870951054 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 27 Feb 2024 19:33:04 +0000
Subject: [PATCH 30/39] Update qiskit_algorithms/optimizers/scipy_optimizer.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index d1941a6b..2af01040 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -83,8 +83,8 @@ def __init__(
 
         if "bounds" in self._kwargs:
             raise RuntimeError(
-                "Optimizer bounds should be parsed in SciPyOptimizer.minimize() and not in "
-                "SciPyOptimizer.__init__()."
+                "Optimizer bounds should be passed to SciPyOptimizer.minimize() and is not  "
+                "supported in SciPyOptimizer constructor kwargs."
             )
         if "bounds" in self._options:
             raise RuntimeError(

From 08284c9bcf62746c3613ba5702b2b4de35bf9d40 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 27 Feb 2024 19:34:32 +0000
Subject: [PATCH 31/39] Update qiskit_algorithms/optimizers/scipy_optimizer.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 2af01040..8df1ed45 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -88,7 +88,7 @@ def __init__(
             )
         if "bounds" in self._options:
             raise RuntimeError(
-                "Optimizer bounds should be parsed in SciPyOptimizer.minimize() as a kwarg and not as "
+                "Optimizer bounds should be passed to SciPyOptimizer.minimize() and is not as "
                 "options."
             )
 

From b9a32dc24c9382b8ffd047aba0622f1b29cced63 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Tue, 27 Feb 2024 19:45:52 +0000
Subject: [PATCH 32/39] Remove internal implementation checks in
 test_optimizers.py

---
 test/optimizers/test_optimizers.py | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index 8fb9151e..989d3626 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -198,10 +198,6 @@ def test_scipy_optimizer_parse_bounds(self):
             # Initialize SciPyOptimizer instance with SLSQP method
             optimizer = SciPyOptimizer("SLSQP")
 
-            # Ensure that _bounds attribute is initially present and =None
-            self.assertTrue(hasattr(optimizer, "_bounds"))
-            self.assertTrue(optimizer._bounds is None)
-
             # Call minimize method with a simple lambda function and bounds
             optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 

From 7a5fc0c64bd221476fc7154407a398ec350117d3 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Thu, 29 Feb 2024 19:48:41 +0000
Subject: [PATCH 33/39] Remove loose incompatible-bounds logic

---
 .../optimizers/scipy_optimizer.py             | 17 +--------
 test/optimizers/test_optimizers.py            | 37 -------------------
 2 files changed, 2 insertions(+), 52 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 8df1ed45..629b33b6 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -15,12 +15,10 @@
 
 from collections.abc import Callable
 from typing import Any
-import warnings
 
 import numpy as np
 from scipy.optimize import minimize
 
-from qiskit_algorithms.exceptions import QiskitAlgorithmsOptimizersWarning
 from qiskit_algorithms.utils.validation import validate_min
 from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT
 
@@ -133,24 +131,13 @@ def minimize(
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
 
-        # Overwrite the previous bounds
+        # Allocate bounds
         if bounds is not None:
             self._bounds = bounds
 
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
-        if self.is_bounds_ignored and self._bounds is not None:
-            warnings.warn(
-                f"Optimizer method {self._method} does not support bounds. Bounds ignored.",
-                QiskitAlgorithmsOptimizersWarning,
-            )
+        if self.is_bounds_ignored:
             self._bounds = None
-        elif not self.is_bounds_ignored and self._bounds is None:
-            warnings.warn(
-                f"Optimizer method {self._method} may require defining bounds. "
-                f"Check the Scipy documentation for more info: "
-                f"https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html",
-                QiskitAlgorithmsOptimizersWarning,
-            )
 
         # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index 989d3626..7512f8cd 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -49,7 +49,6 @@
     SciPyOptimizer,
 )
 from qiskit_algorithms.utils import algorithm_globals
-from qiskit_algorithms.exceptions import QiskitAlgorithmsOptimizersWarning
 
 
 @ddt
@@ -223,42 +222,6 @@ def test_scipy_optimizer_parse_bounds(self):
         with self.assertRaises(RuntimeError):
             _ = SciPyOptimizer("SLSQP", options={"bounds": [(0.0, 1.0)]})
 
-    def test_scipy_optimizer_warning(self):
-        """
-        Test warning handling in SciPyOptimizer.minimize method when using unsupported
-        optimizer. Verifies that a warning is raised when attempting to use an
-        optimizer that does not support bounds.
-
-        Raises:
-            AssertionError: If the expected warning is not raised.
-
-        """
-        # Initialize SciPyOptimizer instance with "cobyla" method
-        optimizer = SciPyOptimizer("cobyla")
-
-        # Use assertWarns context manager to check if the expected warning is raised
-        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            # Call minimize method with a simple lambda function and bounds for unsupported optimizer
-            optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
-
-    def test_scipy_optimizer_bounds_required(self):
-        """
-        Test bounds requirement handling in SciPyOptimizer.minimize method for optimizers
-        that require bounds. Verifies that an exception is raised when attempting to use
-        an optimizer that requires bounds without supplying them.
-
-        Raises:
-            AssertionError: If the expected exception is not raised.
-
-        """
-        # Initialize SciPyOptimizer instance with "SLSQP" method
-        optimizer = SciPyOptimizer("SLSQP")
-
-        # Use assertWarns context manager to check if the expected exception is raised
-        with self.assertWarns(QiskitAlgorithmsOptimizersWarning):
-            # Call minimize method with a simple lambda function without supplying required bounds
-            optimizer.minimize(lambda x: -x, 1.0)
-
     # ESCH and ISRES do not do well with rosen
     @data(
         (CRS, True),

From a467036cf9b8c3f6e658d3d2cd4c2a27c81c1c09 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Thu, 29 Feb 2024 19:52:55 +0000
Subject: [PATCH 34/39] Remove loose incompatible-bounds logic

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index 629b33b6..a2110fe3 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -131,7 +131,7 @@ def minimize(
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
 
-        # Allocate bounds
+        # Allocate bounds attribute
         if bounds is not None:
             self._bounds = bounds
 

From a8d52e075d9e40220b6b9d7e8f23befd6cb9adc0 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Thu, 7 Mar 2024 14:08:47 +0100
Subject: [PATCH 35/39] Update qiskit_algorithms/optimizers/scipy_optimizer.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index a2110fe3..f1a17b40 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -86,7 +86,7 @@ def __init__(
             )
         if "bounds" in self._options:
             raise RuntimeError(
-                "Optimizer bounds should be passed to SciPyOptimizer.minimize() and is not as "
+                "Optimizer bounds should be passed to SciPyOptimizer.minimize() and not as "
                 "options."
             )
 

From 0f602e736c726cebaea7bd6bf34e4b00c32b2c32 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Thu, 7 Mar 2024 23:04:23 +0100
Subject: [PATCH 36/39] Remove redundant `_bounds` attributes

---
 qiskit_algorithms/optimizers/scipy_optimizer.py | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/qiskit_algorithms/optimizers/scipy_optimizer.py b/qiskit_algorithms/optimizers/scipy_optimizer.py
index f1a17b40..f8cbaf84 100644
--- a/qiskit_algorithms/optimizers/scipy_optimizer.py
+++ b/qiskit_algorithms/optimizers/scipy_optimizer.py
@@ -76,9 +76,6 @@ def __init__(
         self._max_evals_grouped = max_evals_grouped
         self._kwargs = kwargs
 
-        # Initialize bounds and re-allocate if definition in options or kwargs
-        self._bounds: list[tuple[float, float]] | None = None
-
         if "bounds" in self._kwargs:
             raise RuntimeError(
                 "Optimizer bounds should be passed to SciPyOptimizer.minimize() and is not  "
@@ -131,13 +128,9 @@ def minimize(
         bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
 
-        # Allocate bounds attribute
-        if bounds is not None:
-            self._bounds = bounds
-
         # Remove ignored bounds to suppress the warning of scipy.optimize.minimize
         if self.is_bounds_ignored:
-            self._bounds = None
+            bounds = None
 
         # Remove ignored gradient to suppress the warning of scipy.optimize.minimize
         if self.is_gradient_ignored:
@@ -171,7 +164,7 @@ def minimize(
             x0=x0,
             method=self._method,
             jac=jac,
-            bounds=self._bounds,
+            bounds=bounds,
             options=self._options,
             **self._kwargs,
         )

From 1472f4f2c032c22fb0b72b53e4537b5f1e13119d Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Thu, 7 Mar 2024 23:15:45 +0100
Subject: [PATCH 37/39] Add release note

---
 ...ise-error-incorrect-bounds-passing-c878f155221fa8f4.yaml | 6 ++++++
 1 file changed, 6 insertions(+)
 create mode 100644 releasenotes/notes/0.3/raise-error-incorrect-bounds-passing-c878f155221fa8f4.yaml

diff --git a/releasenotes/notes/0.3/raise-error-incorrect-bounds-passing-c878f155221fa8f4.yaml b/releasenotes/notes/0.3/raise-error-incorrect-bounds-passing-c878f155221fa8f4.yaml
new file mode 100644
index 00000000..c2a0908c
--- /dev/null
+++ b/releasenotes/notes/0.3/raise-error-incorrect-bounds-passing-c878f155221fa8f4.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    Resolved the issue with multiply-defined Scipy bounds in Optimizers. In line with Scipy, now only the ``minimize()``
+    method supports the ``bounds`` keyword. An error is raised when trying to pass ``bounds`` in the Optimizer constructor
+    via ``kwargs`` or ``options``.

From f4d76fa9daf593191e319e6ac366037741334a3d Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Wed, 13 Mar 2024 16:02:05 +0000
Subject: [PATCH 38/39] Remove _bounds attr from tests, add `list[tuple[None,
 None]]` as possible bounds typing

---
 qiskit_algorithms/optimizers/gsls.py | 2 +-
 test/optimizers/test_optimizers.py   | 6 ------
 2 files changed, 1 insertion(+), 7 deletions(-)

diff --git a/qiskit_algorithms/optimizers/gsls.py b/qiskit_algorithms/optimizers/gsls.py
index c6b423db..76ba3db6 100644
--- a/qiskit_algorithms/optimizers/gsls.py
+++ b/qiskit_algorithms/optimizers/gsls.py
@@ -110,7 +110,7 @@ def minimize(
         fun: Callable[[POINT], float],
         x0: POINT,
         jac: Callable[[POINT], POINT] | None = None,
-        bounds: list[tuple[float, float]] | None = None,
+        bounds: list[tuple[float, float]] | list[tuple[None, None]] | None = None,
     ) -> OptimizerResult:
         if not isinstance(x0, np.ndarray):
             x0 = np.asarray(x0)
diff --git a/test/optimizers/test_optimizers.py b/test/optimizers/test_optimizers.py
index 7512f8cd..c3b9e4cb 100644
--- a/test/optimizers/test_optimizers.py
+++ b/test/optimizers/test_optimizers.py
@@ -200,16 +200,10 @@ def test_scipy_optimizer_parse_bounds(self):
             # Call minimize method with a simple lambda function and bounds
             optimizer.minimize(lambda x: -x, 1.0, bounds=[(0.0, 1.0)])
 
-            # Assert that _bounds attribute is set after minimize method call
-            self.assertTrue(hasattr(optimizer, "_bounds"))
-
             # Assert that "bounds" is not present in optimizer options and kwargs
             self.assertFalse("bounds" in optimizer._options)
             self.assertFalse("bounds" in optimizer._kwargs)
 
-            # Assert that _bounds attribute is not None after setting bounds
-            self.assertFalse(optimizer._bounds is None)
-
         except TypeError:
             # This would give: https://github.com/qiskit-community/qiskit-machine-learning/issues/570
             self.fail(

From 997f38b91048c65f1413be31fe64516d833d1987 Mon Sep 17 00:00:00 2001
From: Edoardo Altamura <38359901+edoaltamura@users.noreply.github.com>
Date: Wed, 13 Mar 2024 16:03:00 +0000
Subject: [PATCH 39/39] Remove _bounds attr from tests

---
 qiskit_algorithms/optimizers/gsls.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/qiskit_algorithms/optimizers/gsls.py b/qiskit_algorithms/optimizers/gsls.py
index 76ba3db6..c6b423db 100644
--- a/qiskit_algorithms/optimizers/gsls.py
+++ b/qiskit_algorithms/optimizers/gsls.py
@@ -110,7 +110,7 @@ def minimize(
         fun: Callable[[POINT], float],
         x0: POINT,
         jac: Callable[[POINT], POINT] | None = None,
-        bounds: list[tuple[float, float]] | list[tuple[None, None]] | None = None,
+        bounds: list[tuple[float, float]] | None = None,
     ) -> OptimizerResult:
         if not isinstance(x0, np.ndarray):
             x0 = np.asarray(x0)